Skip to content

fix(cli): print init's "Created files" summary after install, not before - #10963

Merged
os-elon merged 1 commit into
mainfrom
claude/issue-10557-init-created-files-summary
Aug 22, 2026
Merged

fix(cli): print init's "Created files" summary after install, not before#10963
os-elon merged 1 commit into
mainfrom
claude/issue-10557-init-created-files-summary

Conversation

@os-elon

Copy link
Copy Markdown
Collaborator

Fixes#10557

Premise, reproduced

objectstack init demo-app -t app --package-manager pnpm, real pnpm install, then a full disk walk of the result:

printed "Created files:" real disk (demo-app/)
package.json package.json
pnpm-workspace.yaml pnpm-lock.yaml <- unnamed (138 KB)
objectstack.config.ts pnpm-workspace.yaml
tsconfig.json objectstack.config.ts
src/objects/index.ts tsconfig.json
src/objects/demo_app_item.ts src/objects/index.ts
.gitignore src/objects/demo_app_item.ts
.gitignore
node_modules/ <- unnamed (575 MB)

Confirmed against the CLI's real npm path too (--package-manager omitted, npm_config_user_agent unset → 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 the execSync('<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:

  • B (predict what install will write) has no anchor point in init.ts that knows the shape of a package manager's output ahead of time — chosenPm is resolved from a flag/env at the moment install runs, and nothing upstream renders a lockfile name or estimates node_modules/ size. Reaching for it means either hard-coding pnpm-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.
  • A (move the print after install) already has a working precedent in this exact repo: packages/create-objectstack/src/index.ts hit 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 driving init through a real install failure (a pnpm shim on PATH that exits 1): the summary still prints, and it prints exactly what's on disk — no lockfile, no node_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 install try/catch (regardless of installSucceeded), and derives the list from summarizeTree(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 copy

Per the card's suggested landing, packages/create-objectstack/src/created-summary.ts is now imported by init.ts instead of re-implemented:

  • create-objectstack/package.json gains an exports entry for ./created-summary (types + import), and tsup.config.ts gains a second build entry (dts: { entry: ['src/created-summary.ts'] }) so it ships a .d.tscreate-objectstack's existing entry (index.ts) stays undeclared, since it's a CLI side-effect module, not a library surface.
  • @objectstack/cli takes create-objectstack: "workspace:*" as a new dependency (the reuse runs from the already-dependency-heavy cli package into the deliberately dependency-light scaffolder, never the other way — create-objectstack gains no new dependency and stays npm-registry-light for npx create-objectstack).
  • init.ts imports { 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 files init didn't write. Mirrored create-objectstack's own targetWasEmpty snapshot (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)

  • Removed the createdFiles: string[] accumulator and its 6 push sites.
  • Added targetWasEmpty (readdir snapshot before the first write).
  • Print moved from immediately-after-template-write to immediately-after-the-install-attempt (success or failure), via the new printCreatedFilesSummary().

Verification

Manual, against the real CLI (node packages/cli/bin/run.js init … --package-manager pnpm), four scenarios:

  • successful pnpm install → pnpm-lock.yaml and node_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).
  • simulated failed install (fake pnpm on PATH exiting 1) → summary still prints, and correctly omits the lockfile/node_modules that never landed.
  • no name arg, into a directory with pre-existing README.md → "Project contents:" (not "Created files:"), README.md correctly listed.

Pinned: packages/cli/test/init-created-files-summary.e2e.test.ts — spawns the real command (bin/run-dev.js via tsx, no build needed) with a fake pnpm on PATH (writes pnpm-lock.yaml + 15 dummy node_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.yaml and a collapsed node_modules/ line are in the printed summary; the install log precedes Created 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, then git checkout HEAD -- packages/cli/src/commands/init.ts to restore):

  • RED on pre-fix code, 3 of 4 tests, exactly the ones that discriminate the fix:
    • pnpm-lock.yaml/node_modules/ not in stdout (expected … to contain 'pnpm-lock.yaml')
    • ordering assertion failed (installIdx 404 not less than summaryIdx 208 — summary printed first)
    • "every path on disk is named" failed on node_modules
    • (the 4th — "a failed install still prints a summary" — passed even pre-fix, for the unrelated reason that the old code always printed early; expected, not a discriminator)
  • GREEN on the restored fix, all 4.

Gate union, derived at final commit d7fd68d636 via node 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 per lint.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 reaches create-objectstack/created-summary (through init.ts, which 3 existing test files already import), and without an alias that resolves through dist/ rather than source. Fixed by adding one anchored resolve.alias entry to packages/cli/vitest.config.ts (documented inline, mirroring the file's existing service-cache entry) — 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

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

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

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

11 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/deployment/self-hosting.mdx(via package.json (literal))
  • content/docs/deployment/tenancy-modes.mdx(via package.json (literal))
  • content/docs/deployment/troubleshooting.mdx(via package.json (literal))
  • content/docs/getting-started/examples.mdx(via package.json (literal), tsconfig.json (literal), os init (command))
  • content/docs/getting-started/your-first-project.mdx(via package.json (literal), tsconfig.json (literal), os init (command))
  • content/docs/plugins/development.mdx(via package.json (literal), tsconfig.json (literal))
  • content/docs/plugins/index.mdx(via package.json (literal), tsconfig.json (literal))
  • content/docs/protocol/kernel/index.mdx(via package.json (literal), tsconfig.json (literal))
  • content/docs/protocol/kernel/plugin-spec.mdx(via package.json (literal), tsconfig.json (literal))
  • content/docs/protocol/objectql/schema.mdx(via package.json (literal))

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
  • 4 changed file(s) yielded no anchor (packages/cli/package.json, packages/cli/vitest.config.ts, packages/create-objectstack/package.json, …) — pages documenting those are invisible to this run
  • 1 anchor(s) matched too much of the corpus to be a work list: objectstack.config.ts (literal, 33 pages)
  • 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 — 27 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 926778bce01620ae56a9b0eecbf0400c43a64aa8packageMentionDocs.

Which tree this was computed on

This run read content/docs from 019891c63fd3c582769637e1ff32232a5df44aaf — the merge of head d7fd68d636c17d5397ddcc2844119292b09174af into base 926778bce01620ae56a9b0eecbf0400c43a64aa8, 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 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

⚠️ 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 926778bce01620ae56a9b0eecbf0400c43a64aa8 → pass the list as
args.docs, on the commit named under Which tree this was computed on.

@github-actionsgithub-actionsBot added documentation Improvements or additions to documentation dependencies Pull requests that update a dependency file tests tooling labels Aug 22, 2026
@os-elon
os-elon marked this pull request as ready for review August 22, 2026 01:17
@os-elon
os-elon added this pull request to the merge queueAug 22, 2026
@github-actions

Copy link
Copy Markdown
Contributor

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

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

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

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

    ✗ Build failed in 5.91s
    

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

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

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

历史信号:

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

分诊清单:

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

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

Merged via the queue into main with commit 818e027Aug 22, 2026
35 checks passed
@os-elon
os-elon deleted the claude/issue-10557-init-created-files-summary branch August 22, 2026 01:30
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

dependenciesPull requests that update a dependency filedocumentationImprovements or additions to documentationsize/mteststooling

Projects

None yet

Development

Successfully merging this pull request may close these issues.

objectstack init prints its "Created files" list before pnpm install, so it never names pnpm-lock.yaml or node_modules/

2 participants

@os-elon@claude