@f5-sales-demo/pi-utils 21.19.5 → 21.21.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +2 -2
- package/src/postmortem.ts +31 -0
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"type": "module",
|
|
3
3
|
"name": "@f5-sales-demo/pi-utils",
|
|
4
|
-
"version": "21.
|
|
4
|
+
"version": "21.21.0",
|
|
5
5
|
"description": "Shared utilities for pi packages",
|
|
6
6
|
"homepage": "https://github.com/f5-sales-demo/xcsh",
|
|
7
7
|
"author": "Can Boluk",
|
|
@@ -39,7 +39,7 @@
|
|
|
39
39
|
},
|
|
40
40
|
"devDependencies": {
|
|
41
41
|
"@types/bun": "^1.3",
|
|
42
|
-
"@f5-sales-demo/pi-natives": "21.
|
|
42
|
+
"@f5-sales-demo/pi-natives": "21.21.0"
|
|
43
43
|
},
|
|
44
44
|
"engines": {
|
|
45
45
|
"bun": ">=1.3.7"
|
package/src/postmortem.ts
CHANGED
|
@@ -25,6 +25,36 @@ export enum Reason {
|
|
|
25
25
|
const callbackList: ((reason: Reason) => Promise<void> | void)[] = [];
|
|
26
26
|
// Tracks cleanup run state (to prevent recursion/reentry issues)
|
|
27
27
|
let cleanupStage: "idle" | "running" | "complete" = "idle";
|
|
28
|
+
const signalInterceptors = new Map<Reason, Set<() => boolean | Promise<boolean>>>();
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Temporarily intercept one postmortem signal before global cleanup begins.
|
|
32
|
+
*
|
|
33
|
+
* This is intentionally scoped and opt-in. Returning true declares that the
|
|
34
|
+
* caller handled the signal; returning false leaves the normal cleanup/exit
|
|
35
|
+
* path unchanged. The returned function removes the interceptor.
|
|
36
|
+
*/
|
|
37
|
+
export function interceptSignal(reason: Reason, handler: () => boolean | Promise<boolean>): () => void {
|
|
38
|
+
const handlers = signalInterceptors.get(reason) ?? new Set();
|
|
39
|
+
handlers.add(handler);
|
|
40
|
+
signalInterceptors.set(reason, handlers);
|
|
41
|
+
return () => {
|
|
42
|
+
handlers.delete(handler);
|
|
43
|
+
if (handlers.size === 0) signalInterceptors.delete(reason);
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
async function signalWasIntercepted(reason: Reason): Promise<boolean> {
|
|
48
|
+
const handlers = [...(signalInterceptors.get(reason) ?? [])].reverse();
|
|
49
|
+
for (const handler of handlers) {
|
|
50
|
+
try {
|
|
51
|
+
if (await handler()) return true;
|
|
52
|
+
} catch (error) {
|
|
53
|
+
logger.error("Signal interceptor failed", { reason, error: String(error) });
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
return false;
|
|
57
|
+
}
|
|
28
58
|
|
|
29
59
|
/**
|
|
30
60
|
* Internal: runs all registered cleanup callbacks for the given reason.
|
|
@@ -79,6 +109,7 @@ function formatFatalError(label: string, err: Error): string {
|
|
|
79
109
|
if (isMainThread) {
|
|
80
110
|
process
|
|
81
111
|
.on("SIGINT", async () => {
|
|
112
|
+
if (await signalWasIntercepted(Reason.SIGINT)) return;
|
|
82
113
|
await runCleanup(Reason.SIGINT);
|
|
83
114
|
process.exit(130); // 128 + SIGINT (2)
|
|
84
115
|
})
|