Uh oh!
There was an error while loading. Please reload this page.
fix(cli): scaffolded pnpm-workspace.yaml declares an explicit empty packages list - #11057
Conversation
…ackages list (#10933) Both scaffold paths now render `packages: []` — `renderPnpmWorkspaceYaml` in `objectstack init`, and the bundled `blank` template that `npx create-objectstack` copies. The file was deliberately keyless so it would act purely as a settings file for a single-package project; that intent is now written down instead of inferred from a missing key. Writing it down is what fixes a first-command failure. pnpm 9.x and 10.0–10.4 parse `pnpm-workspace.yaml` BEFORE they read `engines`, so they refused a brand-new project outright with "ERROR packages field missing or empty" — naming a file the user never wrote, with no hint that the cause is their pnpm version — and no `engines.pnpm` floor could ever reach them, because they never got as far as the engines check. Measured, one clean install per pnpm version, each with its own store: 9.15.9, 10.0.0, 10.4.0 raw workspace error BEFORE; the floor's own ERR_PNPM_UNSUPPORTED_ENGINE naming ">=10.15" AFTER 10.5.0–10.14.0 ERR_PNPM_UNSUPPORTED_ENGINE, unchanged 10.15.0, 10.34.5, 11.22.0 installs, byte-identical pnpm-lock.yaml The empty key was measured EQUIVALENT to omission on every supported pnpm (10.15.0, 10.34.5, 11.22.0): identical lockfile bytes, identical node_modules/.modules.yaml once the run-local prunedAt/storeDir fields are dropped, identical `pnpm ls -r --depth -1`, identical second-install "Already up to date". The triage fallback (drop to option C if it is not equivalent) is therefore not taken. It is an EMPTY list on purpose: `packages: ['.']` satisfies the same parsers but declares the project root a workspace MEMBER — a monorepo root — which a single-package scaffold is not, and which reads to the next author as licence to add member packages to an app. `engines.pnpm` is unchanged at `>=10.15`. It is, however, now what refuses 10.0–10.4 rather than the workspace file: with the floor lowered those versions install (exit 0, measured), but they read neither the build allowlist nor the peer rules out of `pnpm-workspace.yaml` ("The following dependencies have build scripts that were ignored: better-sqlite3, esbuild"), so admitting them is a support decision and not a side effect of this change. Both docblocks and both test suites record that measurement, and the decision is filed unassigned as #11048. Fixes#10933 Claude-Session: https://claude.ai/code/session_019bmVFqoQPq63zhKrxdYG1r Co-authored-by: Claude <noreply@anthropic.com>
📓 Docs Drift CheckThis PR changes 2 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 c36b505dd6050e477af8209719d8a86bbdd200bc && git checkout c36b505dd6050e477af8209719d8a86bbdd200bc
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin 072d072d2a74e358e923f6f6a76acbf7621305c3 b83ba6738ac3a1d72bb8d9dab7093fc86cdf8fa5 && git checkout -B drift-repro 072d072d2a74e358e923f6f6a76acbf7621305c3 && git merge --no-ff b83ba6738ac3a1d72bb8d9dab7093fc86cdf8fa5
node scripts/docs-audit/affected-docs.mjs --json 072d072d2a74e358e923f6f6a76acbf7621305c3
|
⛔ merge queue 构建失败 — 先分诊,再决定要不要重排队列构建 32580464495 红了。队列跑的是全量套件(PR 侧 CI 只跑 affected 子集), 失败的 job(日志抽取,best effort):
跨 PR 相同签名(24h,按失败测试文件聚合):
历史信号:
分诊清单:
Generated by Claude Code · merge-queue-triage workflow (#4859) |
Fixes#10933
Both scaffold paths now render an explicit empty
packages: []into the project'spnpm-workspace.yaml:renderPnpmWorkspaceYaml()inpackages/cli/src/commands/init.ts(objectstack init) and the bundledblanktemplate thatnpx create-objectstackcopies. Per the maintainer's ruling on #10933, quoted from the card:The fallback condition was measured, and it does not trigger
The ruling carried triage's fallback: if
packages: []is measured to be non-equivalent to omission on pnpm 10.15+, fall back to C with a documented minimum pnpm version. It is equivalent. One clean install per pnpm version, each with its own store, on the renderedappshape, keyless render vspackages: []render:pnpm-lock.yamlbytesnode_modules/.modules.yaml(run-localprunedAt/storeDirdropped, keys sorted)node_modulesentriespnpm ls -r --depth -1pnpm installAlready up to date, identical outputAlready up to date, identical outputSo option A ships and no minimum-pnpm-version documentation fallback is needed.
Per-version install results
Measured with the real rendered files (dumped from the two renderers themselves, not retyped), one clean install per pnpm version, each with its own store.
BEFOREis the keyless file onmain,AFTERis this branch.ERROR packages field missing or emptyERR_PNPM_UNSUPPORTED_ENGINEERROR packages field missing or emptyERR_PNPM_UNSUPPORTED_ENGINEERROR packages field missing or emptyERR_PNPM_UNSUPPORTED_ENGINEDone in 8s using pnpm v10.15.0Done in 7.1s using pnpm v11.22.0Verbatim, pnpm 10.0.0 on
main's keyless render:and on this branch's render:
The same before/after pair was measured on the
blanktemplate files directly (10.0.0 and 11.22.0), with the 11.22.0 lockfiles byte-identical across the two.The ruling expected 10.0–10.4 to "install a scaffolded project normally", on the premise that those versions "can never be reached by any floor". That premise held for the keyless file only. With the key present they now reach the
engines.pnpmcheck — and PR #10932's>=10.15floor refuses them, which is the AFTER column above. Lower the floor and they do install (measured, exit 0,Done in 12.3son 10.0.0) — but they read neither the build allowlist nor the peer rules out ofpnpm-workspace.yaml:so a scaffold installed there is quietly missing its native builds.
engines.pnpmis therefore untouched here, and the support question is filed unassigned with the full measurement as issue #11048. Not addressed in this PR, and neither is any of #10498 (theallowBuildsboundary in the comment), #10499 (two paths rendering one rule) or #10931 (the peer-warning skew) — all three land in the same file and stay open.What the tests pin
Cheap assertions were deliberately avoided: "the file contains
packages:" proves almost nothing. Each scaffold path now ratchets, over the file with comments stripped so the prose above the key cannot satisfy an assertion about the key:packages:(test/init.test.ts,src/template-consistency.test.ts);[].packages: ['.']would declare the project root a workspace member, i.e. a monorepo root, which reads to the next author as an invitation to add member packages to an app;- itemform are both refused;packages,onlyBuiltDependencies,allowBuilds,peerDependencyRules, in that order, so a re-added key that also drags a setting in fails.Reverse-verified, one leg per path, direction predicted first (red, on the new assertions only — both suites read source/files directly, so no build artifact is involved):
renderPnpmWorkspaceYaml()→Tests 3 failed | 46 passed (49)intest/init.test.ts, exactly the three new assertions; restored →49 passed;blanktemplate file →Tests 3 failed | 27 passed (30)insrc/template-consistency.test.ts; restored →30 passed.The floor assertions that outlived their premise were re-derived rather than left stale: their population grew (10.4.0 added) and their justification is now the measured "does not honour the workspace file's settings", not the retired "refuses to parse it".
Verification
Gate set derived with
node scripts/pm/dispatch-gates.mjs(no path arguments — it derives the change set from the merge base itself), and every family it named was run, plus the convention-triggered ones it printed for a diff that touches test files.All of the below ran at
b83ba6738a, the head of this branch, and every one is quoted from the verdict line the gate itself printed (exit codes captured before any pipe). Heavy runs went throughscripts/pm/os-verify-lock.sh, this container's shared verify lock.Package suites and types:
pnpm --filter @objectstack/cli --filter create-objectstack test—Test Files 13 passed (13) / Tests 140 passed (140)(create-objectstack) andTest Files 153 passed (153) / Tests 1696 passed (1696)(cli); lock verdictcommand-exit 0.pnpm --filter @objectstack/cli --filter create-objectstack typecheck—tsc --noEmitclean in both.pnpm lint— clean, no findings; lock verdictcommand-exit 0.Path-derived families (
lint.yml,ci.yml,cut-rc.yml,pr-automation.yml,docs-drift-check.yml):check:changeset-gate-self-tests— self-tests pass (212 + 116 assertions over real temp git repos).check:cross-package-test-inputs/scripts/check-cross-package-test-inputs.mjs—OK: 13 package(s) read outside themselves, all declared, and turbo.json hashes every declared glob.check:objectui-changeset—objectui-range --self-test: all checks passed.check:slot-lookup—slot-lookup ratchet holds: 107 unswept site(s) in 25 file(s), none new.check:template-version-sync—sync-template-versions --self-test: 40 assertions over temp fixtures, running the real CLI.check:test-source-alias—OK — 72 packages with tests scanned.check:type-check-coverage— self-test47 semantic case(s) + 59 observation case(s) + 29 re-measure case(s) + 28 built-closure case(s) + 19 auto-lowering case(s) hold.check:type-check-debt— run on the BUILT workspace closure (turbo run build --filter=./packages/* --filter=./packages/*/*first, exactly aslint.ymldoes, so the result is a measurement and not a refusal):check-type-check-coverage --re-measure: OK — 33 ledger entr(ies) re-measured in 282.6s, 1908 raw tsc error(s) total, none above its recorded number.The pre-existing@objectstack/plugin-authsurplus it reports is untouched by this diff and not lowered here.check:type-source-resolution—OK — 77 packages with a tsconfig.json scanned.scripts/check-adr-0087-registration.mjs—this PR adds no declared-breaking changeset (1 non-breaking changeset(s) seen).scripts/check-changeset-no-major.mjs—This diff introduces no major bump.scripts/check-ci-filter-parity.mjs—OK: all 83 declared cross-package glob(s) (72 unique) are covered.scripts/check-empty-changeset.mjs—No empty-frontmatter changeset introduced by this diff (1 declaring changeset(s) added).scripts/check-plugin-teardown-shape.mjs—61 Plugin implementation(s) across 4443 source(s), baseline fully burned down.scripts/docs-audit/check-affected-docs.mjs—affected-docs self-test: 339 cases pass.Convention-triggered families (this diff adds and edits test files):
check:query-options-erasure—ratchet holds: 67 unswept non-test site(s) in 17 file(s), none new.check:engine-double-contract— clean; this diff adds no engine double.check:where-matcher—conformance holds: 276 matcher(s) discovered, 276 answer the combinator battery correctly or refuse it loudly.check:type-check-coverageandcheck:type-check-debt— above.Also run, since this diff edits files:
check:nul-bytes(self-test 75 assertions, clean).Generated by Claude Code