|
- 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: process.platform === 'win32',
- 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);
- }
|