import { spawn } from 'node:child_process'; export function startProcess(command, args, options = {}) { const child = spawn(command, args, { cwd: options.cwd, env: { ...process.env, ...(options.env || {}) }, shell: options.shell ?? false, stdio: ['ignore', 'pipe', 'pipe'], }); const output = []; child.stdout.on('data', (chunk) => output.push(chunk.toString())); child.stderr.on('data', (chunk) => output.push(chunk.toString())); child.once('error', (error) => { output.push(`\nprocess failed to start: ${error.message}\n`); }); child.once('exit', (code) => { if (code !== null && code !== 0) { output.push(`\nprocess exited with code ${code}\n`); } }); return { child, output }; } function hasExited(child) { return child.exitCode !== null || child.signalCode !== null; } function sendSignal(child, signal) { if (!child || hasExited(child)) return false; try { return child.kill(signal); } catch (error) { if (error?.code === 'ESRCH' || hasExited(child)) return false; throw error; } } function waitForExit(child, timeoutMs) { if (!child || hasExited(child)) return Promise.resolve(true); return new Promise((resolve) => { let settled = false; let timer; const finish = (exited) => { if (settled) return; settled = true; clearTimeout(timer); child.off('exit', onExit); child.off('close', onExit); resolve(exited); }; const onExit = () => finish(true); child.once('exit', onExit); child.once('close', onExit); timer = setTimeout(() => finish(false), timeoutMs); timer.unref(); }); } function killWindowsProcessTree(pid) { if (!pid) return Promise.resolve(); return new Promise((resolve) => { const killer = spawn('taskkill', ['/PID', String(pid), '/T', '/F'], { stdio: 'ignore', }); killer.once('error', resolve); killer.once('exit', resolve); }); } export async function stopProcess(child) { if (!child || hasExited(child)) return; if (process.platform === 'win32') { await killWindowsProcessTree(child.pid); await waitForExit(child, 5000); return; } sendSignal(child, 'SIGTERM'); const exited = await waitForExit(child, 5000); if (exited) return; sendSignal(child, 'SIGKILL'); await waitForExit(child, 5000); }