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