diff --git a/packages/cli/src/commands/e2e.ts b/packages/cli/src/commands/e2e.ts index a9f749f..f8294eb 100755 --- a/packages/cli/src/commands/e2e.ts +++ b/packages/cli/src/commands/e2e.ts @@ -2,38 +2,36 @@ * E2ECommand - Run end-to-end validation * DD §17. Every gate executes a real check (no file existence or hardcoded outputs). */ -import { execSync } from 'child_process' +import { execFileSync } from 'child_process' import { existsSync } from 'fs' import { join } from 'path' function findBun(): string { - try { return execSync('which bun', { encoding: 'utf-8' }).trim() } catch {} + // Try common paths first (no shell) const candidates = [ + process.env.BUN_INSTALL ? `${process.env.BUN_INSTALL}/bin/bun` : null, join(process.env.HOME || '/root', '.bun', 'bin', 'bun'), '/usr/local/bin/bun', '/usr/bin/bun' - ] + ].filter((p): p is string => Boolean(p)) for (const c of candidates) { if (existsSync(c)) return c } - throw new Error('bun not found — cannot run E2E tests') + // PATH fallback + return 'bun' } function findTsc(): string { - try { return execSync('node_modules/.bin/tsc', { encoding: 'utf-8' }).trim() } catch {} - return './node_modules/.bin/tsc' -} - -function findDepcruise(): string { - try { return execSync('npx --no-install depcruise', { encoding: 'utf-8' }).trim() } catch {} - return 'npx --no-install depcruise' + const local = './node_modules/.bin/tsc' + return existsSync(local) ? local : 'tsc' } /** - * Run a command and return pass/fail + error excerpt. + * Run a command using execFileSync (no shell, no string interpolation). + * Args are passed as an array — safe against command injection. */ function runCmd(label: string, cmd: string, args: string[], cwd?: string, timeoutMs = 180000): { pass: boolean; detail: string } { try { - const output = execSync([cmd, ...args].join(' '), { + execFileSync(cmd, args, { cwd: cwd || process.cwd(), encoding: 'utf-8', stdio: 'pipe', @@ -74,7 +72,7 @@ export function e2eCommand(): void { }}, { label: 'P0: depcruise dependency boundary (INV-4)', fn: () => { try { - execSync('node_modules/.bin/depcruise --config .dependency-cruiser.js packages/*/src/ 2>&1', { + execFileSync('node_modules/.bin/depcruise', ['--config', '.dependency-cruiser.js', 'packages/cli/src/', 'packages/contracts/src/', 'packages/llm/src/', 'packages/runtime/src/', 'packages/toolchain-cpp/src/', 'packages/tui/src/', 'packages/workers/src/'], { encoding: 'utf-8', stdio: 'pipe', timeout: 60000 }) return { pass: true, detail: '✅' } @@ -85,7 +83,7 @@ export function e2eCommand(): void { { label: 'P0: tsc strict typecheck (0 errors)', fn: () => { const tsc = findTsc() try { - execSync(`${tsc} --noEmit -p tsconfig.check.json`, { encoding: 'utf-8', stdio: 'pipe', timeout: 90000 }) + execFileSync(tsc, ['--noEmit', '-p', 'tsconfig.check.json'], { encoding: 'utf-8', stdio: 'pipe', timeout: 90000 }) return { pass: true, detail: '✅' } } catch (err: any) { const stdout = err.stdout || '' diff --git a/packages/runtime/src/workers/WorkerManager.ts b/packages/runtime/src/workers/WorkerManager.ts index b4e4e2a..38442cb 100755 --- a/packages/runtime/src/workers/WorkerManager.ts +++ b/packages/runtime/src/workers/WorkerManager.ts @@ -7,7 +7,8 @@ * @module packages/runtime/src/workers/WorkerManager */ -import { spawn, execSync } from 'child_process' +import { spawn } from 'child_process' +import { existsSync } from 'fs' import type { ChildProcess } from 'child_process' import { WorkerProcess, type WorkerExitCode } from './WorkerProcess.js' import { WorkerProtocol, type WorkerMessage, type WorkerMessageType } from './WorkerProtocol.js' @@ -226,18 +227,17 @@ export class WorkerManager { } private find_bun(): string { - try { - return execSync('which bun', { encoding: 'utf-8' }).trim() - } catch { - // Try common paths - const common = ['/home/airlongdian/.bun/bin/bun', '/usr/local/bin/bun', '/usr/bin/bun'] - for (const path of common) { - try { - execSync(`test -x ${path}`) - return path - } catch { /* */ } - } - return 'bun' + // Try common paths first (no shell, no string interpolation) + const candidates = [ + process.env.BUN_INSTALL ? `${process.env.BUN_INSTALL}/bin/bun` : null, + `${process.env.HOME || '/root'}/.bun/bin/bun`, + '/usr/local/bin/bun', + '/usr/bin/bun', + ].filter((p): p is string => Boolean(p)) + + for (const candidate of candidates) { + if (existsSync(candidate)) return candidate } + return 'bun' // PATH fallback } } diff --git a/packages/toolchain-cpp/src/test/CppTestRunner.ts b/packages/toolchain-cpp/src/test/CppTestRunner.ts index 1a41362..5f0e11d 100755 --- a/packages/toolchain-cpp/src/test/CppTestRunner.ts +++ b/packages/toolchain-cpp/src/test/CppTestRunner.ts @@ -5,7 +5,7 @@ * @module packages/toolchain-cpp/src/test/CppTestRunner */ -import { execSync } from 'child_process' +import { execFileSync } from 'child_process' import { DiagnosticParser } from '../analysis/DiagnosticParser.js' export interface CppTestOutput { @@ -22,7 +22,7 @@ export class CppTestRunner { const start = Date.now() try { - const output = execSync('ctest --output-on-failure', { + const output = execFileSync('ctest', ['--output-on-failure'], { cwd: build_dir, encoding: 'utf-8', stdio: 'pipe'