import { spawn } from 'node:child_process'; import { join } from 'node:path'; /** * Result from running a CLI command. */ export interface RunResult { /** Stdout output with ANSI codes stripped */ stdout: string; /** Stderr output */ stderr: string; /** Process exit code */ exitCode: number; } /** * Build a clean env for spawned CLI processes. * Strips INIT_CWD which npm/npx sets to the runner's directory — without this, * the CLI resolves the working directory from INIT_CWD instead of the spawn's cwd. * @see https://docs.npmjs.com/cli/v10/commands/npm-run-script */ export function cleanSpawnEnv(extraEnv: Record = {}): NodeJS.ProcessEnv { return { ...process.env, INIT_CWD: undefined, ...extraEnv }; } /** * Spawn a command, collect output, and strip ANSI codes. */ export function spawnAndCollect( command: string, args: string[], cwd: string, extraEnv: Record = {} ): Promise { return new Promise(resolve => { const proc = spawn(command, args, { cwd, env: cleanSpawnEnv(extraEnv), }); let stdout = ''; let stderr = ''; proc.stdout.on('data', (data: Buffer) => { stdout += data.toString(); }); proc.stderr.on('data', (data: Buffer) => { stderr += data.toString(); }); proc.on('close', code => { // Strip ANSI escape codes from stdout // eslint-disable-next-line no-control-regex stdout = stdout.replace(/\x1B\[\??\d*[a-zA-Z]/g, '').trim(); resolve({ stdout, stderr, exitCode: code ?? 1 }); }); }); } /** * Get the path to the CLI entry point. * Uses the built bundle - run `npm run build` before tests. */ function getCLIPath(): string { // Navigate from src/test-utils to dist/cli/index.mjs return join(__dirname, '..', '..', 'dist', 'cli', 'index.mjs'); } /** * Run the AgentCore CLI via the local build (unit/integ tests). * Skips dependency installation by default for speed. */ export async function runCLI(args: string[], cwd: string, skipInstall = true): Promise { return spawnAndCollect('node', [getCLIPath(), ...args], cwd, skipInstall ? { AGENTCORE_SKIP_INSTALL: '1' } : {}); }