25개 이상의 토픽을 선택하실 수 없습니다. Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 

97 lines
2.3 KiB

  1. import { spawn } from 'node:child_process';
  2. export function startProcess(command, args, options = {}) {
  3. const child = spawn(command, args, {
  4. cwd: options.cwd,
  5. env: { ...process.env, ...(options.env || {}) },
  6. shell: options.shell ?? false,
  7. stdio: ['ignore', 'pipe', 'pipe'],
  8. });
  9. const output = [];
  10. child.stdout.on('data', (chunk) => output.push(chunk.toString()));
  11. child.stderr.on('data', (chunk) => output.push(chunk.toString()));
  12. child.once('error', (error) => {
  13. output.push(`\nprocess failed to start: ${error.message}\n`);
  14. });
  15. child.once('exit', (code) => {
  16. if (code !== null && code !== 0) {
  17. output.push(`\nprocess exited with code ${code}\n`);
  18. }
  19. });
  20. return { child, output };
  21. }
  22. function hasExited(child) {
  23. return child.exitCode !== null || child.signalCode !== null;
  24. }
  25. function sendSignal(child, signal) {
  26. if (!child || hasExited(child)) return false;
  27. try {
  28. return child.kill(signal);
  29. } catch (error) {
  30. if (error?.code === 'ESRCH' || hasExited(child)) return false;
  31. throw error;
  32. }
  33. }
  34. function waitForExit(child, timeoutMs) {
  35. if (!child || hasExited(child)) return Promise.resolve(true);
  36. return new Promise((resolve) => {
  37. let settled = false;
  38. let timer;
  39. const finish = (exited) => {
  40. if (settled) return;
  41. settled = true;
  42. clearTimeout(timer);
  43. child.off('exit', onExit);
  44. child.off('close', onExit);
  45. resolve(exited);
  46. };
  47. const onExit = () => finish(true);
  48. child.once('exit', onExit);
  49. child.once('close', onExit);
  50. timer = setTimeout(() => finish(false), timeoutMs);
  51. timer.unref();
  52. });
  53. }
  54. function killWindowsProcessTree(pid) {
  55. if (!pid) return Promise.resolve();
  56. return new Promise((resolve) => {
  57. const killer = spawn('taskkill', ['/PID', String(pid), '/T', '/F'], {
  58. stdio: 'ignore',
  59. });
  60. killer.once('error', resolve);
  61. killer.once('exit', resolve);
  62. });
  63. }
  64. export async function stopProcess(child) {
  65. if (!child || hasExited(child)) return;
  66. if (process.platform === 'win32') {
  67. await killWindowsProcessTree(child.pid);
  68. await waitForExit(child, 5000);
  69. return;
  70. }
  71. sendSignal(child, 'SIGTERM');
  72. const exited = await waitForExit(child, 5000);
  73. if (exited) return;
  74. sendSignal(child, 'SIGKILL');
  75. await waitForExit(child, 5000);
  76. }