• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1// Note: since nyc uses this module to output coverage, any lines
2// that are in the direct sync flow of nyc's outputCoverage are
3// ignored, since we can never get coverage for them.
4// grab a reference to node's real process object right away
5import { signals } from './signals.js';
6export { signals };
7const processOk = (process) => !!process &&
8    typeof process === 'object' &&
9    typeof process.removeListener === 'function' &&
10    typeof process.emit === 'function' &&
11    typeof process.reallyExit === 'function' &&
12    typeof process.listeners === 'function' &&
13    typeof process.kill === 'function' &&
14    typeof process.pid === 'number' &&
15    typeof process.on === 'function';
16const kExitEmitter = Symbol.for('signal-exit emitter');
17const global = globalThis;
18const ObjectDefineProperty = Object.defineProperty.bind(Object);
19// teeny special purpose ee
20class Emitter {
21    emitted = {
22        afterExit: false,
23        exit: false,
24    };
25    listeners = {
26        afterExit: [],
27        exit: [],
28    };
29    count = 0;
30    id = Math.random();
31    constructor() {
32        if (global[kExitEmitter]) {
33            return global[kExitEmitter];
34        }
35        ObjectDefineProperty(global, kExitEmitter, {
36            value: this,
37            writable: false,
38            enumerable: false,
39            configurable: false,
40        });
41    }
42    on(ev, fn) {
43        this.listeners[ev].push(fn);
44    }
45    removeListener(ev, fn) {
46        const list = this.listeners[ev];
47        const i = list.indexOf(fn);
48        /* c8 ignore start */
49        if (i === -1) {
50            return;
51        }
52        /* c8 ignore stop */
53        if (i === 0 && list.length === 1) {
54            list.length = 0;
55        }
56        else {
57            list.splice(i, 1);
58        }
59    }
60    emit(ev, code, signal) {
61        if (this.emitted[ev]) {
62            return false;
63        }
64        this.emitted[ev] = true;
65        let ret = false;
66        for (const fn of this.listeners[ev]) {
67            ret = fn(code, signal) === true || ret;
68        }
69        if (ev === 'exit') {
70            ret = this.emit('afterExit', code, signal) || ret;
71        }
72        return ret;
73    }
74}
75class SignalExitBase {
76}
77const signalExitWrap = (handler) => {
78    return {
79        onExit(cb, opts) {
80            return handler.onExit(cb, opts);
81        },
82        load() {
83            return handler.load();
84        },
85        unload() {
86            return handler.unload();
87        },
88    };
89};
90class SignalExitFallback extends SignalExitBase {
91    onExit() {
92        return () => { };
93    }
94    load() { }
95    unload() { }
96}
97class SignalExit extends SignalExitBase {
98    // "SIGHUP" throws an `ENOSYS` error on Windows,
99    // so use a supported signal instead
100    /* c8 ignore start */
101    #hupSig = process.platform === 'win32' ? 'SIGINT' : 'SIGHUP';
102    /* c8 ignore stop */
103    #emitter = new Emitter();
104    #process;
105    #originalProcessEmit;
106    #originalProcessReallyExit;
107    #sigListeners = {};
108    #loaded = false;
109    constructor(process) {
110        super();
111        this.#process = process;
112        // { <signal>: <listener fn>, ... }
113        this.#sigListeners = {};
114        for (const sig of signals) {
115            this.#sigListeners[sig] = () => {
116                // If there are no other listeners, an exit is coming!
117                // Simplest way: remove us and then re-send the signal.
118                // We know that this will kill the process, so we can
119                // safely emit now.
120                const listeners = this.#process.listeners(sig);
121                let { count } = this.#emitter;
122                // This is a workaround for the fact that signal-exit v3 and signal
123                // exit v4 are not aware of each other, and each will attempt to let
124                // the other handle it, so neither of them do. To correct this, we
125                // detect if we're the only handler *except* for previous versions
126                // of signal-exit, and increment by the count of listeners it has
127                // created.
128                /* c8 ignore start */
129                const p = process;
130                if (typeof p.__signal_exit_emitter__ === 'object' &&
131                    typeof p.__signal_exit_emitter__.count === 'number') {
132                    count += p.__signal_exit_emitter__.count;
133                }
134                /* c8 ignore stop */
135                if (listeners.length === count) {
136                    this.unload();
137                    const ret = this.#emitter.emit('exit', null, sig);
138                    /* c8 ignore start */
139                    const s = sig === 'SIGHUP' ? this.#hupSig : sig;
140                    if (!ret)
141                        process.kill(process.pid, s);
142                    /* c8 ignore stop */
143                }
144            };
145        }
146        this.#originalProcessReallyExit = process.reallyExit;
147        this.#originalProcessEmit = process.emit;
148    }
149    onExit(cb, opts) {
150        /* c8 ignore start */
151        if (!processOk(this.#process)) {
152            return () => { };
153        }
154        /* c8 ignore stop */
155        if (this.#loaded === false) {
156            this.load();
157        }
158        const ev = opts?.alwaysLast ? 'afterExit' : 'exit';
159        this.#emitter.on(ev, cb);
160        return () => {
161            this.#emitter.removeListener(ev, cb);
162            if (this.#emitter.listeners['exit'].length === 0 &&
163                this.#emitter.listeners['afterExit'].length === 0) {
164                this.unload();
165            }
166        };
167    }
168    load() {
169        if (this.#loaded) {
170            return;
171        }
172        this.#loaded = true;
173        // This is the number of onSignalExit's that are in play.
174        // It's important so that we can count the correct number of
175        // listeners on signals, and don't wait for the other one to
176        // handle it instead of us.
177        this.#emitter.count += 1;
178        for (const sig of signals) {
179            try {
180                const fn = this.#sigListeners[sig];
181                if (fn)
182                    this.#process.on(sig, fn);
183            }
184            catch (_) { }
185        }
186        this.#process.emit = (ev, ...a) => {
187            return this.#processEmit(ev, ...a);
188        };
189        this.#process.reallyExit = (code) => {
190            return this.#processReallyExit(code);
191        };
192    }
193    unload() {
194        if (!this.#loaded) {
195            return;
196        }
197        this.#loaded = false;
198        signals.forEach(sig => {
199            const listener = this.#sigListeners[sig];
200            /* c8 ignore start */
201            if (!listener) {
202                throw new Error('Listener not defined for signal: ' + sig);
203            }
204            /* c8 ignore stop */
205            try {
206                this.#process.removeListener(sig, listener);
207                /* c8 ignore start */
208            }
209            catch (_) { }
210            /* c8 ignore stop */
211        });
212        this.#process.emit = this.#originalProcessEmit;
213        this.#process.reallyExit = this.#originalProcessReallyExit;
214        this.#emitter.count -= 1;
215    }
216    #processReallyExit(code) {
217        /* c8 ignore start */
218        if (!processOk(this.#process)) {
219            return 0;
220        }
221        this.#process.exitCode = code || 0;
222        /* c8 ignore stop */
223        this.#emitter.emit('exit', this.#process.exitCode, null);
224        return this.#originalProcessReallyExit.call(this.#process, this.#process.exitCode);
225    }
226    #processEmit(ev, ...args) {
227        const og = this.#originalProcessEmit;
228        if (ev === 'exit' && processOk(this.#process)) {
229            if (typeof args[0] === 'number') {
230                this.#process.exitCode = args[0];
231                /* c8 ignore start */
232            }
233            /* c8 ignore start */
234            const ret = og.call(this.#process, ev, ...args);
235            /* c8 ignore start */
236            this.#emitter.emit('exit', this.#process.exitCode, null);
237            /* c8 ignore stop */
238            return ret;
239        }
240        else {
241            return og.call(this.#process, ev, ...args);
242        }
243    }
244}
245const process = globalThis.process;
246// wrap so that we call the method on the actual handler, without
247// exporting it directly.
248export const {
249/**
250 * Called when the process is exiting, whether via signal, explicit
251 * exit, or running out of stuff to do.
252 *
253 * If the global process object is not suitable for instrumentation,
254 * then this will be a no-op.
255 *
256 * Returns a function that may be used to unload signal-exit.
257 */
258onExit,
259/**
260 * Load the listeners.  Likely you never need to call this, unless
261 * doing a rather deep integration with signal-exit functionality.
262 * Mostly exposed for the benefit of testing.
263 *
264 * @internal
265 */
266load,
267/**
268 * Unload the listeners.  Likely you never need to call this, unless
269 * doing a rather deep integration with signal-exit functionality.
270 * Mostly exposed for the benefit of testing.
271 *
272 * @internal
273 */
274unload, } = signalExitWrap(processOk(process) ? new SignalExit(process) : new SignalExitFallback());
275//# sourceMappingURL=index.js.map