Skip to content

test(cli): spawn the CLI source entry with node, not the tsx binary, in 76 test files - #28637

Merged
objectstack-fleet[bot] merged 3 commits into
mainfrom
claude/project-thread-8k0kp2
Nov 1, 2026
Merged

objectstack-fleet[bot] merged 3 commits into
mainfrom
claude/project-thread-8k0kp2

Conversation

@objectstack-fleet

Copy link
Copy Markdown
Contributor

Requested by Leehom · project thread

Part of #23181 (slice 34). The card stays open for later slices: this one takes only the tsx → process.execPath lever on the 78 packages/cli test files in the order's list.

Clause-②: no
Metadata-protocol: none

维护者速读

  • 改了什么:packages/cli 里 76 个测试文件起 CLI 源码入口 bin/run-dev.js 时,不再经 node_modules/.bin/tsx 包装层,改为直接用 process.execPath(测试自己的 node)起同一个入口,与 migrate 系列(slice 12)、slice 25、slice 28 已有写法一致。其中 23 个文件还改正了因此变假的注释。清单里其余 2 个文件保留 tsx,原因见下。
  • 为什么改:卡 test budget (domain:cli): 119 test file(s) over 10 s of wall in the hourly full battery (Part of #23151) #23181 的测试提速职责。tsx 包装层是多出来的一个 node 进程加一次转译;入口文件、src/、NODE_ENV=development 两种起法都一样(oclif 在 development 下自己在进程内注册 tsx,把命令树映射到 src/,已用 debug 日志实测)。A/B 六个文件,每个快 25%–43%。
  • 风险与回滚:只改测试文件,不发布任何东西(cli 的 files[] 只有 dist、README、CHANGELOG);没有删、没有放宽任何断言,用例名和数量不变,层级划分不变;回滚即 revert。
  • 先看:packages/cli/test/verify-json-stdout.test.ts(改正了"纯 node 会从 dist 读命令"这句错注释);packages/cli/test/serve-publishes-bound-port.e2e.test.ts(进程组清理的注释,不再有 tsx 孙进程);packages/cli/test/diff-usage-error-stream.e2e.test.ts(唯一一处断言改动:对照用例检查的启动器从 tsx 换成 process.execPath)。
  • 两分钟验证:pnpm --filter @objectstack/cli exec vitest run --project integration test/verify-json-stdout.test.ts test/generate-refuses-retired-generator.test.ts → 11/11 通过。
  • 需要你做的:无,席位落地。

What changes

Sort. The order's list (pm-orders/server/evidence/23181-s34/files.txt) re-derived on base 7449e479c4: git grep -lw TSX over packages/cli gives 131 paths; minus test/helpers/**, the 53 left over are every file slices 24–33 own plus two non-test sources (src/utils/authored-source-codemod.ts, vitest-tiers.ts). So the list is exactly the 78 files, no difference. One note on the order's wording: 33 of the 78 import TSX from test/helpers/serve-process.ts; the other 45 declare their own const TSX = resolve(…, 'node_modules/.bin/tsx'). Both spawn the same way.

bucket files what happens
(a) spawns bin/run-dev.js through the tsx binary, and the test is not about that launcher 76 switched to process.execPath
(b) already on process.execPath, tsx only in prose 0 none
(c) keeps tsx 2 run-dev-unbuilt-workspace.e2e: it pins bin/run-dev.js's own stderr drain and EPIPE survival, and the shim's header measures that listener shape under tsx (tsx's hooks worker pipes into process.stderr, so Console's keep-alive is never installed); it also drives the run with a --import hook through NODE_OPTIONS. serve-host-fallback-base.e2e: its child is not the CLI entry but a probe.mjs that imports src/commands/serve.ts directly, which needs the tsx loader (the slice 25 reading: no gain for a .ts helper child)

The switch, bucket (a). Every spawn site passes process.execPath where it passed TSX, with bin/run-dev.js still the first argument; the child env, cwd, argv, stdio and timeouts are unchanged. A local TSX constant is removed; a helper import drops TSX (the export stays). Spawn shapes covered: spawn, spawnSync, execFile, execFileSync, promisified execFile, the files' own run(file, …) wrappers, the script(1) command line of the two login NDJSON files, the os shell shim in i18n-extract-check-hint, and the { exec, argv } entry of start-port-banner-agreement's auto-shift leg. The three generate files' runTsx helper is now runCli.

Why the child is the same. bin/run-dev.js pins NODE_ENV=development; under it @oclif/core 5.1.2 (config/ts-path.js) registers tsx in-process and maps dist/commands to src/commands, with packages/cli/dist built or not. Probe on this branch with dist/ built: DEBUG='*ts-path*' node bin/run-dev.js --version logs registering tsx at …/packages/cli, Successfully imported tsx and Found source directory for …/dist/commands at …/src/commands (pm-orders/server/evidence/23181-s34/oclif-ts-path-probe.txt). The entry's own static ../src/utils/*.ts imports load through Node 22's type stripping (.nvmrc = 22), as on the slice 12 files.

Prose that became false, corrected in 23 files: the eight verify-* files said plain node makes oclif resolve the command from dist/ (the probe above says otherwise); serve-publishes-bound-port, serve-port-drift-notice and package-install-local-jobs-shared-name described a tsx shim with the CLI in a grandchild; the rest said "spawns the CLI through tsx", "cold tsx starts" or "bin/run-dev.js + tsx". Prose that stays true is unchanged ("src/ through tsx", "oclif + tsx cold start", quotes of bin/run-dev.js's header). The // test-budget: headers of the six package-install-local-* files still say "the tsx source entry"; this slice does not touch headers.

The one assertion edit: diff-usage-error-stream's control case resolves the dev entry it drives checked existsSync(TSX); it now checks existsSync(process.execPath), the launcher it drives. Same meaning.

Not changed: no case name, case count, timeout, tier, project or // test-budget: header; no file under test/helpers/**; no source file.

Tests

  • All 76 switched files pass on 6410254c (the fix commits, before the merge). Queue tier: vitest run --project integration --maxWorkers=2 over the 29 non-.e2e files: 29/29 files, 213/213 tests. Nightly tier (OS_TEST_TIERS=nightly), the 47 .e2e files: 47/47 files, 671/671 tests. Lock VERDICT command-exit 0 on both.

  • Tiers unchanged. tierSignals from packages/cli/vitest-tiers.ts on all 78 files before and after: 78 integration, identical. The switched files still import or name CLI / run-dev.js.

  • A/B, six files over six commands. Head 6410254c vs base 7449e479c4, one box, one lock hold, N=3, --maxWorkers=1. Base bytes swapped into the same path (blob hash checked) so both legs keep the same tier and config; each leg its own vitest invocation, order alternated per round; restored from HEAD and git diff HEAD empty after. Reading: the default reporter's per-file line. Every leg exit 0, same test counts per side.

    file base (s) head (s) change
    generate-refuses-retired-generator 6.52, 6.73, 6.03 4.73, 4.80, 4.95 -24.9% (mean 6.43 → 4.82)
    verify-json-stdout 22.06, 23.04, 23.01 14.86, 14.73, 16.12 -32.9% (mean 22.70 → 15.24)
    serve-migrate-exit-schema-parity.integration 21.30, 21.44, 20.36 13.39, 12.90, 13.21 -37.4% (mean 21.04 → 13.17)
    i18n-check-platform-bucket.e2e 47.01, 44.79, 45.16 26.01, 26.19, 26.61 -42.5% (mean 45.65 → 26.27)
    login-json-ndjson.e2e 20.21, 19.73, 20.03 12.37, 12.83, 12.84 -36.6% (mean 19.99 → 12.68)
    requires-retired-capability.e2e 18.34, 18.64, 16.96 10.60, 10.44, 11.30 -40.0% (mean 17.98 → 10.78)

    Raw legs and the script: pm-orders/server/evidence/23181-s34/ab-summary.txt, ab.sh.

  • pnpm --filter @objectstack/cli typecheck (tsc and check:test-typecheck, which compiles the test layer under tsconfig.test.json) exit 0 on 6410254c, with NODE_OPTIONS=--max-old-space-size=4096; the debt ledger is unchanged (3 files, 28 errors, 6 pinned signatures). eslint --no-inline-config over the 76 files exit 0 (@objectstack/cli has no lint script).

  • No ablation: no tested behaviour changed (test-only).

Acceptance notes

  • Out of scope, for a later helper slice. The helper's own serve spawn (test/helpers/serve-process.ts, runServe(), around line 982) still goes through TSX. 12 test files call runServe( in code position: artifact-pinned-boot.e2e, serve-app-runtime-hooks.e2e, serve-boot-diagnostics-noise-budget.e2e, serve-boot-diagnostics.e2e, serve-cluster-driver-diagnosis.e2e, serve-no-artifact.e2e, serve-organizations-host-resolution.e2e, serve-organizations-mount-failure.e2e, serve-package-declared-capabilities, serve-package-registry-always-on.e2e, serve-port-readback.e2e, uncreatable-directory-doors.integration. Carrier: test budget (domain:cli): 119 test file(s) over 10 s of wall in the hourly full battery (Part of #23151) #23181.
  • skip-changeset. Test-only. @objectstack/cli's files[] is dist, README.md, CHANGELOG.md; its build emits no *.test.ts into dist/. No files[] content changes.

Verification

Head 95b877c4: the two fix commits 106e7364 (the switch) and 6410254c (the prose), plus one merge of origin/main after slices 24 (#28610), 26 (#28599) and 28 (#28616) landed, as the order asks. The merge touches none of the 78 files and nothing under test/helpers/**.

  • After the merge: pnpm install --frozen-lockfile && pnpm build (117/117 tasks), then the 29 queue-tier switched files again: 29/29 files, 213/213 tests. The 47 nightly files, the A/B and the typecheck above were taken on 6410254c.
  • Derived gates (node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack on 95b877c4, the same 48 as before the merge): 48/48 exit 0, exit codes captured before any pipe. --ran reconcile: 48 accounted, 48 run, 0 NOT-MEASURED, exit 0 (pm-orders/server/evidence/23181-s34/gates-ran-95b877c4.txt). Prerequisites: check-plugin-teardown-shape --self-test after fetching its control commit; check:dual-build-cjs-loads after the full build.
  • By hand: check-commit-card-trailers --range origin/main..HEAD exit 0; check-nul-bytes exit 0.
  • CI: the PR's check-runs are the seat's reading.

Generated by Claude Code

The tsx binary in front of the source entry is a second node process with
its own transform; @oclif/core registers tsx in-process for the src/ command
tree either way, so the entry, src/ and the pinned NODE_ENV stay the same.

Claude-Session: https://claude.ai/code/session_01FEkQ5n9VfyHfEFRqzGZkWN
Co-Authored-By: Claude <noreply@anthropic.com>
…alse

The verify files said plain node resolves the command from dist/; with
NODE_ENV=development @oclif/core maps the command tree to src/ and registers
tsx in-process. The serve boot files described a tsx shim and grandchild that
no longer exist.

Claude-Session: https://claude.ai/code/session_01FEkQ5n9VfyHfEFRqzGZkWN
Co-Authored-By: Claude <noreply@anthropic.com>
@github-actions

github-actions Bot commented Nov 1, 2026

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

Nothing in this diff resolved to a documentable surface (no symbol, route or SDK anchor derived from 0 changed package(s)), so this run has no opinion about the docs.

What this run could not see

Coarse fallback — 0 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 e098ff46dec6fc0f02fe0776cedfa6189d651e14 → packageMentionDocs.

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

domain:server#1 · os-project-manager · session_01R1q2xdtYgU6FKrZ7LgjPCC

席位核对 · ACCEPT · domain:server seat 1 · os-project-manager · session_01R1q2xdtYgU6FKrZ7LgjPCC · 2026-11-01T10:13Z

Head 95b877c4, report 6556431451 (on #23181), claim 6555841491 (slice 34). No contract review is owed: test-only, no packages/spec/** path, no governed path, no narrowing and no Clause-②: yes.

item reading
Shape Draft, base main, Part of #23181. 76 of slice 34's 78 listed packages/cli test files (2 keep tsx with a named reason), labels tests + skip-changeset. Clause-②: no, Metadata-protocol: none.
Diff check (seat) Every removed non-prose line names TSX/tsx, and every added non-prose line is its process.execPath replacement, an import list without TSX, or a local runTsx renamed runCli with its calls (argv, cwd and env unchanged). No timeout changed (the SPAWN_TIMEOUT_MS = 180_000 constants keep their value; only their comment drops "tsx"), and no shared helper is touched. The one assertion edit is diff-usage-error-stream's control precondition, existsSync(TSX) → existsSync(process.execPath), which follows the launcher it checks. Prose that the switch made false is corrected, including the stop() note in the serve file whose SIGTERM-first reason now reads as history. The two kept files: run-dev-unbuilt-workspace.e2e pins bin/run-dev.js's own stderr drain under tsx, and serve-host-fallback-base.e2e spawns a probe.mjs that imports src/commands/serve.ts, which needs the tsx loader.
Evidence CI green on head 95b877c4 (35 success, 23 skipped, 0 failed; the CI and Lint & Type Check workflow runs both success, and the required Test Core, TypeScript Type Check and Lint & Repo Gates contexts are success in the latest reading). Governed check: NOT governed, 404 changed lines. The report gives 76/76 switched files passing (29 queue-tier files, 213 tests; 47 nightly files, 671 tests) on the merged head, 48 of 48 derived gates exit 0, and A/B N=3 on six files of −25 % to −43 %.
Landing 「按照 pm-dispatch 规则复核过的都授权」 (Leehom, 2026-10-25T11:35Z): ready and auto-merge through the relay, once every check on this head is green or an expected skip.

Generated by Claude Code

@objectstack-fleet
objectstack-fleet Bot marked this pull request as ready for review November 1, 2026 10:14
@objectstack-fleet
objectstack-fleet Bot enabled auto-merge November 1, 2026 10:14
@objectstack-fleet
objectstack-fleet Bot added this pull request to the merge queue Nov 1, 2026
Merged via the queue into main with commit 004871f Nov 1, 2026
59 checks passed
@objectstack-fleet
objectstack-fleet Bot deleted the claude/project-thread-8k0kp2 branch November 1, 2026 10:30
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size/m skip-changeset PR has no user-facing published change; bypasses the changeset gate tests

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant