or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

docs

build-automation.mdcore-bundling.mdindex.mdplugins.mdtransformation.md
tile.json

build-automation.mddocs/

Build Automation (Sparky)

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.

Main Function

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 Management

Task Definition

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.

Task Execution

exec(name: string): Promise<void>;

Execute a specific task by name. Tasks are automatically timed and logged during execution.

File Operations

Source Selection

src(glob: string): SparkyChain;

Select files using glob patterns for processing. Returns a chainable interface for file operations.

Directory Removal

rm(folder: string): void;

Remove directories and their contents. Paths are resolved relative to the script location.

Sparky Chain Interface

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;
}

Usage Examples

Basic Task Setup

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");
  }
});

File Processing Tasks

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();
});

Watch Mode Tasks

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();
});

Complex Build Workflow

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");
});

Regex-Based Task Matching

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");
});

Integration with External Tools

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");
});

Custom Context with State Management

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();
  }
});

Automatic Task Execution

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.js

Logging and Time Measurement

Sparky includes built-in logging and time measurement:

  • Task execution is automatically timed
  • Start and completion messages are logged
  • Failed tasks display error information
  • Support for colored output and progress indicators