-
Notifications
You must be signed in to change notification settings - Fork 15
Expand file tree
/
Copy pathexit-process.ts
More file actions
96 lines (83 loc) · 2.7 KB
/
exit-process.ts
File metadata and controls
96 lines (83 loc) · 2.7 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
import os from 'node:os';
import process from 'node:process';
// POSIX shells convention: exit status = 128 + signal number
// https://www.gnu.org/software/bash/manual/html_node/Exit-Status.html#:~:text=When%20a%20command%20terminates%20on%20a%20fatal%20signal%20whose%20number%20is%20N%2C%20Bash%20uses%20the%20value%20128%2BN%20as%20the%20exit%20status.
const UNIX_SIGNAL_EXIT_CODE_OFFSET = 128;
const unixSignalExitCode = (signalNumber: number) =>
UNIX_SIGNAL_EXIT_CODE_OFFSET + signalNumber;
const SIGINT_CODE = 2;
const SIGTERM_CODE = 15;
const SIGQUIT_CODE = 3;
export const SIGNAL_EXIT_CODES = (): Record<SignalName, number> => {
const isWindowsRuntime = os.platform() === 'win32';
return {
SIGINT: isWindowsRuntime ? SIGINT_CODE : unixSignalExitCode(SIGINT_CODE),
SIGTERM: unixSignalExitCode(SIGTERM_CODE),
SIGQUIT: unixSignalExitCode(SIGQUIT_CODE),
};
};
export const DEFAULT_FATAL_EXIT_CODE = 1;
export type SignalName = 'SIGINT' | 'SIGTERM' | 'SIGQUIT';
export type FatalKind = 'uncaughtException' | 'unhandledRejection';
export type CloseReason =
| { kind: 'signal'; signal: SignalName }
| { kind: 'fatal'; fatal: FatalKind }
| { kind: 'exit' };
export type ExitHandlerOptions = {
onExit?: (code: number, reason: CloseReason) => void;
onError?: (err: unknown, kind: FatalKind) => void;
exitOnFatal?: boolean;
exitOnSignal?: boolean;
fatalExitCode?: number;
};
export function installExitHandlers(options: ExitHandlerOptions = {}): void {
// eslint-disable-next-line functional/no-let
let closedReason: CloseReason | undefined;
const {
onExit,
onError,
exitOnFatal,
exitOnSignal,
fatalExitCode = DEFAULT_FATAL_EXIT_CODE,
} = options;
const close = (code: number, reason: CloseReason) => {
if (closedReason) {
return;
}
closedReason = reason;
onExit?.(code, reason);
};
process.on('uncaughtException', err => {
onError?.(err, 'uncaughtException');
if (exitOnFatal) {
close(fatalExitCode, {
kind: 'fatal',
fatal: 'uncaughtException',
});
}
});
process.on('unhandledRejection', reason => {
onError?.(reason, 'unhandledRejection');
if (exitOnFatal) {
close(fatalExitCode, {
kind: 'fatal',
fatal: 'unhandledRejection',
});
}
});
(['SIGINT', 'SIGTERM', 'SIGQUIT'] as const).forEach(signal => {
process.on(signal, () => {
close(SIGNAL_EXIT_CODES()[signal], { kind: 'signal', signal });
if (exitOnSignal) {
// eslint-disable-next-line n/no-process-exit
process.exit(SIGNAL_EXIT_CODES()[signal]);
}
});
});
process.on('exit', code => {
if (closedReason) {
return;
}
close(code, { kind: 'exit' });
});
}