1import { spawn as nodeSpawn, } from 'child_process'; 2import crossSpawn from 'cross-spawn'; 3import { onExit } from 'signal-exit'; 4import { allSignals } from './all-signals.js'; 5import { watchdog } from './watchdog.js'; 6/* c8 ignore start */ 7const spawn = process?.platform === 'win32' ? crossSpawn : nodeSpawn; 8/** 9 * Normalizes the arguments passed to `foregroundChild`. 10 * 11 * Exposed for testing. 12 * 13 * @internal 14 */ 15export const normalizeFgArgs = (fgArgs) => { 16 let [program, args = [], spawnOpts = {}, cleanup = () => { }] = fgArgs; 17 if (typeof args === 'function') { 18 cleanup = args; 19 spawnOpts = {}; 20 args = []; 21 } 22 else if (!!args && typeof args === 'object' && !Array.isArray(args)) { 23 if (typeof spawnOpts === 'function') 24 cleanup = spawnOpts; 25 spawnOpts = args; 26 args = []; 27 } 28 else if (typeof spawnOpts === 'function') { 29 cleanup = spawnOpts; 30 spawnOpts = {}; 31 } 32 if (Array.isArray(program)) { 33 const [pp, ...pa] = program; 34 program = pp; 35 args = pa; 36 } 37 return [program, args, { ...spawnOpts }, cleanup]; 38}; 39export function foregroundChild(...fgArgs) { 40 const [program, args, spawnOpts, cleanup] = normalizeFgArgs(fgArgs); 41 spawnOpts.stdio = [0, 1, 2]; 42 if (process.send) { 43 spawnOpts.stdio.push('ipc'); 44 } 45 const child = spawn(program, args, spawnOpts); 46 const unproxySignals = proxySignals(child); 47 const childHangup = () => { 48 try { 49 child.kill('SIGHUP'); 50 /* c8 ignore start */ 51 } 52 catch (_) { 53 // SIGHUP is weird on windows 54 child.kill('SIGTERM'); 55 } 56 /* c8 ignore stop */ 57 }; 58 const removeOnExit = onExit(childHangup); 59 const dog = watchdog(child); 60 let done = false; 61 child.on('close', async (code, signal) => { 62 dog.kill('SIGKILL'); 63 /* c8 ignore start */ 64 if (done) { 65 return; 66 } 67 /* c8 ignore stop */ 68 done = true; 69 const result = cleanup(code, signal); 70 const res = isPromise(result) ? await result : result; 71 removeOnExit(); 72 unproxySignals(); 73 if (res === false) 74 return; 75 else if (typeof res === 'string') { 76 signal = res; 77 code = null; 78 } 79 else if (typeof res === 'number') { 80 code = res; 81 signal = null; 82 } 83 if (signal) { 84 // If there is nothing else keeping the event loop alive, 85 // then there's a race between a graceful exit and getting 86 // the signal to this process. Put this timeout here to 87 // make sure we're still alive to get the signal, and thus 88 // exit with the intended signal code. 89 /* istanbul ignore next */ 90 setTimeout(() => { }, 2000); 91 try { 92 process.kill(process.pid, signal); 93 /* c8 ignore start */ 94 } 95 catch (_) { 96 process.kill(process.pid, 'SIGTERM'); 97 } 98 /* c8 ignore stop */ 99 } 100 else { 101 process.exit(code || 0); 102 } 103 }); 104 if (process.send) { 105 process.removeAllListeners('message'); 106 child.on('message', (message, sendHandle) => { 107 process.send?.(message, sendHandle); 108 }); 109 process.on('message', (message, sendHandle) => { 110 child.send(message, sendHandle); 111 }); 112 } 113 return child; 114} 115/** 116 * Starts forwarding signals to `child` through `parent`. 117 */ 118const proxySignals = (child) => { 119 const listeners = new Map(); 120 for (const sig of allSignals) { 121 const listener = () => { 122 // some signals can only be received, not sent 123 try { 124 child.kill(sig); 125 /* c8 ignore start */ 126 } 127 catch (_) { } 128 /* c8 ignore stop */ 129 }; 130 try { 131 // if it's a signal this system doesn't recognize, skip it 132 process.on(sig, listener); 133 listeners.set(sig, listener); 134 /* c8 ignore start */ 135 } 136 catch (_) { } 137 /* c8 ignore stop */ 138 } 139 return () => { 140 for (const [sig, listener] of listeners) { 141 process.removeListener(sig, listener); 142 } 143 }; 144}; 145const isPromise = (o) => !!o && typeof o === 'object' && typeof o.then === 'function'; 146//# sourceMappingURL=index.js.map