import assert from 'node:assert/strict'; import { test } from 'node:test'; import { runWorkspaceKtx } from './run-ktx.mjs'; function freshBuildFs() { return { stat: async (path) => { if (path.endsWith('/.ktx-build-stamp')) { return { mtimeMs: 2000, isDirectory: () => false }; } if (path.endsWith('/packages/cli/dist/bin.js')) { return { mtimeMs: 2000, isDirectory: () => false }; } return { mtimeMs: 1000, isDirectory: () => path.endsWith('/src') || path.endsWith('/packages'), }; }, readdir: async (path) => { if (path.endsWith('/packages')) { return [{ name: 'cli', isDirectory: () => true }]; } if (path.endsWith('/src')) { return [{ name: 'bin.ts', isDirectory: () => false }]; } return []; }, }; } test('runWorkspaceKtx runs the built CLI when it already exists', async () => { const calls = []; const logs = []; const fs = freshBuildFs(); const exitCode = await runWorkspaceKtx(['--version'], { rootDir: '/workspace/ktx', access: async () => undefined, stat: fs.stat, readdir: fs.readdir, execFile: async (command, args, options) => { calls.push({ command, args, cwd: options.cwd }); return { stdout: '@kaelio/ktx 0.0.0-private\n', stderr: '' }; }, stdout: { write: (chunk) => logs.push(['stdout', chunk]) }, stderr: { write: (chunk) => logs.push(['stderr', chunk]) }, }); assert.equal(exitCode, 0); assert.deepEqual(calls, [ { command: process.execPath, args: ['/workspace/ktx/packages/cli/dist/bin.js', '--version'], cwd: '/workspace/ktx', }, ]); assert.deepEqual(logs, [['stdout', '@kaelio/ktx 0.0.0-private\n']]); }); test('runWorkspaceKtx forwards a caller-provided environment to buffered commands', async () => { const calls = []; const fs = freshBuildFs(); const exitCode = await runWorkspaceKtx(['--version'], { rootDir: '/workspace/ktx', access: async () => undefined, stat: fs.stat, readdir: fs.readdir, env: { PATH: '/bin', GIT_CEILING_DIRECTORIES: '/workspace/ktx/examples' }, execFile: async (command, args, options) => { calls.push({ command, args, cwd: options.cwd, env: options.env }); return { stdout: '@kaelio/ktx 0.0.0-private\n', stderr: '' }; }, stdout: { write: () => undefined }, stderr: { write: () => undefined }, }); assert.equal(exitCode, 0); assert.deepEqual(calls, [ { command: process.execPath, args: ['/workspace/ktx/packages/cli/dist/bin.js', '--version'], cwd: '/workspace/ktx', env: { PATH: '/bin', GIT_CEILING_DIRECTORIES: '/workspace/ktx/examples' }, }, ]); }); test('runWorkspaceKtx drops a leading npm argument separator', async () => { const calls = []; const fs = freshBuildFs(); const exitCode = await runWorkspaceKtx(['--', 'connection', 'test', 'warehouse', '--help'], { rootDir: '/workspace/ktx', access: async () => undefined, stat: fs.stat, readdir: fs.readdir, execFile: async (command, args, options) => { calls.push({ command, args, cwd: options.cwd }); return { stdout: 'Usage: ktx connection test\n', stderr: '' }; }, stdout: { write: () => undefined }, stderr: { write: () => undefined }, }); assert.equal(exitCode, 0); assert.deepEqual(calls, [ { command: process.execPath, args: ['/workspace/ktx/packages/cli/dist/bin.js', 'connection', 'test', 'warehouse', '--help'], cwd: '/workspace/ktx', }, ]); }); test('runWorkspaceKtx builds the workspace CLI before running it when dist is missing', async () => { const calls = []; const logs = []; const writes = []; let binExists = false; const exitCode = await runWorkspaceKtx(['setup', 'demo', '--mode', 'replay', '--no-input', '--viz'], { rootDir: '/workspace/ktx', access: async () => { if (!binExists) { throw Object.assign(new Error('missing'), { code: 'ENOENT' }); } }, execFile: async (command, args, options) => { calls.push({ command, args, cwd: options.cwd }); if (command === 'pnpm') { binExists = true; return { stdout: 'build ok\n', stderr: '' }; } return { stdout: 'Replay complete\n', stderr: '' }; }, writeFile: async (path, contents) => { writes.push({ path, contents }); }, stdout: { write: (chunk) => logs.push(['stdout', chunk]) }, stderr: { write: (chunk) => logs.push(['stderr', chunk]) }, }); assert.equal(exitCode, 0); assert.deepEqual( calls.map((call) => [call.command, call.args]), [ ['pnpm', ['run', 'build']], [ process.execPath, ['/workspace/ktx/packages/cli/dist/bin.js', 'setup', 'demo', '--mode', 'replay', '--no-input', '--viz'], ], ], ); assert.deepEqual(logs, [ ['stderr', 'KTX CLI build output is missing. Building it now with `pnpm run build`...\n'], ['stdout', 'build ok\n'], ['stdout', 'Replay complete\n'], ]); assert.deepEqual(writes, [ { path: '/workspace/ktx/packages/cli/dist/.ktx-build-stamp', contents: '' }, ]); }); test('runWorkspaceKtx rebuilds before running when workspace sources are newer than the build stamp', async () => { const calls = []; const logs = []; const writes = []; let sourceMtimeMs = 3000; const exitCode = await runWorkspaceKtx(['status', '--json', '--no-input'], { rootDir: '/workspace/ktx', access: async () => undefined, stat: async (path) => { if (path.endsWith('/.ktx-build-stamp')) { return { mtimeMs: 2000, isDirectory: () => false }; } if (path.endsWith('/packages/cli/dist/bin.js')) { return { mtimeMs: 2000, isDirectory: () => false }; } return { mtimeMs: sourceMtimeMs, isDirectory: () => path.endsWith('/src') || path.endsWith('/packages'), }; }, readdir: async (path) => { if (path.endsWith('/packages')) { return [{ name: 'context', isDirectory: () => true }]; } if (path.endsWith('/src')) { return [{ name: 'scan.ts', isDirectory: () => false }]; } return []; }, execFile: async (command, args, options) => { calls.push({ command, args, cwd: options.cwd }); if (command === 'pnpm') { sourceMtimeMs = 1000; return { stdout: 'build ok\n', stderr: '' }; } return { stdout: '{"status":"ready"}\n', stderr: '' }; }, writeFile: async (path, contents) => { writes.push({ path, contents }); }, stdout: { write: (chunk) => logs.push(['stdout', chunk]) }, stderr: { write: (chunk) => logs.push(['stderr', chunk]) }, }); assert.equal(exitCode, 0); assert.deepEqual( calls.map((call) => [call.command, call.args]), [ ['pnpm', ['run', 'build']], [process.execPath, ['/workspace/ktx/packages/cli/dist/bin.js', 'status', '--json', '--no-input']], ], ); assert.deepEqual(logs, [ ['stderr', 'KTX CLI build output is stale. Rebuilding it now with `pnpm run build`...\n'], ['stdout', 'build ok\n'], ['stdout', '{"status":"ready"}\n'], ]); assert.deepEqual(writes, [ { path: '/workspace/ktx/packages/cli/dist/.ktx-build-stamp', contents: '' }, ]); }); test('runWorkspaceKtx skips rebuild when only bin.js is older than sources but stamp is fresh', async () => { const calls = []; const logs = []; const writes = []; const exitCode = await runWorkspaceKtx(['status'], { rootDir: '/workspace/ktx', access: async () => undefined, stat: async (path) => { if (path.endsWith('/.ktx-build-stamp')) { return { mtimeMs: 5000, isDirectory: () => false }; } if (path.endsWith('/packages/cli/dist/bin.js')) { return { mtimeMs: 1000, isDirectory: () => false }; } return { mtimeMs: 3000, isDirectory: () => path.endsWith('/src') || path.endsWith('/packages'), }; }, readdir: async (path) => { if (path.endsWith('/packages')) { return [{ name: 'cli', isDirectory: () => true }]; } if (path.endsWith('/src')) { return [{ name: 'setup.ts', isDirectory: () => false }]; } return []; }, execFile: async (command, args, options) => { calls.push({ command, args, cwd: options.cwd }); return { stdout: 'KTX status\n', stderr: '' }; }, writeFile: async (path, contents) => { writes.push({ path, contents }); }, stdout: { write: (chunk) => logs.push(['stdout', chunk]) }, stderr: { write: (chunk) => logs.push(['stderr', chunk]) }, }); assert.equal(exitCode, 0); assert.deepEqual( calls.map((call) => [call.command, call.args]), [[process.execPath, ['/workspace/ktx/packages/cli/dist/bin.js', 'status']]], ); assert.deepEqual(writes, []); assert.deepEqual(logs, [['stdout', 'KTX status\n']]); }); test('runWorkspaceKtx rebuilds when stamp is missing even if bin.js exists', async () => { const calls = []; const logs = []; const writes = []; const exitCode = await runWorkspaceKtx(['status'], { rootDir: '/workspace/ktx', access: async () => undefined, stat: async (path) => { if (path.endsWith('/.ktx-build-stamp')) { throw Object.assign(new Error('missing'), { code: 'ENOENT' }); } if (path.endsWith('/packages/cli/dist/bin.js')) { return { mtimeMs: 2000, isDirectory: () => false }; } return { mtimeMs: 1000, isDirectory: () => path.endsWith('/src') || path.endsWith('/packages'), }; }, readdir: async (path) => { if (path.endsWith('/packages')) { return [{ name: 'cli', isDirectory: () => true }]; } if (path.endsWith('/src')) { return [{ name: 'bin.ts', isDirectory: () => false }]; } return []; }, execFile: async (command, args, options) => { calls.push({ command, args, cwd: options.cwd }); if (command === 'pnpm') { return { stdout: 'build ok\n', stderr: '' }; } return { stdout: 'KTX status\n', stderr: '' }; }, writeFile: async (path, contents) => { writes.push({ path, contents }); }, stdout: { write: (chunk) => logs.push(['stdout', chunk]) }, stderr: { write: (chunk) => logs.push(['stderr', chunk]) }, }); assert.equal(exitCode, 0); assert.deepEqual( calls.map((call) => [call.command, call.args]), [ ['pnpm', ['run', 'build']], [process.execPath, ['/workspace/ktx/packages/cli/dist/bin.js', 'status']], ], ); assert.deepEqual(logs[0], [ 'stderr', 'KTX CLI build output is stale. Rebuilding it now with `pnpm run build`...\n', ]); assert.deepEqual(writes, [ { path: '/workspace/ktx/packages/cli/dist/.ktx-build-stamp', contents: '' }, ]); });