275 lines
8.9 KiB
JavaScript
275 lines
8.9 KiB
JavaScript
// Note: since nyc uses this module to output coverage, any lines
|
|
// that are in the direct sync flow of nyc's outputCoverage are
|
|
// ignored, since we can never get coverage for them.
|
|
// grab a reference to node's real process object right away
|
|
import { signals } from './signals.js';
|
|
export { signals };
|
|
const processOk = (process) => !!process &&
|
|
typeof process === 'object' &&
|
|
typeof process.removeListener === 'function' &&
|
|
typeof process.emit === 'function' &&
|
|
typeof process.reallyExit === 'function' &&
|
|
typeof process.listeners === 'function' &&
|
|
typeof process.kill === 'function' &&
|
|
typeof process.pid === 'number' &&
|
|
typeof process.on === 'function';
|
|
const kExitEmitter = Symbol.for('signal-exit emitter');
|
|
const global = globalThis;
|
|
const ObjectDefineProperty = Object.defineProperty.bind(Object);
|
|
// teeny special purpose ee
|
|
class Emitter {
|
|
emitted = {
|
|
afterExit: false,
|
|
exit: false,
|
|
};
|
|
listeners = {
|
|
afterExit: [],
|
|
exit: [],
|
|
};
|
|
count = 0;
|
|
id = Math.random();
|
|
constructor() {
|
|
if (global[kExitEmitter]) {
|
|
return global[kExitEmitter];
|
|
}
|
|
ObjectDefineProperty(global, kExitEmitter, {
|
|
value: this,
|
|
writable: false,
|
|
enumerable: false,
|
|
configurable: false,
|
|
});
|
|
}
|
|
on(ev, fn) {
|
|
this.listeners[ev].push(fn);
|
|
}
|
|
removeListener(ev, fn) {
|
|
const list = this.listeners[ev];
|
|
const i = list.indexOf(fn);
|
|
/* c8 ignore start */
|
|
if (i === -1) {
|
|
return;
|
|
}
|
|
/* c8 ignore stop */
|
|
if (i === 0 && list.length === 1) {
|
|
list.length = 0;
|
|
}
|
|
else {
|
|
list.splice(i, 1);
|
|
}
|
|
}
|
|
emit(ev, code, signal) {
|
|
if (this.emitted[ev]) {
|
|
return false;
|
|
}
|
|
this.emitted[ev] = true;
|
|
let ret = false;
|
|
for (const fn of this.listeners[ev]) {
|
|
ret = fn(code, signal) === true || ret;
|
|
}
|
|
if (ev === 'exit') {
|
|
ret = this.emit('afterExit', code, signal) || ret;
|
|
}
|
|
return ret;
|
|
}
|
|
}
|
|
class SignalExitBase {
|
|
}
|
|
const signalExitWrap = (handler) => {
|
|
return {
|
|
onExit(cb, opts) {
|
|
return handler.onExit(cb, opts);
|
|
},
|
|
load() {
|
|
return handler.load();
|
|
},
|
|
unload() {
|
|
return handler.unload();
|
|
},
|
|
};
|
|
};
|
|
class SignalExitFallback extends SignalExitBase {
|
|
onExit() {
|
|
return () => { };
|
|
}
|
|
load() { }
|
|
unload() { }
|
|
}
|
|
class SignalExit extends SignalExitBase {
|
|
// "SIGHUP" throws an `ENOSYS` error on Windows,
|
|
// so use a supported signal instead
|
|
/* c8 ignore start */
|
|
#hupSig = process.platform === 'win32' ? 'SIGINT' : 'SIGHUP';
|
|
/* c8 ignore stop */
|
|
#emitter = new Emitter();
|
|
#process;
|
|
#originalProcessEmit;
|
|
#originalProcessReallyExit;
|
|
#sigListeners = {};
|
|
#loaded = false;
|
|
constructor(process) {
|
|
super();
|
|
this.#process = process;
|
|
// { <signal>: <listener fn>, ... }
|
|
this.#sigListeners = {};
|
|
for (const sig of signals) {
|
|
this.#sigListeners[sig] = () => {
|
|
// If there are no other listeners, an exit is coming!
|
|
// Simplest way: remove us and then re-send the signal.
|
|
// We know that this will kill the process, so we can
|
|
// safely emit now.
|
|
const listeners = this.#process.listeners(sig);
|
|
let { count } = this.#emitter;
|
|
// This is a workaround for the fact that signal-exit v3 and signal
|
|
// exit v4 are not aware of each other, and each will attempt to let
|
|
// the other handle it, so neither of them do. To correct this, we
|
|
// detect if we're the only handler *except* for previous versions
|
|
// of signal-exit, and increment by the count of listeners it has
|
|
// created.
|
|
/* c8 ignore start */
|
|
const p = process;
|
|
if (typeof p.__signal_exit_emitter__ === 'object' &&
|
|
typeof p.__signal_exit_emitter__.count === 'number') {
|
|
count += p.__signal_exit_emitter__.count;
|
|
}
|
|
/* c8 ignore stop */
|
|
if (listeners.length === count) {
|
|
this.unload();
|
|
const ret = this.#emitter.emit('exit', null, sig);
|
|
/* c8 ignore start */
|
|
const s = sig === 'SIGHUP' ? this.#hupSig : sig;
|
|
if (!ret)
|
|
process.kill(process.pid, s);
|
|
/* c8 ignore stop */
|
|
}
|
|
};
|
|
}
|
|
this.#originalProcessReallyExit = process.reallyExit;
|
|
this.#originalProcessEmit = process.emit;
|
|
}
|
|
onExit(cb, opts) {
|
|
/* c8 ignore start */
|
|
if (!processOk(this.#process)) {
|
|
return () => { };
|
|
}
|
|
/* c8 ignore stop */
|
|
if (this.#loaded === false) {
|
|
this.load();
|
|
}
|
|
const ev = opts?.alwaysLast ? 'afterExit' : 'exit';
|
|
this.#emitter.on(ev, cb);
|
|
return () => {
|
|
this.#emitter.removeListener(ev, cb);
|
|
if (this.#emitter.listeners['exit'].length === 0 &&
|
|
this.#emitter.listeners['afterExit'].length === 0) {
|
|
this.unload();
|
|
}
|
|
};
|
|
}
|
|
load() {
|
|
if (this.#loaded) {
|
|
return;
|
|
}
|
|
this.#loaded = true;
|
|
// This is the number of onSignalExit's that are in play.
|
|
// It's important so that we can count the correct number of
|
|
// listeners on signals, and don't wait for the other one to
|
|
// handle it instead of us.
|
|
this.#emitter.count += 1;
|
|
for (const sig of signals) {
|
|
try {
|
|
const fn = this.#sigListeners[sig];
|
|
if (fn)
|
|
this.#process.on(sig, fn);
|
|
}
|
|
catch (_) { }
|
|
}
|
|
this.#process.emit = (ev, ...a) => {
|
|
return this.#processEmit(ev, ...a);
|
|
};
|
|
this.#process.reallyExit = (code) => {
|
|
return this.#processReallyExit(code);
|
|
};
|
|
}
|
|
unload() {
|
|
if (!this.#loaded) {
|
|
return;
|
|
}
|
|
this.#loaded = false;
|
|
signals.forEach(sig => {
|
|
const listener = this.#sigListeners[sig];
|
|
/* c8 ignore start */
|
|
if (!listener) {
|
|
throw new Error('Listener not defined for signal: ' + sig);
|
|
}
|
|
/* c8 ignore stop */
|
|
try {
|
|
this.#process.removeListener(sig, listener);
|
|
/* c8 ignore start */
|
|
}
|
|
catch (_) { }
|
|
/* c8 ignore stop */
|
|
});
|
|
this.#process.emit = this.#originalProcessEmit;
|
|
this.#process.reallyExit = this.#originalProcessReallyExit;
|
|
this.#emitter.count -= 1;
|
|
}
|
|
#processReallyExit(code) {
|
|
/* c8 ignore start */
|
|
if (!processOk(this.#process)) {
|
|
return 0;
|
|
}
|
|
this.#process.exitCode = code || 0;
|
|
/* c8 ignore stop */
|
|
this.#emitter.emit('exit', this.#process.exitCode, null);
|
|
return this.#originalProcessReallyExit.call(this.#process, this.#process.exitCode);
|
|
}
|
|
#processEmit(ev, ...args) {
|
|
const og = this.#originalProcessEmit;
|
|
if (ev === 'exit' && processOk(this.#process)) {
|
|
if (typeof args[0] === 'number') {
|
|
this.#process.exitCode = args[0];
|
|
/* c8 ignore start */
|
|
}
|
|
/* c8 ignore start */
|
|
const ret = og.call(this.#process, ev, ...args);
|
|
/* c8 ignore start */
|
|
this.#emitter.emit('exit', this.#process.exitCode, null);
|
|
/* c8 ignore stop */
|
|
return ret;
|
|
}
|
|
else {
|
|
return og.call(this.#process, ev, ...args);
|
|
}
|
|
}
|
|
}
|
|
const process = globalThis.process;
|
|
// wrap so that we call the method on the actual handler, without
|
|
// exporting it directly.
|
|
export const {
|
|
/**
|
|
* Called when the process is exiting, whether via signal, explicit
|
|
* exit, or running out of stuff to do.
|
|
*
|
|
* If the global process object is not suitable for instrumentation,
|
|
* then this will be a no-op.
|
|
*
|
|
* Returns a function that may be used to unload signal-exit.
|
|
*/
|
|
onExit,
|
|
/**
|
|
* Load the listeners. Likely you never need to call this, unless
|
|
* doing a rather deep integration with signal-exit functionality.
|
|
* Mostly exposed for the benefit of testing.
|
|
*
|
|
* @internal
|
|
*/
|
|
load,
|
|
/**
|
|
* Unload the listeners. Likely you never need to call this, unless
|
|
* doing a rather deep integration with signal-exit functionality.
|
|
* Mostly exposed for the benefit of testing.
|
|
*
|
|
* @internal
|
|
*/
|
|
unload, } = signalExitWrap(processOk(process) ? new SignalExit(process) : new SignalExitFallback());
|
|
//# sourceMappingURL=index.js.map
|