import { spawn, spawnSync } from 'node:child_process'; const SUPREME_SCAN_TIMEOUT_MS = 30 * 60 * 1000; /** * Runs `sm scan {projectPath}` and streams scanner output in real time. * * The function verifies that the `sm` binary is available via `which` (Unix) * or `where` (Windows), applies a 30 minute timeout, and throws on failures. */ export async function scannerRunner(projectPath: string): Promise { ensureSmExists(); await new Promise((resolve, reject) => { const child = spawn('sm', ['scan', projectPath], { cwd: projectPath, stdio: ['ignore', 'pipe', 'pipe'], windowsHide: true, }); child.stdout.on('data', (chunk: Buffer | string) => { process.stdout.write(chunk); }); child.stderr.on('data', (chunk: Buffer | string) => { process.stderr.write(chunk); }); const timeoutHandle = setTimeout(() => { child.kill('SIGTERM'); setTimeout(() => { if (!child.killed) { child.kill('SIGKILL'); } }, 5_000).unref(); }, SUPREME_SCAN_TIMEOUT_MS); timeoutHandle.unref(); child.on('error', (error) => { clearTimeout(timeoutHandle); reject(new Error(`Failed to start supreme-max scan: ${error.message}`)); }); child.on('close', (code, signal) => { clearTimeout(timeoutHandle); if (signal !== null) { reject( new Error( signal === 'SIGTERM' ? `supreme-max scan timed out after ${SUPREME_SCAN_TIMEOUT_MS / 60_000} minutes` : `supreme-max scan was terminated by signal ${signal}`, ), ); return; } if (code !== 0) { reject(new Error(`supreme-max scan failed with exit code ${String(code)}`)); return; } resolve(); }); }); } function ensureSmExists(): void { const checker = process.platform === 'win32' ? 'where' : 'which'; const result = spawnSync(checker, ['sm'], { stdio: 'ignore', windowsHide: true, }); if (result.status !== 0) { throw new Error( 'supreme-max executable `sm` was not found in PATH. Install it first and retry.', ); } }