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
|