UNPKG

foreground-child

Version:

Run a child as if it's the foreground process. Give it stdio. Exit when it exits.

141 lines 4.65 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.normalizeFgArgs = void 0; exports.foregroundChild = foregroundChild; const node_child_process_1 = require("node:child_process"); const signal_exit_1 = require("signal-exit"); const proxy_signals_js_1 = require("./proxy-signals.js"); const watchdog_js_1 = require("./watchdog.js"); /** * Normalizes the arguments passed to `foregroundChild`. * * Exposed for testing. * * @internal */ const normalizeFgArgs = (fgArgs) => { let [program, args = [], spawnOpts = {}, cleanup = () => { }] = fgArgs; if (typeof args === 'function') { cleanup = args; spawnOpts = {}; args = []; } else if (!!args && typeof args === 'object' && !Array.isArray(args)) { if (typeof spawnOpts === 'function') cleanup = spawnOpts; spawnOpts = args; args = []; } else if (typeof spawnOpts === 'function') { cleanup = spawnOpts; spawnOpts = {}; } if (Array.isArray(program)) { const [pp, ...pa] = program; program = pp; args = pa; } return [program, args, { ...spawnOpts }, cleanup]; }; exports.normalizeFgArgs = normalizeFgArgs; function foregroundChild(...fgArgs) { const [program, args, spawnOpts, cleanup] = (0, exports.normalizeFgArgs)(fgArgs); spawnOpts.stdio = [0, 1, 2]; if (process.send) { spawnOpts.stdio.push('ipc'); } const child = (0, node_child_process_1.spawn)(program, args, spawnOpts); const childHangup = () => { try { child.kill('SIGHUP'); /* c8 ignore start */ } catch (_) { // SIGHUP is weird on windows child.kill('SIGTERM'); } /* c8 ignore stop */ }; const removeOnExit = (0, signal_exit_1.onExit)(childHangup); (0, proxy_signals_js_1.proxySignals)(child); const dog = (0, watchdog_js_1.watchdog)(child); dog.on('close', (code, signal) => { if (done) return; /* c8 ignore start * this should be impossible, and is intentionally hidden from the * consumer, thus impossible to test. * However, when the watchdog process dies unexpectedly for some reason, * this causes EVERY test to fail immediately, so we can be reasonably * sure that it's doing its job. */ child.kill('SIGKILL'); throw new Error('foreground-child watchdog process died unexpectedly!', { cause: { pid: dog.pid, code, signal, watchedProcess: { cmd: program, args, pid: child.pid, }, }, }); /* c8 ignore stop */ }); let done = false; child.on('close', async (code, signal) => { /* c8 ignore start */ if (done) return; /* c8 ignore stop */ done = true; const result = cleanup(code, signal, { watchdogPid: dog.pid, }); const res = isPromise(result) ? await result : result; removeOnExit(); if (res === false) return; else if (typeof res === 'string') { signal = res; code = null; } else if (typeof res === 'number') { code = res; signal = null; } if (signal) { // If there is nothing else keeping the event loop alive, // then there's a race between a graceful exit and getting // the signal to this process. Put this timeout here to // make sure we're still alive to get the signal, and thus // exit with the intended signal code. /* istanbul ignore next */ setTimeout(() => { }, 2000); try { process.kill(process.pid, signal); /* c8 ignore start */ } catch (_) { process.kill(process.pid, 'SIGTERM'); } /* c8 ignore stop */ } else { process.exit(code || 0); } }); if (process.send) { process.removeAllListeners('message'); child.on('message', (message, sendHandle) => { process.send?.(message, sendHandle); }); process.on('message', (message, sendHandle) => { child.send(message, sendHandle); }); } return child; } const isPromise = (o) => !!o && typeof o === 'object' && typeof o.then === 'function'; //# sourceMappingURL=index.js.map