관리-도구
편집 파일: index.js
// 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