diff --git a/packages/cli/src/commands/validate-json-strict-exit.e2e.test.ts b/packages/cli/src/commands/validate-json-strict-exit.e2e.test.ts index bca34da64db..ccd9b7a6003 100644 --- a/packages/cli/src/commands/validate-json-strict-exit.e2e.test.ts +++ b/packages/cli/src/commands/validate-json-strict-exit.e2e.test.ts @@ -98,7 +98,7 @@ * number only exists once Node has exited and the kernel has masked it to * `& 0xFF`. `test/migrate-exit-code.e2e.test.ts` is the precedent and states * this in the same words — the audience `--json` exists for reads the SHELL, so - * that is what gets asserted. Spawned through `bin/run-dev.js` + tsx, so the + * that is what gets asserted. Spawned through `bin/run-dev.js` + `node`, so the * suite does not depend on `packages/cli/dist` having been built. * * ## Why this file is not beside its siblings in `packages/cli/test/` @@ -143,7 +143,6 @@ function linkSpec(dir: string): void { const HERE = resolve(fileURLToPath(import.meta.url), '..'); const CLI = resolve(HERE, '../../bin/run-dev.js'); -const TSX = resolve(HERE, '../../../../node_modules/.bin/tsx'); /** * The test-only live conversion the child loads with `--import` (its header @@ -230,7 +229,7 @@ interface Run { function runCli(args: string[], cwd: string): Promise { return new Promise((resolvePromise) => { execFile( - TSX, + process.execPath, [CLI, ...args], { cwd, diff --git a/packages/cli/test/build-callable-names.test.ts b/packages/cli/test/build-callable-names.test.ts index 1528b76c80b..1bdbe417850 100644 --- a/packages/cli/test/build-callable-names.test.ts +++ b/packages/cli/test/build-callable-names.test.ts @@ -42,7 +42,7 @@ * * Each kind's fixture carries both collisions against an authored `functions` * entry (a shared function; an equal name) under disjoint names, round-trips - * through `objectstack build` (`bin/run-dev.js`, source via tsx) and then the + * through `objectstack build` (`bin/run-dev.js`, source under `node`) and then the * runtime's own `loadArtifactBundle`, and asserts that each authored name ships * under itself and answers its own code, and that each hook or action ref * answers its own code. Every callable names a module-scope constant, so no @@ -66,9 +66,8 @@ import { locateCallables } from '../src/utils/build-runtime.js'; const HERE = resolve(fileURLToPath(import.meta.url), '..'); const CLI = resolve(HERE, '../bin/run-dev.js'); -const TSX = resolve(HERE, '../../../node_modules/.bin/tsx'); const LOWER_CALLABLES = resolve(HERE, '../src/utils/lower-callables.ts'); -/** A cold `tsx` spawn of the CLI source entry runs well past vitest's 5 s default. */ +/** A cold spawn of the CLI source entry runs well past vitest's 5 s default. */ const SPAWN_TIMEOUT_MS = 120_000; // ── The census ────────────────────────────────────────────────────────────── @@ -258,7 +257,7 @@ interface Run { function runCli(args: string[], cwd: string): Promise { return new Promise((resolvePromise) => { execFile( - TSX, + process.execPath, [CLI, ...args], { cwd, maxBuffer: 16 * 1024 * 1024, env: childEnv({ NO_COLOR: '1' }) }, (err, stdout, stderr) => { diff --git a/packages/cli/test/build-connector-spec-file-artifact.e2e.test.ts b/packages/cli/test/build-connector-spec-file-artifact.e2e.test.ts index be75a041624..630b61a538b 100644 --- a/packages/cli/test/build-connector-spec-file-artifact.e2e.test.ts +++ b/packages/cli/test/build-connector-spec-file-artifact.e2e.test.ts @@ -10,7 +10,7 @@ * are kept. The control for the refusal is the same stack with the file * present, which both commands accept. * - * Runs the CLI from source (`bin/run-dev.js` through tsx), as the other build + * Runs the CLI from source (`bin/run-dev.js` under `node`), as the other build * suites do, so `compile.ts` and `validate.ts` are measured without a rebuild. */ @@ -25,7 +25,6 @@ import { linkSpec } from './helpers/define-stack-fixture.js'; const HERE = resolve(fileURLToPath(import.meta.url), '..'); const CLI = resolve(HERE, '../bin/run-dev.js'); -const TSX = resolve(HERE, '../../../node_modules/.bin/tsx'); const DOCUMENT = { openapi: '3.0.3', @@ -72,7 +71,7 @@ afterAll(() => { function run(command: 'build' | 'validate', cwd: string): Promise<{ code: number; stdout: string; stderr: string }> { return new Promise((done) => { execFile( - TSX, + process.execPath, [CLI, command, '--json'], { cwd, maxBuffer: 16 * 1024 * 1024, env: childEnv({ NO_COLOR: '1' }) }, (err, stdout, stderr) => diff --git a/packages/cli/test/build-demo-staff-artifact.test.ts b/packages/cli/test/build-demo-staff-artifact.test.ts index d9f92fbec24..c8920080e6b 100644 --- a/packages/cli/test/build-demo-staff-artifact.test.ts +++ b/packages/cli/test/build-demo-staff-artifact.test.ts @@ -23,7 +23,7 @@ * `functions` entry runs, so a green here is "this one key was left out", not * "the build wrote nothing". * - * Runs the CLI from source (`bin/run-dev.js` through tsx), as the other build + * Runs the CLI from source (`bin/run-dev.js` under `node`), as the other build * suites do, so `compile.ts` is measured without a rebuild. */ @@ -39,7 +39,6 @@ import { linkSpec } from './helpers/define-stack-fixture.js'; const HERE = resolve(fileURLToPath(import.meta.url), '..'); const CLI = resolve(HERE, '../bin/run-dev.js'); -const TSX = resolve(HERE, '../../../node_modules/.bin/tsx'); const RUNTIME_MODULE = /^objectstack-runtime\.[a-f0-9]+\.mjs$/; const STAFF_EMAIL = 'counsel1@legal.example'; @@ -79,7 +78,7 @@ afterAll(() => { function build(cwd: string): Promise<{ code: number; stdout: string; stderr: string }> { return new Promise((done) => { execFile( - TSX, + process.execPath, [CLI, 'build', '--json'], { cwd, maxBuffer: 16 * 1024 * 1024, env: childEnv({ NO_COLOR: '1' }) }, (err, stdout, stderr) => diff --git a/packages/cli/test/build-docs-step-count.e2e.test.ts b/packages/cli/test/build-docs-step-count.e2e.test.ts index 20e076a9f8c..50766bf3227 100644 --- a/packages/cli/test/build-docs-step-count.e2e.test.ts +++ b/packages/cli/test/build-docs-step-count.e2e.test.ts @@ -44,7 +44,7 @@ * and the `--json` face must stay one JSON document, so the machine face is * asserted to parse and to carry no step text at all. * - * ALTITUDE: this spawns the CLI (`bin/run-dev.js` through tsx) rather than + * ALTITUDE: this spawns the CLI (`bin/run-dev.js` under `node`) rather than * calling a helper, because the defect is in the ORDER of two statements inside * the command body — there is no seam below the process that can observe it. * Same spawn shape and the same `childEnv()` as @@ -62,7 +62,6 @@ import { linkSpec } from './helpers/define-stack-fixture.js'; const HERE = resolve(fileURLToPath(import.meta.url), '..'); const CLI = resolve(HERE, '../bin/run-dev.js'); -const TSX = resolve(HERE, '../../../node_modules/.bin/tsx'); interface Run { code: number; @@ -73,7 +72,7 @@ interface Run { function runCli(args: string[], cwd: string): Promise { return new Promise((resolvePromise) => { execFile( - TSX, + process.execPath, [CLI, ...args], { cwd, maxBuffer: 16 * 1024 * 1024, env: childEnv({ NO_COLOR: '1' }) }, (err, stdout, stderr) => { diff --git a/packages/cli/test/build-runtime-bundle-bodyless.test.ts b/packages/cli/test/build-runtime-bundle-bodyless.test.ts index 783b4f25240..a322cf34a18 100644 --- a/packages/cli/test/build-runtime-bundle-bodyless.test.ts +++ b/packages/cli/test/build-runtime-bundle-bodyless.test.ts @@ -28,7 +28,7 @@ * `functions` entry resolving to a callable that runs through the runtime's own * artifact loader (`loadArtifactBundle`, which merges the module's `functions`). * - * The CLI runs through `bin/run-dev.js` (source, via tsx). Its dependencies, + * The CLI runs through `bin/run-dev.js` (source, under `node`). Its dependencies, * `@objectstack/runtime` included, resolve through `exports` to `dist/`. */ @@ -44,8 +44,7 @@ import { linkSpec } from './helpers/define-stack-fixture.js'; const HERE = resolve(fileURLToPath(import.meta.url), '..'); const CLI = resolve(HERE, '../bin/run-dev.js'); -const TSX = resolve(HERE, '../../../node_modules/.bin/tsx'); -/** A cold `tsx` spawn of the CLI source entry runs well past vitest's 5 s default. */ +/** A cold spawn of the CLI source entry runs well past vitest's 5 s default. */ const SPAWN_TIMEOUT_MS = 120_000; interface Run { @@ -57,7 +56,7 @@ interface Run { function runCli(args: string[], cwd: string): Promise { return new Promise((resolvePromise) => { execFile( - TSX, + process.execPath, [CLI, ...args], { cwd, maxBuffer: 16 * 1024 * 1024, env: childEnv({ NO_COLOR: '1' }) }, (err, stdout, stderr) => { diff --git a/packages/cli/test/build-seed-files-artifact.e2e.test.ts b/packages/cli/test/build-seed-files-artifact.e2e.test.ts index 1ae2d104720..dd11b39376e 100644 --- a/packages/cli/test/build-seed-files-artifact.e2e.test.ts +++ b/packages/cli/test/build-seed-files-artifact.e2e.test.ts @@ -11,7 +11,7 @@ * and SHA-256 under `files`, keyed by the path. The control for the refusal is * the same stack with the file present, which both commands accept. * - * Runs the CLI from source (`bin/run-dev.js` through tsx), as the other build + * Runs the CLI from source (`bin/run-dev.js` under `node`), as the other build * suites do, so `compile.ts` and `validate.ts` are measured without a rebuild. */ @@ -28,7 +28,6 @@ import { linkSpec } from './helpers/define-stack-fixture.js'; const HERE = resolve(fileURLToPath(import.meta.url), '..'); const CLI = resolve(HERE, '../bin/run-dev.js'); -const TSX = resolve(HERE, '../../../node_modules/.bin/tsx'); const PDF = '%PDF-1.7 a seeded contract'; @@ -76,7 +75,7 @@ afterAll(() => { function run(command: 'build' | 'validate', cwd: string): Promise<{ code: number; stdout: string; stderr: string }> { return new Promise((done) => { execFile( - TSX, + process.execPath, [CLI, command, '--json'], { cwd, maxBuffer: 16 * 1024 * 1024, env: childEnv({ NO_COLOR: '1' }) }, (err, stdout, stderr) => diff --git a/packages/cli/test/build-strict-body-count.test.ts b/packages/cli/test/build-strict-body-count.test.ts index 2b768d07cb6..91a34e44157 100644 --- a/packages/cli/test/build-strict-body-count.test.ts +++ b/packages/cli/test/build-strict-body-count.test.ts @@ -21,7 +21,7 @@ * This file pins the door: the header's number, and the `--json` list, on the * real command. * - * The CLI runs through `bin/run-dev.js` (source, via tsx). Its dependencies + * The CLI runs through `bin/run-dev.js` (source, under `node`). Its dependencies * resolve through `exports` to `dist/`. */ @@ -36,8 +36,7 @@ import { defineStackSourceFromLiteral, linkSpec } from './helpers/define-stack-f const HERE = resolve(fileURLToPath(import.meta.url), '..'); const CLI = resolve(HERE, '../bin/run-dev.js'); -const TSX = resolve(HERE, '../../../node_modules/.bin/tsx'); -/** A cold `tsx` spawn of the CLI source entry runs well past vitest's 5 s default. */ +/** A cold spawn of the CLI source entry runs well past vitest's 5 s default. */ const SPAWN_TIMEOUT_MS = 120_000; interface Run { @@ -49,7 +48,7 @@ interface Run { function runCli(args: string[], cwd: string): Promise { return new Promise((resolvePromise) => { execFile( - TSX, + process.execPath, [CLI, ...args], { cwd, maxBuffer: 16 * 1024 * 1024, env: childEnv({ NO_COLOR: '1' }) }, (err, stdout, stderr) => { diff --git a/packages/cli/test/build-view-container-name.test.ts b/packages/cli/test/build-view-container-name.test.ts index 6e17e8d1122..c3108d117da 100644 --- a/packages/cli/test/build-view-container-name.test.ts +++ b/packages/cli/test/build-view-container-name.test.ts @@ -26,7 +26,7 @@ * actually throws for the same payload, driven through `ObjectQL.registerApp`, * rather than a literal copy of the words. * - * The CLI runs through `bin/run-dev.js` (source, via tsx); its dependencies — + * The CLI runs through `bin/run-dev.js` (source, under `node`); its dependencies — * `@objectstack/objectql` among them — resolve through `exports` to `dist/`. */ @@ -42,8 +42,7 @@ import { defineStackSource, linkSpec } from './helpers/define-stack-fixture.js'; const HERE = resolve(fileURLToPath(import.meta.url), '..'); const CLI = resolve(HERE, '../bin/run-dev.js'); -const TSX = resolve(HERE, '../../../node_modules/.bin/tsx'); -/** A cold `tsx` spawn of the CLI source entry runs well past vitest's 5 s default. */ +/** A cold spawn of the CLI source entry runs well past vitest's 5 s default. */ const SPAWN_TIMEOUT_MS = 120_000; /** `os build`'s default `--output`, relative to the working directory. */ const ARTIFACT = join('dist', 'objectstack.json'); @@ -57,7 +56,7 @@ interface Run { function runCli(args: string[], cwd: string): Promise { return new Promise((resolvePromise) => { execFile( - TSX, + process.execPath, [CLI, ...args], { cwd, maxBuffer: 16 * 1024 * 1024, env: childEnv({ NO_COLOR: '1' }) }, (err, stdout, stderr) => { diff --git a/packages/cli/test/create-refuses-invalid-project-name.e2e.test.ts b/packages/cli/test/create-refuses-invalid-project-name.e2e.test.ts index e41a1bf7b27..9e6fd1208cf 100644 --- a/packages/cli/test/create-refuses-invalid-project-name.e2e.test.ts +++ b/packages/cli/test/create-refuses-invalid-project-name.e2e.test.ts @@ -44,7 +44,7 @@ * length rule into the shared validator would break `init` for a name npm * accepts; this test is what says so. * - * Spawned through `bin/run-dev.js` + tsx, so the suite does not depend on + * Spawned through `bin/run-dev.js` + `node`, so the suite does not depend on * `packages/cli/dist` having been built — `@objectstack/cli#test` depends on * `^build` only (the reason `invocation-loudness.e2e.test.ts` spawns that way). */ @@ -67,9 +67,8 @@ import { NPM_PACKAGE_NAME_MAX_LENGTH, validateProjectName } from '../src/command const HERE = resolve(fileURLToPath(import.meta.url), '..'); const CLI = resolve(HERE, '../bin/run-dev.js'); -const TSX = resolve(HERE, '../../../node_modules/.bin/tsx'); -/** oclif + tsx cold start with every command module loaded; ~2-10 s when healthy. */ +/** oclif cold start with every command module loaded; ~2-10 s when healthy. */ const RUN_TIMEOUT_MS = 180_000; /** The card's input: a capital and a space, both npm-invalid. */ @@ -93,7 +92,7 @@ interface Run { function runCli(args: string[], cwd: string): Promise { return new Promise((resolvePromise) => { execFile( - TSX, + process.execPath, [CLI, ...args], { cwd, maxBuffer: 8 * 1024 * 1024, env: childEnv({ NO_COLOR: '1' }) }, (err, stdout, stderr) => { diff --git a/packages/cli/test/dev-no-watch.pin.test.ts b/packages/cli/test/dev-no-watch.pin.test.ts index 164623ac315..a37be762c49 100644 --- a/packages/cli/test/dev-no-watch.pin.test.ts +++ b/packages/cli/test/dev-no-watch.pin.test.ts @@ -58,7 +58,6 @@ import { childEnv } from './helpers/serve-process.js'; const HERE = resolve(fileURLToPath(import.meta.url), '..'); const CLI = resolve(HERE, '../bin/run-dev.js'); -const TSX = resolve(HERE, '../../../node_modules/.bin/tsx'); const PACKAGE_ROOT = resolve(HERE, '..'); @@ -108,7 +107,7 @@ function derivePackageManagerPin(): string { return `pnpm@${answer}`; } -/** oclif + tsx cold start, with every command module loaded; ~2-10 s when healthy. */ +/** oclif cold start, with every command module loaded; ~2-10 s when healthy. */ const RUN_TIMEOUT_MS = 180_000; /** What the fixture workspace's own `dev` script prints — proof it ran. */ @@ -134,7 +133,7 @@ interface Run { function runDev(args: string[], cwd: string): Promise { return new Promise((resolvePromise) => { execFile( - TSX, + process.execPath, [CLI, 'dev', ...args], { cwd, maxBuffer: 8 * 1024 * 1024, env: childEnv({ NO_COLOR: '1' }) }, (err, stdout, stderr) => { @@ -236,7 +235,7 @@ beforeAll(async () => { })}\n`, ); - // Sequential on purpose: three cold tsx starts, each loading every command + // Sequential on purpose: three cold CLI starts, each loading every command // module, in a container several agents share. watchEqualsFalse = await runDev(['--watch=false'], projectDir); noWatchAtWorkspace = await runDev(['--no-watch'], workspaceDir); diff --git a/packages/cli/test/generate-agent-retired.e2e.test.ts b/packages/cli/test/generate-agent-retired.e2e.test.ts index 90a209aa91d..02a0f0ce32e 100644 --- a/packages/cli/test/generate-agent-retired.e2e.test.ts +++ b/packages/cli/test/generate-agent-retired.e2e.test.ts @@ -22,7 +22,7 @@ * `invocation-loudness.e2e.test.ts` documents at length: `process.exitCode` * inside a vitest worker is not an exit status (a CI script judges this * command by `$?`), and these commands print through `utils/format.ts`, whose - * `printError` writes to stdout. Spawned through `bin/run-dev.js` + tsx so the + * `printError` writes to stdout. Spawned through `bin/run-dev.js` + `node` so the * suite does not depend on `packages/cli/dist` having been built. * * The refusal's pointer at skills is asserted here only as TEXT. That the @@ -41,9 +41,8 @@ import { childEnv } from './helpers/serve-process.js'; const HERE = resolve(fileURLToPath(import.meta.url), '..'); const CLI = resolve(HERE, '../bin/run-dev.js'); -const TSX = resolve(HERE, '../../../node_modules/.bin/tsx'); -/** oclif + tsx cold start, with every command module loaded; ~2-10 s when healthy. */ +/** oclif cold start, with every command module loaded; ~2-10 s when healthy. */ const RUN_TIMEOUT_MS = 180_000; interface Run { @@ -52,10 +51,10 @@ interface Run { stderr: string; } -function runTsx(args: string[], cwd: string): Promise { +function runCli(args: string[], cwd: string): Promise { return new Promise((resolvePromise) => { execFile( - TSX, + process.execPath, args, { cwd, maxBuffer: 8 * 1024 * 1024, env: childEnv({ NO_COLOR: '1' }) }, (err, stdout, stderr) => { @@ -83,11 +82,11 @@ let survivor: Run; beforeAll(async () => { dir = mkdtempSync(join(tmpdir(), 'os-g-agent-retired-')); - // Sequential on purpose: three cold tsx starts, each loading every command + // Sequential on purpose: three cold CLI starts, each loading every command // module, in a container several agents share. - retired = await runTsx([CLI, 'g', 'agent', 'support'], dir); - unknown = await runTsx([CLI, 'g', 'nonexistent-type', 'support'], dir); - survivor = await runTsx([CLI, 'g', 'object', 'customer', '--dry-run'], dir); + retired = await runCli([CLI, 'g', 'agent', 'support'], dir); + unknown = await runCli([CLI, 'g', 'nonexistent-type', 'support'], dir); + survivor = await runCli([CLI, 'g', 'object', 'customer', '--dry-run'], dir); }, RUN_TIMEOUT_MS); afterAll(() => { diff --git a/packages/cli/test/generate-object-namespace-prefix.e2e.test.ts b/packages/cli/test/generate-object-namespace-prefix.e2e.test.ts index 797fc4d1032..141e493bb47 100644 --- a/packages/cli/test/generate-object-namespace-prefix.e2e.test.ts +++ b/packages/cli/test/generate-object-namespace-prefix.e2e.test.ts @@ -27,7 +27,7 @@ * * Projects live under this package's `node_modules`, so a config's * `@objectstack/spec` import resolves to the workspace copy without an - * install. Spawned through `bin/run-dev.js` + tsx, so the suite does not + * install. Spawned through `bin/run-dev.js` + `node`, so the suite does not * depend on `packages/cli/dist`. Commands print through `utils/format.ts`, * which writes to stdout, so stdout is what is read. */ @@ -41,9 +41,8 @@ import { childEnv } from './helpers/serve-process.js'; const HERE = dirname(fileURLToPath(import.meta.url)); const CLI = resolve(HERE, '../bin/run-dev.js'); -const TSX = resolve(HERE, '../../../node_modules/.bin/tsx'); -/** oclif + tsx cold starts, eight of them, sequential. */ +/** oclif cold starts, eight of them, sequential. */ const RUN_TIMEOUT_MS = 300_000; interface Run { @@ -55,7 +54,7 @@ interface Run { function runCli(args: string[], cwd: string): Promise { return new Promise((resolvePromise) => { execFile( - TSX, + process.execPath, [CLI, ...args], { cwd, maxBuffer: 8 * 1024 * 1024, env: childEnv({ NO_COLOR: '1' }) }, (err, stdout, stderr) => { @@ -106,7 +105,7 @@ beforeAll(async () => { read(join(dirs.broken, 'src', 'objects', 'index.ts')) + "export { default as legacy } from './legacy.object';\n", ); - // Sequential on purpose: cold tsx starts in a container several agents share. + // Sequential on purpose: cold CLI starts in a container several agents share. runs.generate = await runCli(['g', 'object', 'order_line'], dirs.namespaced); runs.prefixed = await runCli(['g', 'object', 'my_app_invoice'], dirs.namespaced); runs.legacyForm = await runCli(['g', 'object', 'order__line'], dirs.namespaced); diff --git a/packages/cli/test/generate-refuses-name-outside-charset.test.ts b/packages/cli/test/generate-refuses-name-outside-charset.test.ts index 4b9b3a21436..da8bfb62d4f 100644 --- a/packages/cli/test/generate-refuses-name-outside-charset.test.ts +++ b/packages/cli/test/generate-refuses-name-outside-charset.test.ts @@ -47,7 +47,7 @@ * class here is an EXIT CODE plus bytes on disk, `process.exitCode` inside a * vitest worker is not an exit status, and these commands print through * `utils/format.ts`, whose `printError` writes to stdout. Spawned through - * `bin/run-dev.js` + tsx so the suite does not depend on `packages/cli/dist`. + * `bin/run-dev.js` + `node` so the suite does not depend on `packages/cli/dist`. * * ## ⛔ Why this file is NOT named `.e2e` * @@ -73,9 +73,8 @@ import { childEnv } from './helpers/serve-process.js'; const HERE = resolve(fileURLToPath(import.meta.url), '..'); const CLI = resolve(HERE, '../bin/run-dev.js'); -const TSX = resolve(HERE, '../../../node_modules/.bin/tsx'); -/** oclif + tsx cold start, with every command module loaded; ~2-10 s when healthy. */ +/** oclif cold start, with every command module loaded; ~2-10 s when healthy. */ const RUN_TIMEOUT_MS = 240_000; interface Run { @@ -84,10 +83,10 @@ interface Run { stderr: string; } -function runTsx(args: string[], cwd: string): Promise { +function runCli(args: string[], cwd: string): Promise { return new Promise((resolvePromise) => { execFile( - TSX, + process.execPath, args, { cwd, maxBuffer: 8 * 1024 * 1024, env: childEnv({ NO_COLOR: '1' }) }, (err, stdout, stderr) => { @@ -160,12 +159,12 @@ beforeAll(async () => { backstopDir = mkdtempSync(join(tmpdir(), 'os-g-charset-backstop-')); controlDir = mkdtempSync(join(tmpdir(), 'os-g-charset-control-')); - // Sequential on purpose: cold tsx starts, each loading every command module, + // Sequential on purpose: cold CLI starts, each loading every command module, // in a container several agents share. - gated = await runTsx([CLI, 'generate', 'object', 'order-line'], gatedDir); - dryRun = await runTsx([CLI, 'generate', 'flow', 'lead-qual', '--dry-run'], dryRunDir); - backstop = await runTsx([CLI, 'generate', 'object', 'class'], backstopDir); - control = await runTsx([CLI, 'generate', 'object', 'order_line'], controlDir); + gated = await runCli([CLI, 'generate', 'object', 'order-line'], gatedDir); + dryRun = await runCli([CLI, 'generate', 'flow', 'lead-qual', '--dry-run'], dryRunDir); + backstop = await runCli([CLI, 'generate', 'object', 'class'], backstopDir); + control = await runCli([CLI, 'generate', 'object', 'order_line'], controlDir); }, RUN_TIMEOUT_MS); afterAll(() => { diff --git a/packages/cli/test/generate-refuses-unimportable-alias.test.ts b/packages/cli/test/generate-refuses-unimportable-alias.test.ts index 1ad674778ac..4d3486fed37 100644 --- a/packages/cli/test/generate-refuses-unimportable-alias.test.ts +++ b/packages/cli/test/generate-refuses-unimportable-alias.test.ts @@ -16,7 +16,7 @@ * here are on a real child process and on stdout, for the same two reasons * `invocation-loudness.e2e.test.ts` documents at length — these commands print * through `utils/format.ts`, whose `printError` writes to stdout. Spawned - * through `bin/run-dev.js` + tsx so the suite does not depend on + * through `bin/run-dev.js` + `node` so the suite does not depend on * `packages/cli/dist` having been built. * * ## ⛔ Why this file is NOT named `.e2e` @@ -66,9 +66,8 @@ import { childEnv } from './helpers/serve-process.js'; const HERE = resolve(fileURLToPath(import.meta.url), '..'); const CLI = resolve(HERE, '../bin/run-dev.js'); -const TSX = resolve(HERE, '../../../node_modules/.bin/tsx'); -/** oclif + tsx cold start, with every command module loaded; ~2-10 s when healthy. */ +/** oclif cold start, with every command module loaded; ~2-10 s when healthy. */ const RUN_TIMEOUT_MS = 240_000; interface Run { @@ -77,10 +76,10 @@ interface Run { stderr: string; } -function runTsx(args: string[], cwd: string): Promise { +function runCli(args: string[], cwd: string): Promise { return new Promise((resolvePromise) => { execFile( - TSX, + process.execPath, args, { cwd, maxBuffer: 8 * 1024 * 1024, env: childEnv({ NO_COLOR: '1' }) }, (err, stdout, stderr) => { @@ -125,7 +124,7 @@ beforeAll(async () => { controlDir = mkdtempSync(join(tmpdir(), 'os-g-alias-control-')); contextualDir = mkdtempSync(join(tmpdir(), 'os-g-contextual-')); - // Sequential on purpose: cold tsx starts, each loading every command module, + // Sequential on purpose: cold CLI starts, each loading every command module, // in a container several agents share. // // The card's measured row was `os g view class`. `view` suffixes its `const` @@ -134,17 +133,17 @@ beforeAll(async () => { // the project's stack must declare, so in these config-less directories it // is refused before this layer; `dashboard` suffixes its binding the same // way (`classDashboard`) and binds nothing, so it reaches this layer alone. - reserved = await runTsx([CLI, 'generate', 'dashboard', 'class'], reservedDir); + reserved = await runCli([CLI, 'generate', 'dashboard', 'class'], reservedDir); // A second generator, to show the refusal is not one patched call site — // and `let`, which is reserved only because a module is in strict mode. // (`skill`, not `flow`, for the same #21325 reason as above.) - strictReserved = await runTsx([CLI, 'generate', 'skill', 'let'], strictDir); - dryRun = await runTsx([CLI, 'generate', 'dashboard', 'class', '--dry-run'], dryRunDir); + strictReserved = await runCli([CLI, 'generate', 'skill', 'let'], strictDir); + dryRun = await runCli([CLI, 'generate', 'dashboard', 'class', '--dry-run'], dryRunDir); // The layer in front, on the name it owns. - parseLayer = await runTsx([CLI, 'generate', 'object', 'class'], parseLayerDir); - charsetLayer = await runTsx([CLI, 'generate', 'object', 'order-line'], charsetLayerDir); - control = await runTsx([CLI, 'generate', 'dashboard', 'order_line'], controlDir); - contextual = await runTsx([CLI, 'generate', 'dashboard', 'type'], contextualDir); + parseLayer = await runCli([CLI, 'generate', 'object', 'class'], parseLayerDir); + charsetLayer = await runCli([CLI, 'generate', 'object', 'order-line'], charsetLayerDir); + control = await runCli([CLI, 'generate', 'dashboard', 'order_line'], controlDir); + contextual = await runCli([CLI, 'generate', 'dashboard', 'type'], contextualDir); }, RUN_TIMEOUT_MS); afterAll(() => { diff --git a/packages/cli/test/generate-refuses-unparseable-name.test.ts b/packages/cli/test/generate-refuses-unparseable-name.test.ts index 2edb2bff459..39bf4f7735a 100644 --- a/packages/cli/test/generate-refuses-unparseable-name.test.ts +++ b/packages/cli/test/generate-refuses-unparseable-name.test.ts @@ -16,7 +16,7 @@ * assertions here are on a real child process and on stdout, for the same two * reasons `invocation-loudness.e2e.test.ts` documents at length — these * commands print through `utils/format.ts`, whose `printError` writes to - * stdout. Spawned through `bin/run-dev.js` + tsx so the suite does not depend + * stdout. Spawned through `bin/run-dev.js` + `node` so the suite does not depend * on `packages/cli/dist` having been built. * * ## ⛔ Why this file is NOT named `.e2e` @@ -85,9 +85,8 @@ import { childEnv } from './helpers/serve-process.js'; const HERE = resolve(fileURLToPath(import.meta.url), '..'); const CLI = resolve(HERE, '../bin/run-dev.js'); -const TSX = resolve(HERE, '../../../node_modules/.bin/tsx'); -/** oclif + tsx cold start, with every command module loaded; ~2-10 s when healthy. */ +/** oclif cold start, with every command module loaded; ~2-10 s when healthy. */ const RUN_TIMEOUT_MS = 240_000; interface Run { @@ -96,10 +95,10 @@ interface Run { stderr: string; } -function runTsx(args: string[], cwd: string): Promise { +function runCli(args: string[], cwd: string): Promise { return new Promise((resolvePromise) => { execFile( - TSX, + process.execPath, args, { cwd, maxBuffer: 8 * 1024 * 1024, env: childEnv({ NO_COLOR: '1' }) }, (err, stdout, stderr) => { @@ -155,16 +154,16 @@ beforeAll(async () => { dryRunDir = mkdtempSync(join(tmpdir(), 'os-g-dryrun-')); unparseableDir = mkdtempSync(join(tmpdir(), 'os-g-unparseable-')); - // Sequential on purpose: cold tsx starts, each loading every command module, + // Sequential on purpose: cold CLI starts, each loading every command module, // in a container several agents share. - refused = await runTsx([CLI, 'generate', 'object', 'foo.bar'], refusedDir); + refused = await runCli([CLI, 'generate', 'object', 'foo.bar'], refusedDir); // A second generator, to show the refusal is not one patched call site. - refusedAgain = await runTsx([CLI, 'generate', 'flow', 'foo.bar'], refusedDir); - dryRun = await runTsx([CLI, 'generate', 'object', 'foo.bar', '--dry-run'], dryRunDir); + refusedAgain = await runCli([CLI, 'generate', 'flow', 'foo.bar'], refusedDir); + dryRun = await runCli([CLI, 'generate', 'object', 'foo.bar', '--dry-run'], dryRunDir); // Inside the charset, outside the grammar — the name that reaches THIS // check now that a charset gate stands in front of it (#16726). - unparseable = await runTsx([CLI, 'generate', 'object', 'class'], unparseableDir); - control = await runTsx([CLI, 'generate', 'object', 'order_line'], controlDir); + unparseable = await runCli([CLI, 'generate', 'object', 'class'], unparseableDir); + control = await runCli([CLI, 'generate', 'object', 'order_line'], controlDir); }, RUN_TIMEOUT_MS); afterAll(() => { diff --git a/packages/cli/test/generate-skill.e2e.test.ts b/packages/cli/test/generate-skill.e2e.test.ts index c7469292823..9e6d0db4359 100644 --- a/packages/cli/test/generate-skill.e2e.test.ts +++ b/packages/cli/test/generate-skill.e2e.test.ts @@ -60,8 +60,10 @@ * Assertions run against a REAL CHILD PROCESS and read stdout, for the two * reasons `generate-agent-retired.e2e.test.ts` documents: `process.exitCode` * inside a vitest worker is not an exit status, and these commands print - * through `utils/format.ts`, which writes to stdout. Spawned through - * `bin/run-dev.js` + tsx, so the suite does not depend on `packages/cli/dist`. + * through `utils/format.ts`, which writes to stdout. The CLI is spawned as + * `bin/run-dev.js` under this process's own `node`, so the suite does not + * depend on `packages/cli/dist`; only the parse probe, a bare `.ts` file, runs + * through the tsx binary. */ import { describe, it, expect, beforeAll, afterAll } from 'vitest'; @@ -86,6 +88,7 @@ import { childEnv } from './helpers/serve-process.js'; const HERE = resolve(fileURLToPath(import.meta.url), '..'); const CLI = resolve(HERE, '../bin/run-dev.js'); +/** Runs the parse probe only: a bare `.ts` module, which plain `node` cannot import. */ const TSX = resolve(HERE, '../../../node_modules/.bin/tsx'); /** @@ -96,7 +99,7 @@ const TSX = resolve(HERE, '../../../node_modules/.bin/tsx'); */ const SPEC_PACKAGE_ROOT = dirname(createRequire(import.meta.url).resolve('@objectstack/spec/package.json')); -/** oclif + tsx cold start, with every command module loaded; ~2-10 s when healthy. */ +/** oclif cold start, with every command module loaded; ~2-10 s when healthy. */ const RUN_TIMEOUT_MS = 180_000; /** The contract under test, read from the registry — never restated. */ @@ -111,10 +114,10 @@ interface Run { stderr: string; } -function runTsx(args: string[], cwd: string): Promise { +function runChild(bin: string, args: string[], cwd: string): Promise { return new Promise((resolvePromise) => { execFile( - TSX, + bin, args, { cwd, maxBuffer: 8 * 1024 * 1024, env: childEnv({ NO_COLOR: '1' }) }, (err, stdout, stderr) => { @@ -161,13 +164,13 @@ beforeAll(async () => { specLink = join(dir, 'node_modules', '@objectstack', 'spec'); symlinkSync(SPEC_PACKAGE_ROOT, specLink, 'dir'); - // Sequential on purpose: cold tsx starts, each loading every command module, + // Sequential on purpose: cold CLI starts, each loading every command module, // in a container several agents share. // `lead_qualification`, not `lead-qualification`: the #16726 charset gate // refuses kebab-case at the door. Both spellings derive the same written // file and the same barrel alias, so what this file measures is unchanged. - generated = await runTsx([CLI, 'g', 'skill', 'lead_qualification'], dir); - control = await runTsx([CLI, 'g', 'object', 'customer'], dir); + generated = await runChild(process.execPath, [CLI, 'g', 'skill', 'lead_qualification'], dir); + control = await runChild(process.execPath, [CLI, 'g', 'object', 'customer'], dir); const skillDir = join(dir, 'src', 'skills'); const produced = existsSync(skillDir) @@ -188,7 +191,7 @@ beforeAll(async () => { `import skill from './src/skills/${writtenName}';\n` + 'process.stdout.write(JSON.stringify(skill));\n', ); - parseProbe = await runTsx([probe], dir); + parseProbe = await runChild(TSX, [probe], dir); } }, RUN_TIMEOUT_MS); diff --git a/packages/cli/test/hook-body-build-reach.e2e.test.ts b/packages/cli/test/hook-body-build-reach.e2e.test.ts index f57be754efa..9a3fdd044bf 100644 --- a/packages/cli/test/hook-body-build-reach.e2e.test.ts +++ b/packages/cli/test/hook-body-build-reach.e2e.test.ts @@ -18,7 +18,7 @@ * A unit test over the extractor alone would restate exactly the false * confidence this card is about. The only way to establish what the real * authoring path does is to run it, so every test here spawns the actual CLI - * (`bin/run-dev.js` + tsx against a `mkdtemp` project — the + * (`bin/run-dev.js` + `node` against a `mkdtemp` project — the * `validate-top-level-strict.e2e.test.ts` pattern) and reads the artifact the * shell was left holding. * @@ -56,7 +56,6 @@ import { linkSpec } from './helpers/define-stack-fixture.js'; const HERE = resolve(fileURLToPath(import.meta.url), '..'); const CLI = resolve(HERE, '../bin/run-dev.js'); -const TSX = resolve(HERE, '../../../node_modules/.bin/tsx'); interface Run { code: number; @@ -67,7 +66,7 @@ interface Run { function runCli(args: string[], cwd: string): Promise { return new Promise((resolvePromise) => { execFile( - TSX, + process.execPath, [CLI, ...args], { cwd, maxBuffer: 16 * 1024 * 1024, env: childEnv({ NO_COLOR: '1' }) }, (err, stdout, stderr) => { diff --git a/packages/cli/test/migrate-apply-refuses-before-ddl.e2e.test.ts b/packages/cli/test/migrate-apply-refuses-before-ddl.e2e.test.ts index 2f9d37e9100..b834e33df9e 100644 --- a/packages/cli/test/migrate-apply-refuses-before-ddl.e2e.test.ts +++ b/packages/cli/test/migrate-apply-refuses-before-ddl.e2e.test.ts @@ -52,7 +52,7 @@ * * Same reason as `migrate-unloadable-host-config-exit.e2e.test.ts`: the claim * is about a command an operator runs, and `process.exitCode` set inside a - * vitest worker is not an exit status. Spawned through `bin/run-dev.js` + tsx, + * vitest worker is not an exit status. Spawned through `bin/run-dev.js` + `node`, * so the suite does not depend on `packages/cli/dist` having been built. */ @@ -62,7 +62,7 @@ import { existsSync, mkdtempSync, rmSync, writeFileSync } from 'node:fs'; import { tmpdir } from 'node:os'; import { join } from 'node:path'; import { SqlDriver } from '@objectstack/driver-sql'; -import { CLI, TSX, childEnv } from './helpers/serve-process.js'; +import { CLI, childEnv } from './helpers/serve-process.js'; import { NO_DDL_EXECUTED_NOTICE } from '../src/utils/schema-migration-plugins.js'; /** @@ -75,7 +75,7 @@ import { NO_DDL_EXECUTED_NOTICE } from '../src/utils/schema-migration-plugins.js */ const REQUIRED_VAR = 'OS_E2E_13118_SECRET'; -/** Generous: a cold tsx compile of the command tree dominates a ~1 s apply. */ +/** Generous: a cold load of the command tree dominates a ~1 s apply. */ const RUN_BUDGET_MS = 120_000; interface Run { @@ -87,7 +87,7 @@ interface Run { function runCli(args: string[], cwd: string): Promise { return new Promise((resolvePromise) => { execFile( - TSX, + process.execPath, [CLI, ...args], { cwd, diff --git a/packages/cli/test/migrate-exit-code.e2e.test.ts b/packages/cli/test/migrate-exit-code.e2e.test.ts index 4ce585d5994..6705a57d0b4 100644 --- a/packages/cli/test/migrate-exit-code.e2e.test.ts +++ b/packages/cli/test/migrate-exit-code.e2e.test.ts @@ -25,7 +25,7 @@ * It has to be a real child process. `process.exitCode` set inside a vitest * worker is not an exit status — the number the defect produced only exists * once Node has exited and the kernel has masked it to `& 0xFF`. Spawned - * through `bin/run-dev.js` + tsx (the pattern `migrate-meta.e2e.test.ts` and + * through `bin/run-dev.js` + `node` (the pattern `migrate-meta.e2e.test.ts` and * `emit-json-pipe.test.ts` already use) so the suite does not depend on * `packages/cli/dist` having been built. */ @@ -40,7 +40,6 @@ import { childEnv } from './helpers/serve-process.js'; const HERE = resolve(fileURLToPath(import.meta.url), '..'); const CLI = resolve(HERE, '../bin/run-dev.js'); -const TSX = resolve(HERE, '../../../node_modules/.bin/tsx'); /** Minimal project: the command boots a stack, it does not need much of one. */ const CONFIG = ` @@ -79,7 +78,7 @@ function runCli(args: string[], cwd: string, env: Record = {}): const started = Date.now(); return new Promise((resolvePromise) => { execFile( - TSX, + process.execPath, [CLI, ...args], { cwd, maxBuffer: 16 * 1024 * 1024, env: childEnv({ NO_COLOR: '1', ...env }) }, (err, stdout, stderr) => { @@ -189,7 +188,7 @@ describe('os migrate recorded-by --json — a successful run exits 0 (#4873)', ( }); it('still exits promptly — #4813 stays fixed', () => { - // Wall time of the whole child, tsx compile included. + // Wall time of the whole child, command-tree load included. for (const run of runs) expect(run.wallMs).toBeLessThan(SLOW_RUN_MS); }); }); diff --git a/packages/cli/test/migrate-unloadable-host-config-exit.e2e.test.ts b/packages/cli/test/migrate-unloadable-host-config-exit.e2e.test.ts index 3fddcd804ac..443f60a3560 100644 --- a/packages/cli/test/migrate-unloadable-host-config-exit.e2e.test.ts +++ b/packages/cli/test/migrate-unloadable-host-config-exit.e2e.test.ts @@ -27,7 +27,7 @@ * worker is not one — only a process that has actually exited has a status for * a shell, a `set -e` script or a container entrypoint to read. That audience * is the entire point of the ruling, and it is the one an in-process assertion - * cannot stand in for. Spawned through `bin/run-dev.js` + tsx, the pattern + * cannot stand in for. Spawned through `bin/run-dev.js` + `node`, the pattern * `migrate-exit-code.e2e.test.ts` and `migrate-plan-exits.e2e.test.ts` already * use, so the suite does not depend on `packages/cli/dist` having been built. * @@ -47,7 +47,7 @@ import { execFile } from 'node:child_process'; import { mkdtempSync, rmSync, writeFileSync } from 'node:fs'; import { tmpdir } from 'node:os'; import { join } from 'node:path'; -import { CLI, TSX, childEnv } from './helpers/serve-process.js'; +import { CLI, childEnv } from './helpers/serve-process.js'; /** * The environment variable the unloadable fixture demands. @@ -58,7 +58,7 @@ import { CLI, TSX, childEnv } from './helpers/serve-process.js'; */ const REQUIRED_VAR = 'OS_E2E_12953_SECRET'; -/** Generous: a cold tsx compile of the command tree dominates a ~1 s plan. */ +/** Generous: a cold load of the command tree dominates a ~1 s plan. */ const RUN_BUDGET_MS = 120_000; interface Run { @@ -70,7 +70,7 @@ interface Run { function runCli(args: string[], cwd: string): Promise { return new Promise((resolvePromise) => { execFile( - TSX, + process.execPath, [CLI, ...args], { cwd, diff --git a/packages/cli/test/package-install-local-handlers.integration.test.ts b/packages/cli/test/package-install-local-handlers.integration.test.ts index b1785631333..1c8b25e3f50 100644 --- a/packages/cli/test/package-install-local-handlers.integration.test.ts +++ b/packages/cli/test/package-install-local-handlers.integration.test.ts @@ -39,8 +39,9 @@ * * ## Spawn shape * - * The tsx source entry (`bin/run-dev.js`), as every `runServe()` caller spawns - * it, for `os start` and for `os package install` alike. It pins + * The source entry (`bin/run-dev.js`), as every `runServe()` caller spawns it + * but under this process's own `node` rather than the tsx binary, for + * `os start` and for `os package install` alike. It pins * `NODE_ENV=development`, where a taken port is a hop rather than an exit, so * each boot takes a kernel-assigned port and reads it back off its own ready * banner (`readKernelAssignedPort`) before any request is addressed to it. The CLI runs @@ -64,7 +65,6 @@ import { KERNEL_ASSIGNED_PORT, probeThroughChild, readKernelAssignedPort, - TSX, } from './helpers/serve-process.js'; /** The banner's tail — every row above it has printed. */ @@ -76,7 +76,7 @@ const OBJECT = 'tasks_app_task'; /** * The development dev-admin seed (`objectstack dev`'s documented, loginable * admin — `admin@objectos.ai` / `admin123`, promoted to platform admin): the - * tsx entry runs `os start` in development, which seeds it on every boot that + * source entry runs `os start` in development, which seeds it on every boot that * finds no login, so it is the operator on all three boots and holds the * `manage_metadata` capability the install route demands. */ @@ -144,7 +144,7 @@ interface LiveStart { function bootStart(cwd: string, home: string, extra: string[] = []): Promise { return new Promise((resolveBoot, rejectBoot) => { - const child = spawn(TSX, [CLI, 'start', '-p', KERNEL_ASSIGNED_PORT, '--home', home, '--auth-secret', E2E_SECRET_KEY, '--no-ui', ...extra], { + const child = spawn(process.execPath, [CLI, 'start', '-p', KERNEL_ASSIGNED_PORT, '--home', home, '--auth-secret', E2E_SECRET_KEY, '--no-ui', ...extra], { cwd, // `childEnv`, never a bare `...process.env` — see its header. env: childEnv({ NO_COLOR: '1', OS_CLOUD_URL: 'off', OS_LOG_LEVEL: 'warn', OS_SECRET_KEY: E2E_SECRET_KEY }), @@ -247,7 +247,7 @@ async function authenticate(live: LiveStart): Promise { */ function packageInstall(appDir: string, live: LiveStart): Promise<{ exit: number | null; output: string }> { return new Promise((done) => { - const child = spawn(TSX, [CLI, 'package', 'install', './dist/objectstack.json', '--runtime', live.base, '--email', EMAIL, '--password', PASSWORD], { + const child = spawn(process.execPath, [CLI, 'package', 'install', './dist/objectstack.json', '--runtime', live.base, '--email', EMAIL, '--password', PASSWORD], { cwd: appDir, env: childEnv({ NO_COLOR: '1' }), stdio: ['ignore', 'pipe', 'pipe'], diff --git a/packages/cli/test/qa-names-and-tags-run.test.ts b/packages/cli/test/qa-names-and-tags-run.test.ts index 3b5680d78f9..9f6137897f8 100644 --- a/packages/cli/test/qa-names-and-tags-run.test.ts +++ b/packages/cli/test/qa-names-and-tags-run.test.ts @@ -17,7 +17,7 @@ * out — not run, and not merely hidden from the report. * * A real child process, because `process.exit(1)` inside a vitest worker is - * not an exit status. Spawned through `bin/run-dev.js` + tsx so the suite does + * not an exit status. Spawned through `bin/run-dev.js` + `node` so the suite does * not depend on `packages/cli/dist`. The target is a stub `node:http` server * in this process: the runner's HTTP adapter is what is exercised, and no * ObjectStack server is booted. @@ -30,9 +30,9 @@ import type { AddressInfo } from 'node:net'; import { mkdtempSync, mkdirSync, rmSync, writeFileSync } from 'node:fs'; import { tmpdir } from 'node:os'; import { join } from 'node:path'; -import { CLI, TSX, childEnv } from './helpers/serve-process.js'; +import { CLI, childEnv } from './helpers/serve-process.js'; -/** oclif + tsx cold start; a healthy run here is a few seconds. */ +/** oclif cold start; a healthy run here is a few seconds. */ const RUN_TIMEOUT_MS = 120_000; interface Run { @@ -44,7 +44,7 @@ interface Run { function runCli(args: string[], cwd: string): Promise { return new Promise((resolvePromise) => { execFile( - TSX, + process.execPath, [CLI, ...args], { cwd, maxBuffer: 8 * 1024 * 1024, env: childEnv({ NO_COLOR: '1' }) }, (err, stdout, stderr) => { diff --git a/packages/cli/test/qa-requires-skip-run.test.ts b/packages/cli/test/qa-requires-skip-run.test.ts index 2d3c06c6b5c..606d5006907 100644 --- a/packages/cli/test/qa-requires-skip-run.test.ts +++ b/packages/cli/test/qa-requires-skip-run.test.ts @@ -21,7 +21,7 @@ * hidden from the report. * * A real child process, because `process.exit(1)` inside a vitest worker is - * not an exit status. Spawned through `bin/run-dev.js` + tsx so the suite does + * not an exit status. Spawned through `bin/run-dev.js` + `node` so the suite does * not depend on `packages/cli/dist`. The target is a stub `node:http` server * in this process that answers discovery the way both real producers do; no * ObjectStack server is booted. @@ -34,9 +34,9 @@ import type { AddressInfo } from 'node:net'; import { mkdtempSync, mkdirSync, rmSync, writeFileSync } from 'node:fs'; import { tmpdir } from 'node:os'; import { join } from 'node:path'; -import { CLI, TSX, childEnv } from './helpers/serve-process.js'; +import { CLI, childEnv } from './helpers/serve-process.js'; -/** oclif + tsx cold start; a healthy run here is a few seconds. */ +/** oclif cold start; a healthy run here is a few seconds. */ const RUN_TIMEOUT_MS = 120_000; /** The variable the `params`-gated scenario needs — test-only, so `OS_TEST_*`. */ @@ -51,7 +51,7 @@ interface Run { function runCli(args: string[], cwd: string, env: Record = {}): Promise { return new Promise((resolvePromise) => { execFile( - TSX, + process.execPath, [CLI, ...args], // The variable is REMOVED unless the run sets it, so an operator's shell // cannot decide what the unset-variable run measures. diff --git a/packages/cli/test/refusal-renders-once.e2e.test.ts b/packages/cli/test/refusal-renders-once.e2e.test.ts index 8feebdb7f68..12c574dbb92 100644 --- a/packages/cli/test/refusal-renders-once.e2e.test.ts +++ b/packages/cli/test/refusal-renders-once.e2e.test.ts @@ -21,7 +21,7 @@ * * ## What each case asserts * - * The CLI is SPAWNED — `bin/run-dev.js` through tsx, the source entry that + * The CLI is SPAWNED — `bin/run-dev.js` under `node`, the source entry that * shares the published entry's `handle()` and `flush()` — because the second * rendering is made by the entry point AFTER `run()` has thrown, so no * in-process run can see it (`exit-signal.pin.test.ts` says as much). For each @@ -61,7 +61,7 @@ * ## Tier * * Nightly by name, `integration` by behaviour (`vitest-tiers.ts`): each case is - * a spawn. Every spawn is paid in `beforeAll`, sequentially — ten tsx starts at + * a spawn. Every spawn is paid in `beforeAll`, sequentially — ten CLI starts at * once is the load that turns a shared box's verdicts into timeouts — and no * case is clocked. */ @@ -77,7 +77,6 @@ import { linkSpec } from './helpers/define-stack-fixture.js'; const HERE = resolve(fileURLToPath(import.meta.url), '..'); const CLI = resolve(HERE, '../bin/run-dev.js'); -const TSX = resolve(HERE, '../../../node_modules/.bin/tsx'); interface Run { code: number; @@ -88,7 +87,7 @@ interface Run { function runCli(argv: string[], cwd: string, env: Record = {}): Promise { return new Promise((resolvePromise) => { execFile( - TSX, + process.execPath, [CLI, ...argv], { cwd, maxBuffer: 16 * 1024 * 1024, env: childEnv({ NO_COLOR: '1', ...env }) }, (err, stdout, stderr) => { diff --git a/packages/cli/test/retry-policy-key-validate-door.test.ts b/packages/cli/test/retry-policy-key-validate-door.test.ts index 491369bb356..9433b7ff76d 100644 --- a/packages/cli/test/retry-policy-key-validate-door.test.ts +++ b/packages/cli/test/retry-policy-key-validate-door.test.ts @@ -21,7 +21,7 @@ * Integration tier: it spawns the source CLI (`packages/cli/vitest-tiers.ts`). * Not named `.e2e`, so it runs on every PR and in the merge queue rather than * nightly; two spawns, one per fixture, keep that cost to the floor. The CLI - * runs through `bin/run-dev.js` (source, via tsx), and `@objectstack/spec` + * runs through `bin/run-dev.js` (source, under `node`), and `@objectstack/spec` * resolves through `exports` to `dist/`. */ @@ -36,7 +36,6 @@ import { writeDefineStackConfig } from './helpers/define-stack-fixture.js'; const HERE = resolve(fileURLToPath(import.meta.url), '..'); const CLI = resolve(HERE, '../bin/run-dev.js'); -const TSX = resolve(HERE, '../../../node_modules/.bin/tsx'); type Retry = Record; @@ -109,7 +108,7 @@ interface Run { function runCli(args: string[], cwd: string): Promise { return new Promise((resolvePromise) => { execFile( - TSX, + process.execPath, [CLI, ...args], { cwd, maxBuffer: 16 * 1024 * 1024, env: childEnv({ NO_COLOR: '1' }) }, (err, stdout, stderr) => { diff --git a/packages/cli/test/rule-line-explain-pointer.e2e.test.ts b/packages/cli/test/rule-line-explain-pointer.e2e.test.ts index c44bebc9fc4..f4cafd240b5 100644 --- a/packages/cli/test/rule-line-explain-pointer.e2e.test.ts +++ b/packages/cli/test/rule-line-explain-pointer.e2e.test.ts @@ -14,7 +14,7 @@ * untouched (the `⚠` line itself is unchanged in kind, so * `validate-json-warning-parity.e2e.test.ts` still pairs it). * - * Spawns the real CLI from source (`bin/run-dev.js` + tsx, the + * Spawns the real CLI from source (`bin/run-dev.js` + `node`, the * `validate-json-warning-parity.e2e.test.ts` pattern): what is pinned is the * text two real commands print, not a function's return value. The in-process * pins for the helper and for `os explain ` are in @@ -33,7 +33,6 @@ import { linkSpec } from './helpers/define-stack-fixture.js'; const HERE = resolve(fileURLToPath(import.meta.url), '..'); const CLI = resolve(HERE, '../bin/run-dev.js'); -const TSX = resolve(HERE, '../../../node_modules/.bin/tsx'); /** The tutorial shape: a ticket whose long-text `description` nothing displays. */ const SOURCE = ` @@ -64,7 +63,7 @@ const EXPECTED = [ function runCli(args: string[], cwd: string): Promise<{ code: number; stdout: string; stderr: string }> { return new Promise((done) => { execFile( - TSX, + process.execPath, [CLI, ...args], { cwd, maxBuffer: 16 * 1024 * 1024, env: childEnv({ NO_COLOR: '1' }) }, (err, stdout, stderr) => { diff --git a/packages/cli/test/scaffold-emission-policy.e2e.test.ts b/packages/cli/test/scaffold-emission-policy.e2e.test.ts index bc21399499e..9b1ae129135 100644 --- a/packages/cli/test/scaffold-emission-policy.e2e.test.ts +++ b/packages/cli/test/scaffold-emission-policy.e2e.test.ts @@ -62,7 +62,7 @@ * and would stay green through a build that stopped copying templates at all. * Its `dist/` is present because `@objectstack/cli#test` depends on `^build`. * - * Spawned through `bin/run-dev.js` + tsx, so this suite does not depend on + * Spawned through `bin/run-dev.js` + `node`, so this suite does not depend on * `packages/cli/dist` having been built (`@objectstack/cli#test` depends on * `^build` only) — the same reason `create-refuses-invalid-project-name.e2e.test.ts` * spawns that way. @@ -91,7 +91,6 @@ import { DEFAULT_PLACEMENT, templates } from '../src/commands/create.js'; const HERE = resolve(fileURLToPath(import.meta.url), '..'); const CLI = resolve(HERE, '../bin/run-dev.js'); -const TSX = resolve(HERE, '../../../node_modules/.bin/tsx'); /** * The on-ramp's real entry point and the template it ships, both declared as @@ -111,7 +110,7 @@ const ON_RAMP_TEMPLATE_PKG = resolve(HERE, '../../..', 'packages/create-objectst const GETTING_STARTED = resolve(HERE, '../../..', 'content/docs/getting-started/index.mdx'); const TROUBLESHOOTING = resolve(HERE, '../../..', 'content/docs/deployment/troubleshooting.mdx'); -/** oclif + tsx cold start with every command module loaded; ~2-10 s when healthy. */ +/** oclif cold start with every command module loaded; ~2-10 s when healthy. */ const RUN_TIMEOUT_MS = 180_000; const PROBE_NAME = 'emission-policy-probe'; @@ -257,7 +256,7 @@ describe('the emitted tsconfig.json comes from the shared renderer', () => { function runCli(args: string[], cwd: string): Promise<{ code: number; stderr: string }> { return new Promise((done) => { execFile( - TSX, + process.execPath, [CLI, ...args], { cwd, maxBuffer: 8 * 1024 * 1024, env: childEnv({ NO_COLOR: '1' }) }, (err, _stdout, stderr) => { diff --git a/packages/cli/test/serve-host-config-metadata-door.integration.test.ts b/packages/cli/test/serve-host-config-metadata-door.integration.test.ts index 4c7e226de65..34d2fab7e40 100644 --- a/packages/cli/test/serve-host-config-metadata-door.integration.test.ts +++ b/packages/cli/test/serve-host-config-metadata-door.integration.test.ts @@ -34,8 +34,9 @@ * * ## Spawn shape, and the production posture it does not reach * - * The tsx source entry (`bin/run-dev.js`), the harness the other boot-level - * files here use. That entry runs under `NODE_ENV=development`, so both legs + * The source entry (`bin/run-dev.js`), the harness the other boot-level + * files here use, under this process's own `node` rather than the tsx binary. + * That entry runs under `NODE_ENV=development`, so both legs * boot the DEVELOPMENT posture. The host-config door is decided by the same * three facts on both postures (no artifact door, a host config, no * `MetadataPlugin` in the composition), and the source leg is the branch @@ -61,7 +62,6 @@ import { KERNEL_ASSIGNED_PORT, probeThroughChild, readKernelAssignedPort, - TSX, } from './helpers/serve-process.js'; import { defineStackSourceFromLiteral, linkSpec } from './helpers/define-stack-fixture.js'; @@ -119,7 +119,7 @@ const readings: Partial> = {}; function boot(argv: string[], env: Record): Promise { return new Promise((resolveBoot, rejectBoot) => { - const child = spawn(TSX, [CLI, ...argv, '-p', KERNEL_ASSIGNED_PORT], { + const child = spawn(process.execPath, [CLI, ...argv, '-p', KERNEL_ASSIGNED_PORT], { cwd: dir, // `childEnv`, never a bare `...process.env` — see its header. Neither // artifact variable leaks in from the runner: each leg states its own. diff --git a/packages/cli/test/stack-datasource-default-validate-door.test.ts b/packages/cli/test/stack-datasource-default-validate-door.test.ts index 17a5bef3fd7..3dcf13d4d46 100644 --- a/packages/cli/test/stack-datasource-default-validate-door.test.ts +++ b/packages/cli/test/stack-datasource-default-validate-door.test.ts @@ -19,7 +19,7 @@ * * Integration tier: it spawns the source CLI (`packages/cli/vitest-tiers.ts`). * Not named `.e2e`, so it runs on every PR and in the merge queue; two spawns, - * one per fixture. The CLI runs through `bin/run-dev.js` (source, via tsx), and + * one per fixture. The CLI runs through `bin/run-dev.js` (source, under `node`), and * `@objectstack/spec` resolves through `exports` to `dist/`. */ @@ -34,7 +34,6 @@ import { writeDefineStackConfig } from './helpers/define-stack-fixture.js'; const HERE = resolve(fileURLToPath(import.meta.url), '..'); const CLI = resolve(HERE, '../bin/run-dev.js'); -const TSX = resolve(HERE, '../../../node_modules/.bin/tsx'); /** One stack: one object, and one declared sqlite datasource under `name`. */ const stackOf = (name: string) => ({ @@ -74,7 +73,7 @@ interface Run { function runCli(args: string[], cwd: string): Promise { return new Promise((resolvePromise) => { execFile( - TSX, + process.execPath, [CLI, ...args], { cwd, maxBuffer: 16 * 1024 * 1024, env: childEnv({ NO_COLOR: '1' }) }, (err, stdout, stderr) => { diff --git a/packages/cli/test/validate-field-predicate-traversal.test.ts b/packages/cli/test/validate-field-predicate-traversal.test.ts index b927de8dc09..dd9e37eb132 100644 --- a/packages/cli/test/validate-field-predicate-traversal.test.ts +++ b/packages/cli/test/validate-field-predicate-traversal.test.ts @@ -30,7 +30,7 @@ * * The exit status is what a CI step reads, and it only exists once Node has * exited — the same reason `src/commands/validate-json-strict-exit.e2e.test.ts` - * gives. Spawned through `bin/run-dev.js` + tsx, so the suite does not depend on + * gives. Spawned through `bin/run-dev.js` + `node`, so the suite does not depend on * `packages/cli/dist`; it lands in the INTEGRATION project (`vitest-tiers.ts`). * ⛔ Deliberately NOT named `*.e2e.test.ts`: that name selects the NIGHTLY run, * and this pin belongs to the queue's. @@ -47,7 +47,6 @@ import { linkSpec } from './helpers/define-stack-fixture.js'; const HERE = resolve(fileURLToPath(import.meta.url), '..'); const CLI = resolve(HERE, '../bin/run-dev.js'); -const TSX = resolve(HERE, '../../../node_modules/.bin/tsx'); const objects = (orderFields: string, orderExtra = '') => ` import { defineStack } from '@objectstack/spec'; @@ -125,7 +124,7 @@ interface Run { function runValidate(cwd: string): Promise { return new Promise((resolvePromise) => { execFile( - TSX, + process.execPath, [CLI, 'validate', '--json'], // `childEnv()`, never a bulk `process.env` copy: the vitest worker's // `TEST` / `VITEST*` family must not reach the child (check:cli-test-child-env). diff --git a/packages/cli/test/validate-json-warning-parity.e2e.test.ts b/packages/cli/test/validate-json-warning-parity.e2e.test.ts index a26936cfcc3..31f66f3cfce 100644 --- a/packages/cli/test/validate-json-warning-parity.e2e.test.ts +++ b/packages/cli/test/validate-json-warning-parity.e2e.test.ts @@ -52,7 +52,7 @@ * stay exactly equal. * * Spawns the real CLI (the `validate-top-level-strict.e2e.test.ts` pattern: - * `bin/run-dev.js` + tsx, no dependency on `packages/cli/dist`) because the + * `bin/run-dev.js` + `node`, no dependency on `packages/cli/dist`) because the * divergence being pinned is between two real invocations, not two functions. */ @@ -67,7 +67,6 @@ import { linkSpec } from './helpers/define-stack-fixture.js'; const HERE = resolve(fileURLToPath(import.meta.url), '..'); const CLI = resolve(HERE, '../bin/run-dev.js'); -const TSX = resolve(HERE, '../../../node_modules/.bin/tsx'); interface Fixture { /** Directory name / test label. */ @@ -158,7 +157,7 @@ interface Run { function runCli(args: string[], cwd: string): Promise { return new Promise((resolvePromise) => { execFile( - TSX, + process.execPath, [CLI, ...args], { cwd, maxBuffer: 16 * 1024 * 1024, env: childEnv({ NO_COLOR: '1' }) }, (err, stdout, stderr) => { diff --git a/packages/cli/test/validate-top-level-strict.e2e.test.ts b/packages/cli/test/validate-top-level-strict.e2e.test.ts index 3f847d8b1e9..d3215efe9d7 100644 --- a/packages/cli/test/validate-top-level-strict.e2e.test.ts +++ b/packages/cli/test/validate-top-level-strict.e2e.test.ts @@ -16,7 +16,7 @@ * `--strict` at all** (B subsumes Shape A's warning-accounting change). The * exit status is the whole point of the card — it is the only thing a CI * pipeline reads — so this test spawns the real CLI (the - * `migrate-exit-code.e2e.test.ts` pattern: `bin/run-dev.js` + tsx, no + * `migrate-exit-code.e2e.test.ts` pattern: `bin/run-dev.js` + `node`, no * dependency on `packages/cli/dist`) and asserts what the SHELL sees. * * Schema-layer pins (issue code/path, near-miss guidance, curated @@ -35,7 +35,6 @@ import { linkSpec } from './helpers/define-stack-fixture.js'; const HERE = resolve(fileURLToPath(import.meta.url), '..'); const CLI = resolve(HERE, '../bin/run-dev.js'); -const TSX = resolve(HERE, '../../../node_modules/.bin/tsx'); /** The card's failure shape: a valid stack plus ONE stray top-level key. */ const CONFIG_WITH_STRAY_KEY = ` @@ -78,7 +77,7 @@ interface Run { function runCli(args: string[], cwd: string): Promise { return new Promise((resolvePromise) => { execFile( - TSX, + process.execPath, [CLI, ...args], { cwd, maxBuffer: 16 * 1024 * 1024, env: childEnv({ NO_COLOR: '1' }) }, (err, stdout, stderr) => { diff --git a/packages/cli/test/validate-view-container-name.test.ts b/packages/cli/test/validate-view-container-name.test.ts index 15ec45c5b8c..66337632aa7 100644 --- a/packages/cli/test/validate-view-container-name.test.ts +++ b/packages/cli/test/validate-view-container-name.test.ts @@ -25,7 +25,7 @@ * with the payload `AppPlugin` hands it — rather than a literal copy of the * words, which would be a second spelling of them. * - * The CLI runs through `bin/run-dev.js` (source, via tsx); its dependencies — + * The CLI runs through `bin/run-dev.js` (source, under `node`); its dependencies — * `@objectstack/objectql` among them — resolve through `exports` to `dist/`. */ @@ -41,8 +41,7 @@ import { defineStackSource, linkSpec } from './helpers/define-stack-fixture.js'; const HERE = resolve(fileURLToPath(import.meta.url), '..'); const CLI = resolve(HERE, '../bin/run-dev.js'); -const TSX = resolve(HERE, '../../../node_modules/.bin/tsx'); -/** A cold `tsx` spawn of the CLI source entry runs well past vitest's 5 s default. */ +/** A cold spawn of the CLI source entry runs well past vitest's 5 s default. */ const SPAWN_TIMEOUT_MS = 120_000; interface Run { @@ -54,7 +53,7 @@ interface Run { function runCli(args: string[], cwd: string): Promise { return new Promise((resolvePromise) => { execFile( - TSX, + process.execPath, [CLI, ...args], { cwd, maxBuffer: 16 * 1024 * 1024, env: childEnv({ NO_COLOR: '1' }) }, (err, stdout, stderr) => {