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.9or later.
- 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
- CLI —
tsdown-stale-guardfor CI pipelines - Programmatic API —
checkBuildState()for tool integrations - Build guard —
guardStaleBuild()throws on stale builds, great for test setups - Structured diagnostics — errors use logs-sdk with stable codes and actionable fixes
npm i tsdown-stale-guard// 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...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)
})# Check if the build is up to date
tsdown-stale-guard
# Use a custom hash file path
tsdown-stale-guard --hash-file custom.hash.yamlExit code 0 if fresh, 1 if stale.
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}`)
}
}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()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.
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 |
The plugin hooks into Rolldown's build pipeline:
transform— collects all source file paths during bundlinggenerateBundle— collects output file nameswriteBundle— 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.
MIT License © Anthony Fu