Skip to content

Commit 004871f

Browse files
test(cli): spawn the CLI source entry with node, not the tsx binary, in 76 test files (#28637)
<!-- claude-projects-attribution: {"github_login":"os-project-manager"} --> _Requested by **Leehom** · [project thread](https://claude.ai/code/project/chan_01TenzsRopZj4jMbvjX8uz1X?thread=cmsg_01TenzsRopZj4jMbvjX8uz1XCHfAmysCiUnEeJgGTefeZA)_ 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,原因见下。 - **为什么改**:卡 #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: #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](https://claude.ai/code/session_01FEkQ5n9VfyHfEFRqzGZkWN)_ --------- Co-authored-by: Claude <noreply@anthropic.com>
1 parent 28ef35e commit 004871f

76 files changed

Lines changed: 171 additions & 233 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

‎packages/cli/test/artifact-flag-precedence.integration.test.ts‎

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -85,7 +85,6 @@ import {
8585
KERNEL_ASSIGNED_PORT,
8686
probeThroughChild,
8787
readKernelAssignedPort,
88-
TSX,
8988
} from './helpers/serve-process.js';
9089
import { defineStackSourceFromLiteral, linkSpec, writeDefineStackConfig } from './helpers/define-stack-fixture.js';
9190

@@ -127,7 +126,7 @@ interface Live {
127126

128127
function boot(argv: string[], cwd: string, env: Record<string, string | undefined>): Promise<Live> {
129128
return new Promise((resolveBoot, rejectBoot) => {
130-
const child = spawn(TSX, [CLI, ...argv], {
129+
const child = spawn(process.execPath, [CLI, ...argv], {
131130
cwd,
132131
// `childEnv`, never a bare `...process.env` — see its header. Neither
133132
// artifact variable leaks in from the runner: each case states its own.

‎packages/cli/test/build-json-advisory-parity.e2e.test.ts‎

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -82,7 +82,6 @@ import { linkSpec } from './helpers/define-stack-fixture.js';
8282

8383
const HERE = resolve(fileURLToPath(import.meta.url), '..');
8484
const CLI = resolve(HERE, '../bin/run-dev.js');
85-
const TSX = resolve(HERE, '../../../node_modules/.bin/tsx');
8685

8786
interface Run {
8887
code: number;
@@ -93,7 +92,7 @@ interface Run {
9392
function runCli(args: string[], cwd: string): Promise<Run> {
9493
return new Promise((resolvePromise) => {
9594
execFile(
96-
TSX,
95+
process.execPath,
9796
[CLI, ...args],
9897
{ cwd, maxBuffer: 16 * 1024 * 1024, env: childEnv({ NO_COLOR: '1' }) },
9998
(err, stdout, stderr) => {

‎packages/cli/test/build-json-failure-conversions.e2e.test.ts‎

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -88,7 +88,6 @@ import { LIVE_PROBE, liveProbeNodeOptions } from './helpers/live-conversion-prob
8888

8989
const HERE = resolve(fileURLToPath(import.meta.url), '..');
9090
const CLI = resolve(HERE, '../bin/run-dev.js');
91-
const TSX = resolve(HERE, '../../../node_modules/.bin/tsx');
9291
const COMPILE_TS = resolve(HERE, '../src/commands/compile.ts');
9392

9493
/**
@@ -111,7 +110,7 @@ interface Run {
111110
function runCli(args: string[], cwd: string): Promise<Run> {
112111
return new Promise((resolvePromise) => {
113112
execFile(
114-
TSX,
113+
process.execPath,
115114
[CLI, ...args],
116115
{ cwd, maxBuffer: 16 * 1024 * 1024, env: childEnv({ NO_COLOR: '1', NODE_OPTIONS: liveProbeNodeOptions() }) },
117116
(err, stdout, stderr) => {

‎packages/cli/test/build-json-failure-warnings.e2e.test.ts‎

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -93,7 +93,6 @@ import { linkSpec } from './helpers/define-stack-fixture.js';
9393

9494
const HERE = resolve(fileURLToPath(import.meta.url), '..');
9595
const CLI = resolve(HERE, '../bin/run-dev.js');
96-
const TSX = resolve(HERE, '../../../node_modules/.bin/tsx');
9796
const COMPILE_TS = resolve(HERE, '../src/commands/compile.ts');
9897

9998
interface Run {
@@ -105,7 +104,7 @@ interface Run {
105104
function runCli(args: string[], cwd: string): Promise<Run> {
106105
return new Promise((resolvePromise) => {
107106
execFile(
108-
TSX,
107+
process.execPath,
109108
[CLI, ...args],
110109
{ cwd, maxBuffer: 16 * 1024 * 1024, env: childEnv({ NO_COLOR: '1' }) },
111110
(err, stdout, stderr) => {

‎packages/cli/test/build-json-undeclared-key-parity.e2e.test.ts‎

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,6 @@ import { linkSpec } from './helpers/define-stack-fixture.js';
7575

7676
const HERE = resolve(fileURLToPath(import.meta.url), '..');
7777
const CLI = resolve(HERE, '../bin/run-dev.js');
78-
const TSX = resolve(HERE, '../../../node_modules/.bin/tsx');
7978

8079
interface Run {
8180
code: number;
@@ -86,7 +85,7 @@ interface Run {
8685
function runCli(args: string[], cwd: string): Promise<Run> {
8786
return new Promise((resolvePromise) => {
8887
execFile(
89-
TSX,
88+
process.execPath,
9089
[CLI, ...args],
9190
{ cwd, maxBuffer: 16 * 1024 * 1024, env: childEnv({ NO_COLOR: '1' }) },
9291
(err, stdout, stderr) => {

‎packages/cli/test/build-multi-package-artifact.e2e.test.ts‎

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -48,14 +48,13 @@ import { linkSpec } from './helpers/define-stack-fixture.js';
4848

4949
const HERE = resolve(fileURLToPath(import.meta.url), '..');
5050
const CLI = resolve(HERE, '../bin/run-dev.js');
51-
const TSX = resolve(HERE, '../../../node_modules/.bin/tsx');
5251

5352
interface Run { code: number; stdout: string; stderr: string }
5453

5554
function runCli(args: string[], cwd: string): Promise<Run> {
5655
return new Promise((resolvePromise) => {
5756
execFile(
58-
TSX,
57+
process.execPath,
5958
[CLI, ...args],
6059
{ cwd, maxBuffer: 16 * 1024 * 1024, env: childEnv({ NO_COLOR: '1' }) },
6160
(err, stdout, stderr) => {

‎packages/cli/test/build-package-docs-attachment.e2e.test.ts‎

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -48,14 +48,13 @@ import { linkSpec } from './helpers/define-stack-fixture.js';
4848

4949
const HERE = resolve(fileURLToPath(import.meta.url), '..');
5050
const CLI = resolve(HERE, '../bin/run-dev.js');
51-
const TSX = resolve(HERE, '../../../node_modules/.bin/tsx');
5251

5352
interface Run { code: number; stdout: string; stderr: string }
5453

5554
function runCli(args: string[], cwd: string): Promise<Run> {
5655
return new Promise((resolvePromise) => {
5756
execFile(
58-
TSX,
57+
process.execPath,
5958
[CLI, ...args],
6059
{ cwd, maxBuffer: 16 * 1024 * 1024, env: childEnv({ NO_COLOR: '1' }) },
6160
(err, stdout, stderr) => {

‎packages/cli/test/build-text-face-advisory-count.test.ts‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -82,7 +82,7 @@ import { execFile } from 'node:child_process';
8282
import { mkdtempSync, rmSync, writeFileSync, mkdirSync } from 'node:fs';
8383
import { tmpdir } from 'node:os';
8484
import { join } from 'node:path';
85-
import { CLI, TSX, childEnv } from './helpers/serve-process.js';
85+
import { CLI, childEnv } from './helpers/serve-process.js';
8686
import { linkSpec } from './helpers/define-stack-fixture.js';
8787

8888
interface Run {
@@ -94,7 +94,7 @@ interface Run {
9494
function runCli(args: string[], cwd: string): Promise<Run> {
9595
return new Promise((resolvePromise) => {
9696
execFile(
97-
TSX,
97+
process.execPath,
9898
[CLI, ...args],
9999
{ cwd, maxBuffer: 16 * 1024 * 1024, env: childEnv({ NO_COLOR: '1' }) },
100100
(err, stdout, stderr) => {

‎packages/cli/test/cloud-login-json-ndjson.e2e.test.ts‎

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,6 @@ import { childEnv } from './helpers/serve-process.js';
7575

7676
const HERE = resolve(fileURLToPath(import.meta.url), '..');
7777
const CLI = resolve(HERE, '../bin/run-dev.js');
78-
const TSX = resolve(HERE, '../../../node_modules/.bin/tsx');
7978
const CLOUD_LOGIN_SRC = resolve(HERE, '../src/commands/cloud/login.ts');
8079
const REPO_ROOT = resolve(HERE, '../../..');
8180
const CLI_DOCS = resolve(REPO_ROOT, 'content/docs/deployment/cli.mdx');
@@ -270,7 +269,7 @@ async function runCloudDeviceLogin(opts: {
270269
});
271270

272271
const shell = [
273-
`'${TSX}' '${CLI}' cloud login${opts.json ? ' --json' : ''} --no-browser`,
272+
`'${process.execPath}' '${CLI}' cloud login${opts.json ? ' --json' : ''} --no-browser`,
274273
`--url 'http://127.0.0.1:${port}'`,
275274
`> '${outFile}' 2> '${errFile}'`,
276275
].join(' ');

‎packages/cli/test/compile-artifact-packages.e2e.test.ts‎

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,6 @@ import { linkSpec } from './helpers/define-stack-fixture.js';
3838

3939
const HERE = resolve(fileURLToPath(import.meta.url), '..');
4040
const CLI = resolve(HERE, '../bin/run-dev.js');
41-
const TSX = resolve(HERE, '../../../node_modules/.bin/tsx');
4241

4342
interface Run {
4443
code: number;
@@ -49,7 +48,7 @@ interface Run {
4948
function runCli(args: string[], cwd: string): Promise<Run> {
5049
return new Promise((resolvePromise) => {
5150
execFile(
52-
TSX,
51+
process.execPath,
5352
[CLI, ...args],
5453
{ cwd, maxBuffer: 16 * 1024 * 1024, env: childEnv({ NO_COLOR: '1' }) },
5554
(err, stdout, stderr) => {

0 commit comments

Comments
 (0)