codekingpro/portable-devtools
114k
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