Skip to content

fix(scripts): make the setup:project strip's output actually build - #368

Merged
arzafran merged 1 commit into
mainfrom
fix/setup-output-builds
Aug 4, 2026
Merged

arzafran merged 1 commit into
mainfrom
fix/setup-output-builds

Conversation

@arzafran

@arzafran arzafran commented Aug 4, 2026

Copy link
Copy Markdown
Member

What this does

Two Highs from the process audit plus a High from cross-model review, all reproduced:

  • P-C3: `selfPrune` wrote a stale early-read package.json back over `addDependencies`' pins — `--keep sanity` claimed "6 dependencies pinned" while the disk had none, then failed with 24 module-not-found errors. Now re-reads before writing.
  • P-B7: the blank strip deleted sanity modules that `lib/seo/routes.ts`, the llms route, the revalidate route, and `lib/dev/stats` still import (the old config crash aborted builds before webpack ever surfaced this). New AST transform ops strip those consumers per-bundle, with the revalidate route split into per-owner transforms to avoid cross-contamination.
  • Review High: the op runner silently no-ops on drifted source — the new load-bearing ops now carry `required: true` and a zero-match run FAILS loudly before self-prune (recoverable by design), with drifted-fixture tests proving it. Legit re-application on already-lean files (boutique preset) downgrades required — found and covered.

Test Plan

  • Blank preset: setup → install → build exit 0
  • `--keep sanity`: build exit 0, deps present on disk
  • Boutique preset (double-touch scenario): build exit 0
  • Drift fixtures: required ops throw pre-self-prune

P-C3: selfPrune wrote back the stale pre-strip package.json object,
silently reverting the dependency pins setupAddIntegrations had just
written to disk for the kept bundle. --keep sanity reported "N
dependencies pinned" while package.json on disk had none of them, and
bun install/bun run build failed on module-not-found. Fix: re-read
package.json from disk immediately before selfPrune instead of reusing
the step-5 in-memory object.

P-B7: a blank/no-sanity strip still didn't build - several files
outside lib/integrations statically depend on sanity even after its
folders/deps are removed:
  - lib/seo/routes.ts (getCmsRoutes) always imported sanityFetch /
    next-sanity / urlForReference; stripped to a lean stub that
    returns [] when sanity isn't kept, restored wholesale via
    overwriteFiles when it is (single-owner file).
  - app/api/revalidate/route.ts is a two-owner file (sanity + shopify);
    added sanity's own codeTransforms/addTransforms for its half
    (parseBody/NextResponse/revalidateTag + the try/catch body) and
    converted shopify's existing overwriteFiles entry on the same file
    to addTransforms too, so keeping either integration without the
    other never reintroduces the other's now-orphaned import.
  - lib/dev/stats renders via stats-gl, a transitive (not direct)
    dependency of webgl's @react-three/drei; folded lib/dev/stats into
    the webgl bundle and added matching codeTransforms/addTransforms
    for its lib/dev/index.tsx and lib/dev/cmdo.tsx wiring.
  - app/llms.txt/route.ts's buildBody() has its own unconditional 'use
    cache' directive, which becomes a hard Next.js compile error once
    setupCacheComponentsOptOut disables Cache Components (no CMS/
    storefront kept); strip it in the same pass as the next.config.ts
    flip.

Extended the AST-transform engine with 7 new op kinds needed for the
above (removeNamedImport, removeTryStatement, replaceFunctionBody,
removeUseCacheDirective, addDestructuredBinding,
addFunctionBodyStatement) - all mirroring existing op patterns.

Required-match contract (cross-model review HIGH finding): the AST-op
runner previously no-opped silently when an op's target didn't match -
if a future rename moves getCmsRoutes, or the revalidate try-block
shape drifts, the strip would remove an import while leaving the code
that used it, producing a broken tree, and self-prune the machinery
that could have caught it. Every op now carries an optional
`required?: boolean` (RequiredMatchOp, extended by all 25 op
interfaces); when true and the op matches nothing, applyOpsToText
throws RequiredOpMatchError, which applyCodeTransforms re-throws
immediately (aborting the whole batch, unlike a regular per-file
failure) - so setup() fails loudly before it ever reaches self-prune,
not after. Marked required: true on every op in the new
seo/revalidate/llms/stats transforms above; left every pre-existing
(legacy) op unmarked, preserving its exact prior no-op-on-miss
behavior. stripAbsentIntegrationWiring downgrades `required` to false
before reapplying a bundle's codeTransforms to an already-lean file (a
genuinely separate, later pass where zero-match is the expected,
idempotent outcome, not drift) - verified empirically via the
boutique preset (shopify kept, sanity absent, which reapplies
sanity's revalidate-route ops a second time) building clean.

Added a drifted-fixture regression suite (RequiredOpMatchError describe
block in setup-project.test.ts): renaming getCmsRoutes or restructuring
the revalidate try-block in a mutated copy of the real source now fails
the strip loudly instead of silently passing; a downgraded-required
reapplication against an already-lean file still doesn't throw.

Added an end-to-end regression test (spawns the real script against a
throwaway rsync copy, --skip-install) that fails without the P-C3 fix
and passes with it.

Verified empirically: rsync copy + blank preset + bun install + bun
run build (exit 0); second rsync copy + --keep sanity + bun install +
bun run build (exit 0, package.json has the sanity deps on disk); third
rsync copy + boutique preset (shopify without sanity, exercises the
required-match downgrade) + bun install + bun run build (exit 0).
Copilot AI review requested due to automatic review settings August 4, 2026 22:49
@vercel

vercel Bot commented Aug 4, 2026 •

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
satus Ready Ready Preview Aug 4, 2026 10:49pm

@arzafran
arzafran merged commit 636a314 into main Aug 4, 2026
13 checks passed

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR hardens setup:project so stripped presets produce a tree that still builds, by (1) preventing stale package.json writes from undoing dependency pins and (2) making strip/add AST transforms safer and more explicit (including “required match” enforcement to catch drift early).

Changes:

  • Re-read package.json from disk before self-pruning so dependency pins written during integration re-add aren’t overwritten by a stale in-memory object.
  • Add “required op match” semantics to AST transforms (and tests) so load-bearing transforms fail loudly if the expected source shape drifts.
  • Expand integration bundle transforms to strip integration-owned wiring from shared surfaces (SEO routes, /llms.txt, revalidate route, dev stats) and add new AST op kinds to support those edits.

Reviewed changes

Copilot reviewed 9 out of 9 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
lib/scripts/setup-project.ts Adds a Cache Components /llms.txt transform and fixes package.json persistence by re-reading before self-prune write.
lib/scripts/setup-project.test.ts Adds required-op contract tests and an end-to-end regression to ensure pinned deps survive self-prune.
lib/scripts/integration-bundles.ts Introduces new strip/add transforms for shared files (SEO routes, revalidate route) and tightens bundle ownership (e.g. dev stats under webgl).
lib/scripts/bundle-installer.ts Downgrades required to false when stripping absent wiring post-union-pass to preserve idempotent behavior.
lib/scripts/ast-transforms/remove-ops.ts Adds new remove/replace operations (named import removal, try removal, use-cache directive removal, function body replacement).
lib/scripts/ast-transforms/index.ts Enforces the required-match contract and rethrows required-match failures as hard errors.
lib/scripts/ast-transforms/add-ops.ts Adds ops to re-add destructured bindings and insert statements into function bodies.
lib/scripts/ast-transforms.ts Re-exports RequiredOpMatchError for tests/callers.
lib/scripts/ast-operation-types.ts Adds the required contract and types for the new AST operation kinds.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +286 to +290
{
kind: 'removeTryStatement',
blockContains: 'SANITY_REVALIDATE_SECRET',
required: true,
},
Comment on lines +709 to +713
{
kind: 'removeUseCacheDirective',
functionName: 'buildBody',
required: true,
},
@arzafran
arzafran deleted the fix/setup-output-builds branch August 26, 2026 17:08

This branch was successfully deployed

1 active deployment
Preview — f409601a Deployed Aug 4, 2026 by vercel[bot]
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