Uh oh!
There was an error while loading. Please reload this page.
fix(cli): scaffold both pnpm build-approval keys so a new project installs on pnpm 11 - #10522
Conversation
…talls on pnpm 11 (#10405) `renderPnpmWorkspaceYaml()` wrote only `onlyBuiltDependencies`. pnpm 11 does not read that key, and it turned an unapproved dependency build script from a warning into a hard error — so `objectstack init my-app && cd my-app && pnpm install` exited 1 with ERR_PNPM_IGNORED_BUILDS, on the first command a newcomer runs after scaffolding. The rendered file now also carries `allowBuilds`, built from the same `SCAFFOLD_BUILT_DEPENDENCIES` list, which is the only key pnpm 11 reads. This is the shape the blank template already ships for the other scaffold path. Measured, one clean install per pnpm version, each with its own store, on a project scaffolded by `objectstack init -t app`: 10.0.0 – 10.25.0 read onlyBuiltDependencies only 10.26.0 – 10.34.x read either key 11.x read allowBuilds only so both keys are load-bearing and neither is redundant. Build permission is still granted to exactly two named packages, no wildcard: esbuild (postinstall installs its platform binary, used to compile objectstack.config.ts) and better-sqlite3 (ships a binding.gyp, which pnpm treats as a native build). Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019bmVFqoQPq63zhKrxdYG1r
📓 Docs Drift CheckThis PR changes 1 package(s): 3 hand-written doc(s) NAME something this change touched and may need an implementation-accuracy re-verification:
⛔ 1 release-owned page(s) also name something this change touched. These are read-only:
What this run could not see
Coarse fallback — 23 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): Which tree this was computed onThis run read A worktree cut from an older # while this PR is open — GitHub drops the merge commit once it closes
git fetch origin cbba7e30d265cc8a41253c17c6898608962b9f11 && git checkout cbba7e30d265cc8a41253c17c6898608962b9f11
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin 2e3cf955405e3e5e0c95fe50a56784f167bea727 2ee9cfb6f2d3c7f467fdc2ab3dac8245ffcef2b8 && git checkout -B drift-repro 2e3cf955405e3e5e0c95fe50a56784f167bea727 && git merge --no-ff 2ee9cfb6f2d3c7f467fdc2ab3dac8245ffcef2b8
node scripts/docs-audit/affected-docs.mjs --json 2e3cf955405e3e5e0c95fe50a56784f167bea727
|
Uh oh!
There was an error while loading. Please reload this page.
Fixes#10405
objectstack initscaffolded a project whose very firstpnpm installexited 1 onpnpm 11.
renderPnpmWorkspaceYaml()inpackages/cli/src/commands/init.tswrote onlyonlyBuiltDependencies; pnpm 11 does not read that key at all, and it turned anunapproved dependency build script from a warning into a hard error. The rendered file
now also carries
allowBuilds, built from the sameSCAFFOLD_BUILT_DEPENDENCIESlist —the shape
packages/create-objectstack/src/templates/blank/pnpm-workspace.yamlalreadyships for the other scaffold path.
Measured before patching
Scaffolded with the published CLI, exactly as a newcomer would:
Then confirmed the rendered file is the cause rather than something adjacent, by
substituting the two renderings of that one file into the same scaffold and changing
nothing else. Every run below is a clean install into a fresh directory with its own
--store-dir— an isolated store matters, because pnpm's side-effects cache willotherwise hand a later run a build an earlier run performed, and the run then reads as
"the key worked" when the key was never read (this confounded the first pass here):
pnpm-workspace.yamlignoredBuildsbetter-sqlite3@13.0.3, esbuild@0.28.2[][][]"builds ran" is checked on the artifacts, not on the exit code:
esbuild/bin/esbuildisan ELF binary rather than the JS shim, and
better-sqlite3/build/Release/exists.Which dependencies need a build, and why — exactly two, no wildcard
The scaffolded tree has 397 packages under
node_modules/.pnpm. Two of them are buildcandidates, and they are the two pnpm names in the error:
esbuild@0.28.2— the only package in the whole tree that declares a lifecyclescript:
postinstall: node install.js, which installs/validates its platform binary.Without it,
bin/esbuildstays a JS shim. The CLI uses esbuild to compileobjectstack.config.ts, so this is on the path ofdev,compileandvalidate.better-sqlite3@13.0.3— declares no install script and setsgypfile: false,but ships a
binding.gyp, which pnpm treats as an implicit native build; that is whyit appears in the ignored-builds list at all. It also ships prebuilds for eight
platform triples, so on a platform with a matching prebuild it loads without
compiling — but on any platform or ABI without one it must compile, or
objectstack servefails with "Could not locate the bindings file". It is the native driver behindthe default standalone SQLite store.
Every other package with a
scriptsentry in that tree declares onlyprepare, whichpnpm does not run for registry dependencies. So the approved set stays the two packages
that were already approved — this PR grants no new build permission to anything, it only
states the existing grant in the second key. A test asserts the set is named packages
rather than a glob, and that both keys grant the same set.
Both keys are load-bearing — measured, not assumed
One clean install per pnpm version, isolated store,
allowBuildsalone:allowBuildsaloneand
onlyBuiltDependenciesalone on pnpm 11.22.0 exits 1 with exactly the ignored set afile approving nothing at all produces. So the bands are
10.0–10.25(needsonlyBuiltDependencies),10.26–10.34.x(either),11.x(needsallowBuilds), andneither key is redundant. The comment the renderer emits into the user's project states
those measured boundaries.
Note this differs from the boundary the blank template's comment states today
(
>= 10.31/10.0–10.30). The template's behaviour is right and its two load-bearingclaims are right; only the version numbers in its prose are off. Filed as #10498 rather
than corrected here, to keep this diff inside
packages/cli— #10317 and #10323 are inflight in
packages/create-objectstack.Reverse verification of the test
The new ratchets fail on the pre-fix renderer. With the fix committed and
packages/cli/src/commands/init.tsalone checked out fromorigin/main(
pnpm --filter @objectstack/cli exec vitest run test/init.test.ts):(The real test name spells that placeholder with angle brackets. It is written with
square brackets here because GitHub's body sanitizer deletes short angle-bracket
fragments — it silently ate the first version of this line, inside the code fence,
leaving
sets allowBuilds. = true.)Restoring the file returns
Test Files 1 passed (1)/Tests 41 passed (41), andgit statusis clean, so the restored tree is byte-identical to the commit. The othertwo new tests (
keeps listing … onlyBuiltDependencies,approves named packages only)stay green in both legs by design — they hold on the old shape too, and exist to stop the
old key being dropped and a wildcard being pasted in later. No
dist/is involved ineither leg: the test imports
../src/commands/init, so nothing here depends on arebuild.
Scope
packages/cli/src/commands/init.ts(the renderer),packages/cli/test/init.test.ts(four ratchets), one changeset. Nothing under
packages/create-objectstack, so no overlapwith #10317 or #10323 — those two are not addressed here. Nothing under
content/docs/releases/**.Existing scaffolds are unaffected:
initwrites this file withflag: 'wx'and neveroverwrites one that already exists. The changeset carries the two lines an already
scaffolded project needs.
Out-of-scope findings filed
packages:key, so pnpm 10.0.0 refuses the install outright — "ERROR packages field missing or empty" #10497 — the rendered file omitspackages:, and pnpm 10.0.0 refuses the installoutright with
ERROR packages field missing or empty, independent of the build keys.Affects both scaffold paths; the band closes by 10.15.0.
allowBuilds— measured floor is 10.26.0, not 10.31 #10498 — the blank template's comment states the wrong pnpm boundary forallowBuilds.them kept shipping the pre-fix shape unnoticed; carries forward the question
objectstack initscaffolds a project whosepnpm installexits 1 on pnpm 11 — its rendered pnpm-workspace.yaml omitsallowBuilds#10405'sbody asks.
Gates run locally, at
2ee9cfb6fSet re-derived after the final commit with
node scripts/pm/dispatch-gates.mjs(no patharguments — it takes the change set from the merge base itself), which named 9
path-derived families plus 5 convention-triggered ones for "adds or edits a test file".
Each line below is the gate's own verdict, not a shell exit code:
check:changeset-gate-self-tests✓ check-changeset-no-major --self-test: 116 assertions …check:cross-package-test-inputsOK: 12 package(s) read outside themselves, all declared, and turbo.json hashes every declared glob.check:objectui-changeset✓ objectui-range --self-test: all checks passedcheck:slot-lookup✓ slot-lookup ratchet holds: 107 unswept site(s) in 25 file(s), none new …check-adr-0087-registration.mjs✓ check-adr-0087-registration: this PR adds no declared-breaking changeset (1 non-breaking changeset(s) seen).check-changeset-no-major.mjs✓ This diff introduces no major bump.check-empty-changeset.mjs✓ No empty-frontmatter changeset introduced by this diff (1 declaring changeset(s) added).docs-audit/check-affected-docs.mjscheck:query-options-erasure✓ query-options-erasure ratchet holds: 67 unswept non-test site(s) in 17 file(s), none new …check:type-check-coveragecheck-type-check-coverage: OK — 64/77 workspace packages type-checked …check:type-check-debtcheck-type-check-coverage --re-measure: OK — 33 ledger entr(ies) re-measured in 257.1s, 1924 raw tsc error(s) total, none above its recorded number.check:engine-double-contractcheck-engine-double-contract: OK — 340 pinned, 133 in the DEBT ledger, 2 exempt.check:where-matcher✓ where-matcher conformance holds: 266 matcher(s) discovered, 266 answer the combinator battery correctly or refuse it loudly …check:nul-bytescheck-nul-bytes: OK (scanned 6144 text file(s) … no raw ASCII control bytes).check:type-check-debtis the one worth calling out:packages/cli/tsconfig.jsonhasinclude: ["src"], so the package's tests sit outside its owntypecheckscript — a newtest file here lands in the TEST_DEBT layer, not in the clean
tsc --noEmitrun. It wasrun the way CI runs it, on the built workspace closure
(
turbo run build --filter=./packages/* --filter=./packages/*/*first).Package suites:
pnpm --filter @objectstack/cli test→Test Files 138 passed (138)/Tests 1534 passed (1534);pnpm --filter @objectstack/cli typecheck→tsc --noEmit,exit 0.
Generated by Claude Code