Skip to content

fix(cli): scaffolded pnpm-workspace.yaml declares an explicit empty packages list - #11057

Merged
os-elon merged 1 commit into
mainfrom
claude/issue-10933-scaffold-workspace-packages-key
Aug 22, 2026
Merged

fix(cli): scaffolded pnpm-workspace.yaml declares an explicit empty packages list#11057
os-elon merged 1 commit into
mainfrom
claude/issue-10933-scaffold-workspace-packages-key

Conversation

@os-elon

Copy link
Copy Markdown
Collaborator

Fixes#10933

Both scaffold paths now render an explicit empty packages: [] into the project's pnpm-workspace.yaml: renderPnpmWorkspaceYaml() in packages/cli/src/commands/init.ts (objectstack init) and the bundled blank template that npx create-objectstack copies. Per the maintainer's ruling on #10933, quoted from the card:

Option A — the scaffolded pnpm-workspace.yaml declares the explicit empty packages: []. This makes the file's existing intent ("acts purely as a settings file", a workspace root with no member packages) explicit […] Execution is serial with #10931/#10498/#10499, which land in the same rendered file; until #10499 closes, the change must be applied to both scaffold paths.

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 rendered app shape, keyless render vs packages: [] render:

probe10.15.010.34.511.22.0
pnpm-lock.yaml bytesidenticalidenticalidentical
node_modules/.modules.yaml (run-local prunedAt/storeDir dropped, keys sorted)identicalidenticalidentical
top-level node_modules entriesidenticalidenticalidentical
pnpm ls -r --depth -1identical but for the pathidentical but for the path
second pnpm installAlready up to date, identical outputAlready up to date, identical output

So 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. BEFORE is the keyless file on main, AFTER is this branch.

pnpmBEFOREAFTER
9.15.9exit 1 · ERROR packages field missing or emptyexit 1 · ERR_PNPM_UNSUPPORTED_ENGINE
10.0.0exit 1 · ERROR packages field missing or emptyexit 1 · ERR_PNPM_UNSUPPORTED_ENGINE
10.4.0exit 1 · ERROR packages field missing or emptyexit 1 · ERR_PNPM_UNSUPPORTED_ENGINE
10.15.0exit 0exit 0 · Done in 8s using pnpm v10.15.0
10.34.5exit 0exit 0
11.22.0exit 0exit 0 · Done in 7.1s using pnpm v11.22.0

Verbatim, pnpm 10.0.0 on main's keyless render:

 ERROR packages field missing or empty
For help, run: pnpm help install

and on this branch's render:

 ERR_PNPM_UNSUPPORTED_ENGINE Unsupported environment (bad pnpm and/or Node.js version)
Your pnpm version is incompatible with "PROJECT-PATH".
Expected version: >=10.15
Got: 10.0.0
This is happening because the package's manifest has an engines.pnpm field specified.

The same before/after pair was measured on the blank template files directly (10.0.0 and 11.22.0), with the 11.22.0 lockfiles byte-identical across the two.

⚠️ The ruled outcome has a second half, and it is NOT in this PR

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.pnpm check — and PR #10932's >=10.15 floor refuses them, which is the AFTER column above. Lower the floor and they do install (measured, exit 0, Done in 12.3s on 10.0.0) — 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 a scaffold installed there is quietly missing its native builds. engines.pnpm is 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 (the allowBuilds boundary 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:

  • the key is present — packages: (test/init.test.ts, src/template-consistency.test.ts);
  • it is an empty list — the inline value is exactly []. 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;
  • no member arrives in any spelling — the inline non-empty form and the block - item form are both refused;
  • the rest of the file is unchanged — the top-level keys are exactly 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):

  • removing the emitted block from renderPnpmWorkspaceYaml()Tests 3 failed | 46 passed (49) in test/init.test.ts, exactly the three new assertions; restored → 49 passed;
  • removing the key from the blank template file → Tests 3 failed | 27 passed (30) in src/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 through scripts/pm/os-verify-lock.sh, this container's shared verify lock.

Package suites and types:

  • pnpm --filter @objectstack/cli --filter create-objectstack testTest Files 13 passed (13) / Tests 140 passed (140) (create-objectstack) and Test Files 153 passed (153) / Tests 1696 passed (1696) (cli); lock verdict command-exit 0.
  • pnpm --filter @objectstack/cli --filter create-objectstack typechecktsc --noEmit clean in both.
  • pnpm lint — clean, no findings; lock verdict command-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.mjsOK: 13 package(s) read outside themselves, all declared, and turbo.json hashes every declared glob.
  • check:objectui-changesetobjectui-range --self-test: all checks passed.
  • check:slot-lookupslot-lookup ratchet holds: 107 unswept site(s) in 25 file(s), none new.
  • check:template-version-syncsync-template-versions --self-test: 40 assertions over temp fixtures, running the real CLI.
  • check:test-source-aliasOK — 72 packages with tests scanned.
  • check:type-check-coverage — self-test 47 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 as lint.yml does, 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-auth surplus it reports is untouched by this diff and not lowered here.
  • check:type-source-resolutionOK — 77 packages with a tsconfig.json scanned.
  • scripts/check-adr-0087-registration.mjsthis PR adds no declared-breaking changeset (1 non-breaking changeset(s) seen).
  • scripts/check-changeset-no-major.mjsThis diff introduces no major bump.
  • scripts/check-ci-filter-parity.mjsOK: all 83 declared cross-package glob(s) (72 unique) are covered.
  • scripts/check-empty-changeset.mjsNo empty-frontmatter changeset introduced by this diff (1 declaring changeset(s) added).
  • scripts/check-plugin-teardown-shape.mjs61 Plugin implementation(s) across 4443 source(s), baseline fully burned down.
  • scripts/docs-audit/check-affected-docs.mjsaffected-docs self-test: 339 cases pass.

Convention-triggered families (this diff adds and edits test files):

  • check:query-options-erasureratchet 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-matcherconformance holds: 276 matcher(s) discovered, 276 answer the combinator battery correctly or refuse it loudly.
  • check:type-check-coverage and check:type-check-debt — above.

Also run, since this diff edits files: check:nul-bytes (self-test 75 assertions, clean).


Generated by Claude Code

…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>
@github-actions

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

This PR changes 2 package(s): @objectstack/cli, objectstack-blank, touching 2 documentable anchor(s).

3 hand-written doc(s) NAME something this change touched and may need an implementation-accuracy re-verification:

  • content/docs/deployment/cli.mdx(via os init (command))
  • content/docs/getting-started/examples.mdx(via os init (command))
  • content/docs/getting-started/your-first-project.mdx(via os init (command))

1 release-owned page(s) also name something this change touched. These are read-only:

  • content/docs/releases/v17.mdx(via os init (command))

content/docs/releases/ is RELEASE-OWNED (AGENTS.md "Documentation Guardrails"): release
notes are written centrally at release time, and a code PR that edits them is the exact PR
that guardrail exists to stop. They are still audited — read-only. If one of them is actually
wrong, file an issue or open a dedicated docs-only PR; do not edit it here.

What this run could not see
  • 1 changed file(s) yielded no anchor (packages/create-objectstack/src/templates/blank/pnpm-workspace.yaml) — pages documenting those are invisible to this run
  • the SDK route bridge reached 45 of 221 client-bound route-ledger rows — the other 176 have no registrar path: tail to select them, so pages documenting THEIR client methods cannot appear above, on this or any run: node scripts/docs-audit/affected-docs.mjs --bridge-coverage

Coarse fallback — 23 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): node scripts/docs-audit/affected-docs.mjs --json 072d072d2a74e358e923f6f6a76acbf7621305c3packageMentionDocs.

Which tree this was computed on

This run read content/docs from c36b505dd6050e477af8209719d8a86bbdd200bc — the merge of head b83ba6738ac3a1d72bb8d9dab7093fc86cdf8fa5 into base 072d072d2a74e358e923f6f6a76acbf7621305c3, which is what actions/checkout gives a pull_request run. Not the PR head.

A worktree cut from an older main holds a different content/docs, so re-deriving there can legitimately return a different list — that is a different tree, not a wrong row. To answer on the same tree:

# 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

⚠️ That checkout carried uncommitted changes, so the commit above does not fully identify what was read.

Advisory only, and a precision-first one (#9192): a page is listed because it names a
symbol, wire route or SDK method this diff touched — not because it mentions a changed
package. Each row says which anchor put it there, so a wrong row is reportable rather than
merely annoying. To re-verify, run the docs-accuracy-audit workflow scoped to these files:
node scripts/docs-audit/affected-docs.mjs 072d072d2a74e358e923f6f6a76acbf7621305c3 → pass the list as
args.docs, on the commit named under Which tree this was computed on.

@github-actions

Copy link
Copy Markdown
Contributor

⛔ merge queue 构建失败 — 先分诊,再决定要不要重排

队列构建 32580464495 红了。队列跑的是全量套件(PR 侧 CI 只跑 affected 子集),
所以失败的测试可能在本 PR 没碰过的包里 —— 那不是重排能修的。每次盲目重排都会让排在后面的所有 PR 重建一轮。

失败的 job(日志抽取,best effort):

  • Console Pin Gate — 失败步骤: Build the Console SPA at the pinned objectui SHA

    ✗ Build failed in 6.17s
    

↳ 失败原因 是判读的关键:超时Test timed out in … / Hook timed out in …)多半是负载/时序,不是本 PR 的回归;
断言AssertionError: …)才指向真实的行为改变。两者的 FAIL 行长得一模一样,只有这一行能区分。

跨 PR 相同签名(24h,按失败测试文件聚合):

  • ⚠️本次没有可用的聚合签名(日志里没有能解析出测试文件名的 FAIL 行)—— 这不是「没有同签名的其他 PR」,是这一轮没测到。跨 PR 聚合本次不可用,请手工比对其他 PR 的同类评论。
  • ⚠️ 24h 评论账本没读完(超过 5 页仍未读到窗口尽头),所以上面的「不同 PR 数」是下界,不是全量。

历史信号:

  • 本 PR 过去 24h 无队列失败记录(首次)。
  • 过去 24h 队列共有 82 个失败构建(不含本次)。

分诊清单:

  1. 失败测试在本 PR 改动的包里 → 真回归,修 PR。
  2. 失败测试与本 PR 无关 → 看上面的「跨 PR 相同签名」;已有汇总 issue ⇒ flaky/环境问题实锤,去那张 issue 上谈,修好前重排只会再烧一轮全队列。
  3. 两者都不是 → 可能与同组 PR 语义冲突;等前面的 PR 落地或失败出队后再重排一次即可,不要连续重排。

Generated by Claude Code · merge-queue-triage workflow (#4859)

Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentationImprovements or additions to documentationsize/mteststooling

Projects

None yet

2 participants

@os-elon@claude