From 6c4bd5edfe85de745f90bcab53dc3b4f27e2301f Mon Sep 17 00:00:00 2001 From: Adrian Webb Date: Sat, 10 Oct 2026 03:19:28 -0400 Subject: [PATCH 1/2] test(cli): reproduce empty output when the invocation symlink moves --- tests/contract/package/installed-assets.test.ts | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/tests/contract/package/installed-assets.test.ts b/tests/contract/package/installed-assets.test.ts index 3565bd63..6ac52bb5 100644 --- a/tests/contract/package/installed-assets.test.ts +++ b/tests/contract/package/installed-assets.test.ts @@ -1,7 +1,7 @@ import assert from 'node:assert/strict'; import { execFile } from 'node:child_process'; import { createHash } from 'node:crypto'; -import { existsSync, lstatSync, mkdtempSync, readFileSync, realpathSync, rmSync, writeFileSync } from 'node:fs'; +import { existsSync, lstatSync, mkdtempSync, readFileSync, realpathSync, rmSync, symlinkSync, writeFileSync } from 'node:fs'; import { tmpdir } from 'node:os'; import { resolve } from 'node:path'; import test from 'node:test'; @@ -61,6 +61,15 @@ test('native production CLI archive retains exact observation assets and execute const env: NodeJS.ProcessEnv = { ...process.env, TREESEED_CONFIG_HOME: resolve(root, 'config') }; delete env.NODE_OPTIONS; delete env.NODE_TEST_CONTEXT; const result = await promisify(execFile)(process.execPath, [binary, '--help'], { cwd: root, env, encoding: 'utf8' }); assert.equal(result.stderr, ''); assert.match(result.stdout, /trsd/u); assert.deepEqual(readFileSync(binary), bytes); + const alias = resolve(root, 'trsd'), replacement = resolve(root, 'replacement.ts'), preload = resolve(root, 'switch-entrypoint.mts'); + symlinkSync(binary, alias); writeFileSync(replacement, bytes); + writeFileSync(preload, `import{registerHooks}from'node:module';import{unlinkSync,symlinkSync,writeFileSync}from'node:fs';import{pathToFileURL}from'node:url'; +registerHooks({resolve(specifier,context,next){if(context.parentURL===pathToFileURL(${JSON.stringify(binary)}).href&&specifier==='./runtime.js'){ +unlinkSync(${JSON.stringify(alias)});symlinkSync(${JSON.stringify(replacement)},${JSON.stringify(alias)});writeFileSync(${JSON.stringify(resolve(root,'switched'))},'observed');}return next(specifier,context);}});`); + const switched = await promisify(execFile)(process.execPath, ['--import', preload, alias, '--help'], { cwd: root, env, encoding: 'utf8' }); + assert.equal(readFileSync(resolve(root, 'switched'), 'utf8'), 'observed'); + assert.equal(switched.stderr, ''); assert.match(switched.stdout, /trsd/u, 'The loaded public binary must finish even when its invocation symlink moves'); + assert.deepEqual(readFileSync(binary), bytes); const inventory = async () => (JSON.parse(await execute('npm', ['pack', '--dry-run', '--ignore-scripts', '--json'], installed)) as Packed[])[0]!; const before = await inventory(); for (const path of ['dist/.treeseed-build-complete.json', 'dist/.treeseed-build-complete.json.new']) From 90488e13994ec8be4741ab3b488059210052690f Mon Sep 17 00:00:00 2001 From: Adrian Webb Date: Sat, 10 Oct 2026 03:24:08 -0400 Subject: [PATCH 2/2] fix(cli): execute the loaded public binary through selection changes --- src/cli/main.ts | 6 +----- tests/contract/package/thin-package.test.ts | 9 +++++++++ 2 files changed, 10 insertions(+), 5 deletions(-) diff --git a/src/cli/main.ts b/src/cli/main.ts index dbcb38b4..edd7e397 100644 --- a/src/cli/main.ts +++ b/src/cli/main.ts @@ -1,9 +1,5 @@ #!/usr/bin/env node -import { realpathSync } from 'node:fs'; -import { resolve } from 'node:path'; -import { fileURLToPath } from 'node:url'; import { runCommandLine } from './runtime.js'; -const executable = (path: string) => { try { return realpathSync(path); } catch { return resolve(path); } }; -if (executable(process.argv[1] ?? '') === executable(fileURLToPath(import.meta.url))) process.exitCode = await runCommandLine(process.argv.slice(2)); +process.exitCode = await runCommandLine(process.argv.slice(2)); diff --git a/tests/contract/package/thin-package.test.ts b/tests/contract/package/thin-package.test.ts index 8ec82755..81ba2dc4 100644 --- a/tests/contract/package/thin-package.test.ts +++ b/tests/contract/package/thin-package.test.ts @@ -1,4 +1,5 @@ import assert from 'node:assert/strict'; +import { spawnSync } from 'node:child_process'; import { existsSync, readFileSync, readdirSync } from 'node:fs'; import test from 'node:test'; import { parse } from 'yaml'; @@ -47,6 +48,14 @@ test('the executable drains complete output before exiting', () => { const main = readFileSync('src/cli/main.ts', 'utf8'); assert.equal(main.includes('process.exit('), false); assert.equal(main.includes('process.exitCode = await runCommandLine'), true); + const invoke = (args: string[]) => spawnSync(process.execPath, ['dist/cli/main.js', ...args], + { encoding: 'utf8', timeout: 5_000, maxBuffer: 1024 * 1024 }); + const help = invoke(['--help']); + assert.equal(help.error, undefined); assert.equal(help.signal, null); assert.equal(help.status, 0); + assert.equal(help.stderr, ''); assert.match(help.stdout, /trsd/u); + const denied = invoke(['definitely-invalid-command', '--json']); + assert.equal(denied.error, undefined); assert.equal(denied.signal, null); assert.equal(denied.status, 1); + assert.equal(denied.stdout, ''); assert.equal(JSON.parse(denied.stderr).error.code, 'unknown_command'); }); test('candidate promotion uses staging while stable publication uses production', () => {