builderman

A dependency-aware task runner for building, developing, and orchestrating complex workflows

Caching

builderman supports task-level caching to skip expensive work when inputs and outputs haven't changed. This is useful for build-style tasks where you want to avoid re-running work when nothing has changed.

Basic Usage

Enable caching by providing cache configuration in your command:

const buildTask = task({
  name: "build",
  commands: {
    build: {
      run: "tsc",
      cache: {
        inputs: ["src"],
        // outputs is optional; if omitted, only inputs are tracked
        outputs: ["dist"],
      },
    },
  },
})

How It Works

When caching is enabled:

  1. First run: The task executes normally and creates a snapshot of the input and output files
  2. Subsequent runs: The task compares the current state with the cached snapshot
  3. Cache hit: If inputs and outputs are unchanged, the task is skipped (no command execution)
  4. Cache miss: If anything changed, the task runs and updates the cache

Cache Inputs

Cache inputs can include:

File Paths

const buildTask = task({
  name: "build",
  commands: {
    build: {
      run: "tsc",
      cache: {
        inputs: ["src", "package.json"],
        outputs: ["dist"],
      },
    },
  },
})

Artifacts

You can reference outputs from other tasks as cache inputs using task.artifact("command"). This creates an artifact dependency that tracks changes to the producing task's outputs.

const shared = task({
  name: "shared",
  cwd: "packages/shared",
  commands: {
    build: {
      run: "npm run build",
      cache: {
        inputs: ["src"],
        outputs: ["dist"],
      },
    },
  },
})

const backend = task({
  name: "backend",
  cwd: "packages/backend",
  commands: {
    build: {
      run: "npm run build",
      cache: {
        inputs: [
          "src",
          shared.artifact("build"), // Track changes to shared's build outputs
        ],
        outputs: ["dist"],
      },
    },
  },
})

When using artifacts:

Input Resolvers

Input resolvers are functions that resolve to cacheable inputs. They're useful for tracking package dependencies and other dynamic inputs.

For example, the @builderman/resolvers-pnpm package provides a resolver for pnpm package dependencies:

import { task } from "builderman"
import { pnpm } from "@builderman/resolvers-pnpm"

const server = task({
  name: "server",
  cwd: "packages/server",
  commands: {
    build: {
      run: "pnpm build",
      cache: {
        inputs: [
          "src",
          pnpm.package(), // Automatically tracks pnpm dependencies
        ],
        outputs: ["dist"],
      },
    },
  },
})

The resolver automatically detects whether you're in a workspace or local package and tracks the appropriate pnpm-lock.yaml and package dependencies.

How Caching Works Internally

The cache system:

Path Resolution

Cache Information in Statistics

When a task has cache configuration, its statistics include cache information:

const result = await pipeline([buildTask]).run()

const taskStats = result.stats.tasks[0]

if (taskStats.cache) {
  console.log("Cache checked:", taskStats.cache.checked)
  console.log("Cache hit:", taskStats.cache.hit)
  console.log("Cache file:", taskStats.cache.cacheFile)
  console.log("Inputs:", taskStats.cache.inputs)
  console.log("Outputs:", taskStats.cache.outputs)
}

Cache Behavior

When to Use Caching

Caching is ideal for:

Caching is not suitable for:

Related: Learn about Skipping Tasks and Execution Statistics.