Uh oh!
There was an error while loading. Please reload this page.
fix(cli): print init's "Created files" summary after install, not before - #10963
Conversation
…ore (#10557) `objectstack init` accumulated the "Created files" list while writing the template — before `<pm> install` ran — so the printed summary could never name `pnpm-lock.yaml` / `package-lock.json` or `node_modules/`. Measured against a real `pnpm install`: 7 entries printed, 9 paths on disk (+ node_modules/, 575 MB). Moves the print to after the install attempt (succeeded or failed) and derives it from a walk of the finished directory, reusing `create-objectstack`'s `created-summary.ts` (now published as the `create-objectstack/created-summary` subpath) instead of a second copy of the same renderer — the two scaffold paths already drifted once (#10499) from carrying separate implementations of this exact list. Fixes#10557 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019bmVFqoQPq63zhKrxdYG1r
📓 Docs Drift CheckThis PR changes 2 package(s): 11 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 — 27 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 019891c63fd3c582769637e1ff32232a5df44aaf && git checkout 019891c63fd3c582769637e1ff32232a5df44aaf
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin 926778bce01620ae56a9b0eecbf0400c43a64aa8 d7fd68d636c17d5397ddcc2844119292b09174af && git checkout -B drift-repro 926778bce01620ae56a9b0eecbf0400c43a64aa8 && git merge --no-ff d7fd68d636c17d5397ddcc2844119292b09174af
node scripts/docs-audit/affected-docs.mjs --json 926778bce01620ae56a9b0eecbf0400c43a64aa8
|
⛔ merge queue 构建失败 — 先分诊,再决定要不要重排队列构建 32542993504 红了。队列跑的是全量套件(PR 侧 CI 只跑 affected 子集), 失败的 job(日志抽取,best effort):
跨 PR 相同签名(24h,按失败测试文件聚合):
历史信号:
分诊清单:
Generated by Claude Code · merge-queue-triage workflow (#4859) |
Uh oh!
There was an error while loading. Please reload this page.
Fixes#10557
Premise, reproduced
objectstack init demo-app -t app --package-manager pnpm, realpnpm install, then a full disk walk of the result:Confirmed against the CLI's real npm path too (
--package-manageromitted,npm_config_user_agentunset → npm):package-lock.json(225 KB) +node_modules/(611 MB, 442 packages) equally unnamed. Root cause matches the card:console.log('Created files:')runs from a list built while writing the template, before theexecSync('<pm> install', …)call a few lines later.The fork — moved the print, not the prediction
Measured the command's actual control flow before picking a side:
init.tsthat knows the shape of a package manager's output ahead of time —chosenPmis resolved from a flag/env at the momentinstallruns, and nothing upstream renders a lockfile name or estimatesnode_modules/size. Reaching for it means either hard-codingpnpm-lock.yaml/package-lock.json/yarn.lock/bun.lock(four names, one per package manager, none actually written yet) or greping the OS's copy. Fragile, and still wrong on a failed install where none of them land.packages/create-objectstack/src/index.tshit the identical defect shape in [finding] The scaffolder's "Created files" list names 12 files and omits AGENTS.md, both ~968 KB skill trees and the lockfiles — then tells you to review the skills it did not name #10323 and answered it by printing unconditionally after the install attempt — success or failure — from a walk of the finished directory (created-summary.ts). Confirmed by drivinginitthrough a real install failure (apnpmshim onPATHthat exits 1): the summary still prints, and it prints exactly what's on disk — no lockfile, nonode_modules/, because neither actually landed. So (A)'s open question ("what prints on a failed install?") has a concrete, already-shipped answer: the truth, whatever that happens to be at that point.Took (A).
printCreatedFilesSummary()now runs once, right after the installtry/catch(regardless ofinstallSucceeded), and derives the list fromsummarizeTree(targetDir)— a disk walk — rather than the accumulated array. The accumulated array and its 6.push()call sites are removed entirely; nothing else read them.Reused
created-summary.ts— as a published subpath, not a copyPer the card's suggested landing,
packages/create-objectstack/src/created-summary.tsis now imported byinit.tsinstead of re-implemented:create-objectstack/package.jsongains anexportsentry for./created-summary(types + import), andtsup.config.tsgains a second build entry (dts: { entry: ['src/created-summary.ts'] }) so it ships a.d.ts—create-objectstack's existing entry (index.ts) stays undeclared, since it's a CLI side-effect module, not a library surface.@objectstack/clitakescreate-objectstack: "workspace:*"as a new dependency (the reuse runs from the already-dependency-heavyclipackage into the deliberately dependency-light scaffolder, never the other way —create-objectstackgains no new dependency and stays npm-registry-light fornpx create-objectstack).init.tsimports{ summarizeTree, describeEntry } from 'create-objectstack/created-summary'.One correctness fix that came along for the ride:
init's "no name given" path scaffolds into the current directory with no emptiness check (unlike the named-arg path, which refuses a non-empty target) — so a disk walk of that directory can include filesinitdidn't write. Mirroredcreate-objectstack's owntargetWasEmptysnapshot (taken before the first write) so the header reads "Project contents:" instead of "Created files:" when the directory already had contents, with a one-line note — verified manually (see below).What actually changed (
packages/cli/src/commands/init.ts)createdFiles: string[]accumulator and its 6 push sites.targetWasEmpty(readdir snapshot before the first write).printCreatedFilesSummary().Verification
Manual, against the real CLI (
node packages/cli/bin/run.js init … --package-manager pnpm), four scenarios:pnpm-lock.yamlandnode_modules/(collapsed, "over 2,000 files") both named; every top-level disk entry reachable.--no-install→ summary matches disk exactly (no phantom lockfile/node_modules).pnpmonPATHexiting 1) → summary still prints, and correctly omits the lockfile/node_modulesthat never landed.README.md→ "Project contents:" (not "Created files:"),README.mdcorrectly listed.Pinned:
packages/cli/test/init-created-files-summary.e2e.test.ts— spawns the real command (bin/run-dev.jsviatsx, no build needed) with a fakepnpmonPATH(writespnpm-lock.yaml+ 15 dummynode_modules/entries + a real symlink to this repo's already-built@objectstack/spec, so the post-install self-test also passes — no network, ~11s). Asserts: exit 0;pnpm-lock.yamland a collapsednode_modules/line are in the printed summary; the install log precedesCreated files:in stdout (the ordering fix itself); every top-level path really on disk is named somewhere in the summary; a failed-install run still prints a summary that is accurate (no phantom entries).Reverse-verification (committed fix, then
git checkout origin/main -- packages/cli/src/commands/init.ts, reran the pinned test, thengit checkout HEAD -- packages/cli/src/commands/init.tsto restore):pnpm-lock.yaml/node_modules/not in stdout (expected … to contain 'pnpm-lock.yaml')installIdx404 not less thansummaryIdx208 — summary printed first)node_modulesGate union, derived at final commit
d7fd68d636vianode scripts/pm/dispatch-gates.mjs(re-run after every push; this is the union at HEAD):Local gates, all exit 0 (per-command exit captured before any pipe):
check:changeset-gate-self-tests,check:cross-package-test-inputs(pnpm alias + root script),check:docs-image-tag,check:objectui-changeset,check:override-consistency,check:slot-lookup,check:template-version-sync,check:test-source-alias,check:type-source-resolution,check-adr-0087-registration.mjs,check-changeset-fixed.mjs,check-changeset-no-major.mjs,check-ci-filter-parity.mjs,check-empty-changeset.mjs,check-osv-exemptions.mjs,check-plugin-teardown-shape.mjs,docs-audit/check-affected-docs.mjs.Convention-triggered (new test file added):
check:query-options-erasure,check:type-check-coverage,check:type-check-debt --re-measure(full workspace built first perlint.yml's own procedure),check:engine-double-contract,check:where-matcher— all exit 0, no new ledger entries, no baseline touched.One gate (
check:test-source-alias) caught a real gap on first run: the new e2e test transitively reachescreate-objectstack/created-summary(throughinit.ts, which 3 existing test files already import), and without an alias that resolves throughdist/rather than source. Fixed by adding one anchoredresolve.aliasentry topackages/cli/vitest.config.ts(documented inline, mirroring the file's existingservice-cacheentry) — confirmed red before, green after.pnpm --filter @objectstack/cli test(full package suite, post-fix): 151 test files / 1667 tests, all passing.pnpm --filter create-objectstack test: 9 files / 102 tests, all passing. Both packages typecheck clean.Out of scope
Nothing filed — no unrelated defect surfaced during measurement.
Generated by Claude Code