Skip to content

chore(build): hash inputs in smart-build cache check#1219

Merged
John-David Dalton (jdalton) merged 1 commit intomainfrom
chore/smart-build-input-hashing
Apr 17, 2026
Merged

chore(build): hash inputs in smart-build cache check#1219
John-David Dalton (jdalton) merged 1 commit intomainfrom
chore/smart-build-input-hashing

Conversation

@jdalton
Copy link
Copy Markdown
Contributor

@jdalton John-David Dalton (jdalton) commented Apr 17, 2026

Why this exists

Today's smart-build cache check is output-only: if packages/cli/dist/index.js exists, it skips. That's fine for "the dist is there" but it misses every other reason a rebuild is needed.

The pain point I hit: bumping target: 'node18''node25' in packages/cli/.config/esbuild.cli.mts, running pnpm build, seeing "CLI Package: skipped (up to date)", getting confused, --force-ing, and realizing the smart build had silently served stale output for a config change. That's a junior-dev-hostile trap — you change the bundler's behavior, the tool says "up to date", and you have to know to distrust it.

Other things that fall into the same bucket:

  • pnpm-lock.yaml changed (dependency bump) → old bundle, stale deps.
  • packages/build-infra/lib/** changed (shared build helpers) → stale bundle.
  • .node-version bumped → stale bundle targeting the old runtime floor.

What this changes

needsBuild() now also rebuilds when the inputs change, not just when the output is missing.

How it works:

  1. Each package entry declares a list of input globs (config, scripts, source, lockfile, .node-version).
  2. On every build attempt, we SHA-256 the (sorted) file paths + contents into a single "build signature".
  3. After a successful build, we write that signature next to the output as dist/index.js.build-signature (gitignored).
  4. Next smart build re-hashes inputs; if the hash differs from the stored one, we rebuild.

Why content hashing instead of mtime:

  • git checkout, rebase, branch switching all rewrite mtimes even when content is identical. mtime-based checks thrash on every branch switch.
  • Content hash gives us "the inputs are semantically the same" → maximum cache hits, zero stale hits.

--force still bypasses everything.

Inputs currently tracked for the CLI:

  • packages/cli/.config/**/*.{mts,ts,json}
  • packages/cli/scripts/**/*.{mts,ts}
  • packages/cli/src/**/*.{mts,ts,cts,json}
  • packages/cli/package.json, tsconfig.json
  • packages/build-infra/lib/**/*.{mts,ts}, package.json
  • pnpm-lock.yaml
  • .node-version

Test plan

  • Fresh `pnpm run build` builds and writes `*.build-signature`
  • Second `pnpm run build` with no changes → skipped
  • Adding a comment to `.config/esbuild.cli.mts` → rebuilds
  • Reverting that change → rebuilds back (signature changed)
  • `--force` still always builds
  • Signature sidecar gitignored
  • CI green

Extends `needsBuild()` in the smart-build script so it rebuilds when the
bundler configs, build scripts, source, lockfile, or Node version change
— not only when the dist output is missing.

How it works:
  - Each package entry declares a list of input globs.
  - We SHA-256 the file paths + contents into a "build signature."
  - The signature is written alongside the output (e.g.
    `packages/cli/dist/index.js.build-signature`) after a successful build.
  - The next smart build re-hashes inputs; if the hash differs, we rebuild.

Force (`--force`) still bypasses everything.

Signature sidecars are gitignored.
@jdalton John-David Dalton (jdalton) merged commit 2f052bf into main Apr 17, 2026
6 checks passed
@jdalton John-David Dalton (jdalton) deleted the chore/smart-build-input-hashing branch April 17, 2026 21:51
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants