134 lines
3.3 KiB
JavaScript
134 lines
3.3 KiB
JavaScript
const signalExit = require('signal-exit')
|
||
/* istanbul ignore next */
|
||
const spawn = process.platform === 'win32' ? require('cross-spawn') : require('child_process').spawn
|
||
|
||
/**
|
||
* Normalizes the arguments passed to `foregroundChild`.
|
||
*
|
||
* See the signature of `foregroundChild` for the supported arguments.
|
||
*
|
||
* @param fgArgs Array of arguments passed to `foregroundChild`.
|
||
* @return Normalized arguments
|
||
* @internal
|
||
*/
|
||
function normalizeFgArgs(fgArgs) {
|
||
let program, args, cb;
|
||
let processArgsEnd = fgArgs.length;
|
||
const lastFgArg = fgArgs[fgArgs.length - 1];
|
||
if (typeof lastFgArg === "function") {
|
||
cb = lastFgArg;
|
||
processArgsEnd -= 1;
|
||
} else {
|
||
cb = (done) => done();
|
||
}
|
||
|
||
if (Array.isArray(fgArgs[0])) {
|
||
[program, ...args] = fgArgs[0];
|
||
} else {
|
||
program = fgArgs[0];
|
||
args = Array.isArray(fgArgs[1]) ? fgArgs[1] : fgArgs.slice(1, processArgsEnd);
|
||
}
|
||
|
||
return {program, args, cb};
|
||
}
|
||
|
||
/**
|
||
*
|
||
* Signatures:
|
||
* ```
|
||
* (program: string | string[], cb?: CloseHandler);
|
||
* (program: string, args: string[], cb?: CloseHandler);
|
||
* (program: string, ...args: string[], cb?: CloseHandler);
|
||
* ```
|
||
*/
|
||
function foregroundChild (...fgArgs) {
|
||
const {program, args, cb} = normalizeFgArgs(fgArgs);
|
||
|
||
const spawnOpts = { stdio: [0, 1, 2] }
|
||
|
||
if (process.send) {
|
||
spawnOpts.stdio.push('ipc')
|
||
}
|
||
|
||
const child = spawn(program, args, spawnOpts)
|
||
|
||
const unproxySignals = proxySignals(process, child)
|
||
process.on('exit', childHangup)
|
||
function childHangup () {
|
||
child.kill('SIGHUP')
|
||
}
|
||
|
||
child.on('close', (code, signal) => {
|
||
// Allow the callback to inspect the child’s exit code and/or modify it.
|
||
process.exitCode = signal ? 128 + signal : code
|
||
|
||
let done = false;
|
||
const doneCB = () => {
|
||
if (done) {
|
||
return
|
||
}
|
||
|
||
done = true
|
||
unproxySignals()
|
||
process.removeListener('exit', childHangup)
|
||
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(function () {}, 200)
|
||
process.kill(process.pid, signal)
|
||
} else {
|
||
process.exit(process.exitCode)
|
||
}
|
||
};
|
||
|
||
const result = cb(doneCB);
|
||
if (result && result.then) {
|
||
result.then(doneCB);
|
||
}
|
||
})
|
||
|
||
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
|
||
}
|
||
|
||
/**
|
||
* Starts forwarding signals to `child` through `parent`.
|
||
*
|
||
* @param parent Parent process.
|
||
* @param child Child Process.
|
||
* @return `unproxy` function to stop the forwarding.
|
||
* @internal
|
||
*/
|
||
function proxySignals (parent, child) {
|
||
const listeners = new Map()
|
||
|
||
for (const sig of signalExit.signals()) {
|
||
const listener = () => child.kill(sig)
|
||
listeners.set(sig, listener)
|
||
parent.on(sig, listener)
|
||
}
|
||
|
||
return function unproxySignals () {
|
||
for (const [sig, listener] of listeners) {
|
||
parent.removeListener(sig, listener)
|
||
}
|
||
}
|
||
}
|
||
|
||
module.exports = foregroundChild
|