You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
275 lines
8.9 KiB
275 lines
8.9 KiB
// 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
|