Sparky is FuseBox's built-in task automation framework for creating custom build workflows, CI/CD integration, and development tools. It provides a fluent API for file operations, task management, and build orchestration.
function sparky<T>(Ctx: new () => T): {
task: (name: string | RegExp, fn: (ctx: T) => void) => void;
exec: (name: string) => Promise<void>;
src: (glob: string) => SparkyChain;
rm: (folder: string) => void;
};The sparky() function creates a task management system with a custom context type. It automatically executes tasks based on command-line arguments.
task(name: string | RegExp, fn: (ctx: T) => void): void;Define named tasks that can be executed individually or as part of a workflow. Tasks can use string names or regex patterns for flexible matching.
exec(name: string): Promise<void>;Execute a specific task by name. Tasks are automatically timed and logged during execution.
src(glob: string): SparkyChain;Select files using glob patterns for processing. Returns a chainable interface for file operations.
rm(folder: string): void;Remove directories and their contents. Paths are resolved relative to the script location.
The src() method returns a chainable interface for file operations:
interface SparkyChain {
dest(destination: string): SparkyChain;
clean(destination: string): SparkyChain;
watch(glob?: string): SparkyChain;
file(pattern: string, handler: (file: SparkyFile) => void): SparkyChain;
exec(): Promise<void>;
}
interface SparkyFile {
root: string;
name: string;
filepath: string;
absPath: string;
contents: Buffer;
read(): void;
save(): void;
}import { sparky } from "fuse-box";
class BuildContext {
isProduction = false;
setProduction() {
this.isProduction = true;
}
}
const { task, exec, src, rm } = sparky(BuildContext);
task("default", async (ctx) => {
console.log("Running default task");
await exec("clean");
await exec("build");
});
task("clean", (ctx) => {
rm("dist/");
});
task("build", async (ctx) => {
if (ctx.isProduction) {
console.log("Production build");
} else {
console.log("Development build");
}
});import { sparky } from "fuse-box";
class Context {}
const { task, src } = sparky(Context);
task("copy-assets", async () => {
await src("src/assets/**/*")
.dest("dist/assets/")
.exec();
});
task("process-templates", async () => {
await src("src/templates/*.html")
.file("*.html", (file) => {
file.read();
const content = file.contents.toString();
file.contents = Buffer.from(
content.replace(/{{VERSION}}/g, "1.0.0")
);
})
.dest("dist/templates/")
.exec();
});import { sparky } from "fuse-box";
class Context {}
const { task, src } = sparky(Context);
task("watch", async () => {
await src("src/**/*.ts")
.watch()
.dest("dist/")
.exec();
});
task("watch-assets", async () => {
await src("src/assets/**/*")
.watch("src/assets/**/*")
.clean("dist/assets/")
.dest("dist/assets/")
.exec();
});import { sparky } from "fuse-box";
import { fusebox, pluginCSS, pluginSass } from "fuse-box";
class BuildContext {
isProduction = false;
version = "1.0.0";
setVersion(v: string) {
this.version = v;
}
enableProduction() {
this.isProduction = true;
}
}
const { task, exec, src, rm } = sparky(BuildContext);
task("default", async (ctx) => {
await exec("clean");
await exec("copy-assets");
await exec("bundle");
});
task("production", async (ctx) => {
ctx.enableProduction();
await exec("clean");
await exec("copy-assets");
await exec("bundle");
await exec("optimize");
});
task("clean", () => {
rm("dist/");
});
task("copy-assets", async () => {
await src("src/assets/**/*")
.dest("dist/assets/")
.exec();
});
task("bundle", async (ctx) => {
const fuse = fusebox({
target: "browser",
entry: "src/index.ts",
plugins: [pluginCSS(), pluginSass()]
});
if (ctx.isProduction) {
await fuse.runProd({
uglify: true,
manifest: true
});
} else {
await fuse.runDev();
}
});
task("optimize", async () => {
// Additional optimization tasks
console.log("Running optimization tasks");
});import { sparky } from "fuse-box";
class Context {}
const { task } = sparky(Context);
// Match tasks starting with "test:"
task(/^test:/, async (ctx) => {
console.log("Running test task");
});
// Match specific patterns
task(/^build:(dev|prod)$/, async (ctx) => {
console.log("Running build task");
});import { sparky } from "fuse-box";
import { exec as execProcess } from "child_process";
import { promisify } from "util";
const execAsync = promisify(execProcess);
class Context {}
const { task, exec } = sparky(Context);
task("test", async () => {
await execAsync("npm test");
});
task("lint", async () => {
await execAsync("eslint src/**/*.ts");
});
task("typecheck", async () => {
await execAsync("tsc --noEmit");
});
task("ci", async () => {
await exec("lint");
await exec("typecheck");
await exec("test");
await exec("build");
});import { sparky } from "fuse-box";
class BuildContext {
private _startTime: number;
private _stats = {
filesProcessed: 0,
errors: 0
};
start() {
this._startTime = Date.now();
}
finish() {
const duration = Date.now() - this._startTime;
console.log(`Build completed in ${duration}ms`);
console.log(`Files processed: ${this._stats.filesProcessed}`);
console.log(`Errors: ${this._stats.errors}`);
}
incrementFiles() {
this._stats.filesProcessed++;
}
incrementErrors() {
this._stats.errors++;
}
get stats() {
return { ...this._stats };
}
}
const { task, exec, src } = sparky(BuildContext);
task("build-with-stats", async (ctx) => {
ctx.start();
try {
await src("src/**/*.ts")
.file("*.ts", (file) => {
try {
// Process file
ctx.incrementFiles();
} catch (error) {
ctx.incrementErrors();
console.error(`Error processing ${file.name}:`, error);
}
})
.dest("dist/")
.exec();
} finally {
ctx.finish();
}
});Sparky automatically executes tasks based on command-line arguments:
# Runs the "build" task
node sparky-script.js build
# Runs the "test:unit" task (if defined)
node sparky-script.js test:unit
# Runs the "default" task if no arguments provided
node sparky-script.jsSparky includes built-in logging and time measurement: