@oh-my-pi/pi-utils 18.3.1 → 18.3.3
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/dist/types/postmortem.d.ts +19 -0
- package/package.json +2 -2
- package/src/postmortem.ts +39 -0
|
@@ -119,6 +119,25 @@ export declare function registerWorkerIpcFaultHandler(handler: (err: Error) => v
|
|
|
119
119
|
* when the last registrant unregisters.
|
|
120
120
|
*/
|
|
121
121
|
export declare function registerStdioDisconnectHandling(): () => void;
|
|
122
|
+
/**
|
|
123
|
+
* Fail a process entry whose event loop drains while `work` is still pending.
|
|
124
|
+
*
|
|
125
|
+
* The CLI starts from a floating `runCli()` call because top-level await breaks
|
|
126
|
+
* `--bytecode` builds. When a one-shot command awaits a promise that never
|
|
127
|
+
* settles and holds no live handle, the loop drains and Bun exits 0 without
|
|
128
|
+
* output: an unfinished command reported as success. `beforeExit` fires in
|
|
129
|
+
* exactly that state; explicit exits (`process.exit`, {@link quit},
|
|
130
|
+
* {@link exitProcess}, the signal handlers) never emit it.
|
|
131
|
+
*
|
|
132
|
+
* The report runs once and only sets `process.exitCode`, so later `beforeExit`
|
|
133
|
+
* listeners (LSP shutdown) still run before the process exits 1. It starts no
|
|
134
|
+
* work of its own beyond the log write; `once` keeps the second `beforeExit`
|
|
135
|
+
* that any drained follow-up work triggers from reporting again.
|
|
136
|
+
*
|
|
137
|
+
* `describe` names what was running (e.g. the resolved subcommand) at report
|
|
138
|
+
* time; it must return only a command name, never user arguments.
|
|
139
|
+
*/
|
|
140
|
+
export declare function reportUnsettledEntry(work: Promise<unknown>, describe?: () => string | undefined): void;
|
|
122
141
|
/**
|
|
123
142
|
* Mark an error as expected cleanup fallout so the global fatal handlers
|
|
124
143
|
* downgrade it to a log line instead of tearing down the process. Use for
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@oh-my-pi/pi-utils",
|
|
3
|
-
"version": "18.3.
|
|
3
|
+
"version": "18.3.3",
|
|
4
4
|
"description": "Shared utilities for pi packages",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"cli",
|
|
@@ -57,7 +57,7 @@
|
|
|
57
57
|
"fmt": "oxfmt --no-error-on-unmatched-pattern 'src/**/*.{ts,tsx}' '{test,bench,examples,scripts}/**/*.ts' '*.ts'"
|
|
58
58
|
},
|
|
59
59
|
"dependencies": {
|
|
60
|
-
"@oh-my-pi/pi-natives": "18.3.
|
|
60
|
+
"@oh-my-pi/pi-natives": "18.3.3"
|
|
61
61
|
},
|
|
62
62
|
"devDependencies": {
|
|
63
63
|
"@types/bun": "^1.3.14"
|
package/src/postmortem.ts
CHANGED
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
9
|
import * as fs from "node:fs";
|
|
10
|
+
import { APP_NAME } from "./dirs";
|
|
10
11
|
import * as logger from "./logger";
|
|
11
12
|
import { restoreTerminalStderr } from "./stderr-guard";
|
|
12
13
|
|
|
@@ -368,6 +369,44 @@ export function registerStdioDisconnectHandling(): () => void {
|
|
|
368
369
|
};
|
|
369
370
|
}
|
|
370
371
|
|
|
372
|
+
/**
|
|
373
|
+
* Fail a process entry whose event loop drains while `work` is still pending.
|
|
374
|
+
*
|
|
375
|
+
* The CLI starts from a floating `runCli()` call because top-level await breaks
|
|
376
|
+
* `--bytecode` builds. When a one-shot command awaits a promise that never
|
|
377
|
+
* settles and holds no live handle, the loop drains and Bun exits 0 without
|
|
378
|
+
* output: an unfinished command reported as success. `beforeExit` fires in
|
|
379
|
+
* exactly that state; explicit exits (`process.exit`, {@link quit},
|
|
380
|
+
* {@link exitProcess}, the signal handlers) never emit it.
|
|
381
|
+
*
|
|
382
|
+
* The report runs once and only sets `process.exitCode`, so later `beforeExit`
|
|
383
|
+
* listeners (LSP shutdown) still run before the process exits 1. It starts no
|
|
384
|
+
* work of its own beyond the log write; `once` keeps the second `beforeExit`
|
|
385
|
+
* that any drained follow-up work triggers from reporting again.
|
|
386
|
+
*
|
|
387
|
+
* `describe` names what was running (e.g. the resolved subcommand) at report
|
|
388
|
+
* time; it must return only a command name, never user arguments.
|
|
389
|
+
*/
|
|
390
|
+
export function reportUnsettledEntry(work: Promise<unknown>, describe?: () => string | undefined): void {
|
|
391
|
+
let pending = true;
|
|
392
|
+
const settled = (): void => {
|
|
393
|
+
pending = false;
|
|
394
|
+
};
|
|
395
|
+
// Observe both outcomes without claiming the rejection: the caller's `.catch` still owns it.
|
|
396
|
+
void work.then(settled, settled);
|
|
397
|
+
process.once("beforeExit", () => {
|
|
398
|
+
if (!pending) return;
|
|
399
|
+
const command = describe?.();
|
|
400
|
+
const subject = command ? `\`${APP_NAME} ${command}\`` : "command";
|
|
401
|
+
const message = `${subject} ended before completing: the event loop drained while it was still pending (rerun with PI_DEBUG_STARTUP=1 to see the last phase reached)`;
|
|
402
|
+
try {
|
|
403
|
+
fs.writeSync(2, `${APP_NAME}: ${message}\n`);
|
|
404
|
+
} catch {}
|
|
405
|
+
logger.error(message, { command });
|
|
406
|
+
process.exitCode = 1;
|
|
407
|
+
});
|
|
408
|
+
}
|
|
409
|
+
|
|
371
410
|
// Well-known key marking an error as an *expected* teardown artifact (e.g. a
|
|
372
411
|
// browser run-scope abort at normal run end). `Symbol.for` so the marker
|
|
373
412
|
// survives duplicate module instances across bundles/realms.
|