Skip to content

antfu-collective/tsdown-stale-guard

tsdown-stale-guard

npm version npm downloads bundle JSDocs License

Build freshness validation for tsdown. Records hashes of all files involved in a build, enabling fast up-to-date checks without re-building.

This plugin requires tsdown@0.21.9 or later.

Features

  • tsdown/Rolldown plugin — automatically tracks source files, output files, config, and package lock file
  • Composite hash — single hash for quick freshness checks
  • Find-up search — detects lock files and configs in monorepo setups
  • CLItsdown-stale-guard for CI pipelines
  • Programmatic APIcheckBuildState() for tool integrations
  • Build guardguardStaleBuild() throws on stale builds, great for test setups
  • Structured diagnostics — errors use logs-sdk with stable codes and actionable fixes

Install

npm i tsdown-stale-guard

Usage

As a tsdown Plugin

// tsdown.config.ts
import { defineConfig } from 'tsdown'
import { StaleGuardRecorder } from 'tsdown-stale-guard'

export default defineConfig({
  entry: ['src/index.ts'],
  plugins: [
    StaleGuardRecorder(),
  ],
})

After building, a hash file will be generated at node_modules/.cache/tsdown-stale-guard/hash.yaml.

Example of the generated hash file:

version: 1
hash: sha256:abc123...

config:
  tsdown.config.ts: sha256:def456...

lockfile:
  ../../pnpm-lock.yaml: sha256:789abc...

sources:
  src/index.ts: sha256:111111...
  src/utils.ts: sha256:222222...

outputs:
  dist/index.mjs: sha256:aaaaaa...
  dist/index.d.mts: sha256:bbbbbb...

Plugin Options

StaleGuardRecorder({
  hashFile: 'node_modules/.cache/tsdown-stale-guard/hash.yaml', // hash file path (default)
  root: process.cwd(), // root directory (default)
  hashOutputs: true, // hash output files (default)
})

CLI

# Check if the build is up to date
tsdown-stale-guard

# Use a custom hash file path
tsdown-stale-guard --hash-file custom.hash.yaml

Exit code 0 if fresh, 1 if stale.

Programmatic API

import { checkBuildState } from 'tsdown-stale-guard'

const result = await checkBuildState()

if (result.fresh) {
  console.log('Build is up to date')
}
else {
  for (const change of result.changes) {
    console.log(`${change.type}: [${change.category}] ${change.file}`)
  }
}

Guard Stale Build

guardStaleBuild() checks the build state and throws a structured TSDSG0002 error if the build is stale. This is useful for CI pipelines or test setups where you want to fail early when the build output is outdated.

import { guardStaleBuild } from 'tsdown-stale-guard'

// Throws if the build is stale
await guardStaleBuild()

With Vitest

When writing tests against the built output (dist/), you can use guardStaleBuild() to ensure the build is fresh before tests run. Use beforeAll for a per-test-file check:

import { guardStaleBuild } from 'tsdown-stale-guard'
import { beforeAll, describe, it } from 'vitest'

beforeAll(async () => {
  await guardStaleBuild()
})

it('should work', async () => {
  const { myFunction } = await import('../dist/index.mjs')
  // test against the built output
})

Or use globalSetup for a one-time global check:

// test/setup.ts
import { guardStaleBuild } from 'tsdown-stale-guard'

await guardStaleBuild()
// vitest.config.ts
import { defineConfig } from 'vitest/config'

export default defineConfig({
  test: {
    globalSetup: ['test/setup.ts'],
  },
})

Either way, tests fail immediately with a clear error message if the build is stale, instead of producing confusing failures from outdated output.

Diagnostic Codes

All errors thrown by tsdown-stale-guard are structured CodedError objects with stable codes, actionable fix messages, and documentation links.

Code Level Description
TSDSG0001 error tsdownConfigResolved hook was not called (tsdown version too old)
TSDSG0002 error Build is stale — source files, config, or dependencies changed

How It Works

The plugin hooks into Rolldown's build pipeline:

  1. transform — collects all source file paths during bundling
  2. generateBundle — collects output file names
  3. writeBundle — hashes all collected files plus the detected tsdown config and package lock file, then writes the hash file

The hash file includes a composite hash computed from all individual file hashes, enabling a single-comparison freshness check.

Package lock files (pnpm-lock.yaml, yarn.lock, package-lock.json, bun.lockb, bun.lock) and tsdown config files are found via find-up search, supporting monorepo setups where they may live in a parent directory.

Sponsors

Sponsors

License

MIT License © Anthony Fu

About

Build freshness validation for tsdown.

Resources

License

Code of conduct

Contributing

Stars

Watchers

Forks

Contributors