Compatibility
How closeout's three drop-ins are graded — each incumbent's own test suite, unedited — the current grades, including signal-exit's 134 / 135 level with signal-exit itself, and the differences that remain.
closeout/signal-exit, closeout/exit-hook and closeout/restore-cursor are graded, not
described as compatible. Each is run against its incumbent's own test suite, by
compat-oracle,
in CI.
✓ yes · ◐ partial, with what is missing · ✗ no · — does not apply. Every mark links to its evidence: our test or grade, or the incumbent’s source at the version compat-oracle grades.
| Capability | closeout | signal-exit | exit-hook | restore-cursor |
|---|---|---|---|---|
| Compatibility | ||||
Passes exit-hook's own test suitecloseout/exit-hook is graded by exit-hook 5.1.0's own tests, unedited, so changing the import keeps exit-hook's behaviour, 128 + n exit codes included. | closeout: yes21 / 21 of its own tests | signal-exit: does not applya different API | exit-hook: yesits own suite, the control run | restore-cursor: does not applya different API |
Passes restore-cursor's own test suitecloseout/restore-cursor is graded by restore-cursor 5.1.0's own tests, which spawn a child per case and check which stream the cursor comes back on. | closeout: yes6 / 6 of its own tests | signal-exit: does not applya different API | exit-hook: does not applya different API | restore-cursor: yesits own suite, the control run |
Passes signal-exit's own test suite, level with signal-exitcloseout/signal-exit passes 134 of the 135 cases of signal-exit 4.1.0's own suite, and signal-exit itself passes the same 134: the one case short fails for both on current Node. | closeout: partialpartial134 / 135 of its own tests | signal-exit: partialpartialits own suite, the control run, passes 134 of 135: this case fails on Node 22, 24 and 26, all released after signal-exit's last release | exit-hook: does not applya different API | restore-cursor: does not applya different API |
Compatibility
Passes exit-hook's own test suite
closeout/exit-hookis graded by exit-hook 5.1.0's own tests, unedited, so changing the import keeps exit-hook's behaviour, 128 + n exit codes included.signal-exit- signal-exit: does not applya different API
restore-cursor- restore-cursor: does not applya different API
Passes restore-cursor's own test suite
closeout/restore-cursoris graded by restore-cursor 5.1.0's own tests, which spawn a child per case and check which stream the cursor comes back on.signal-exit- signal-exit: does not applya different API
restore-cursor- restore-cursor: yesits own suite, the control run
Passes signal-exit's own test suite, level with signal-exit
closeout/signal-exitpasses 134 of the 135 cases of signal-exit 4.1.0's own suite, and signal-exit itself passes the same 134: the one case short fails for both on current Node.restore-cursor- restore-cursor: does not applya different API
The counts are compat-oracle's baselines, the pass count each drop-in is held to. The family's compatibility page is generated from the oracle's last run and is the authority for the current figures, beside every other drop-in in the family.
How a suite is graded
- The incumbent's repository is cloned at the release tag of the graded version —
signal-exit 4.1.0, exit-hook 5.1.0, restore-cursor 5.1.0 — and its test directory copied
into
packages/compat-oracle/vendor/. No incumbent ships its tests to npm, so a tarball could not be used. Each copy'sPROVENANCEfile names the tag, the commit and the command that reproduces it. - The only edit is the import that reaches the library: it is rewritten to a shim generated per run. Assertions, fixtures and helpers are upstream's, byte for byte.
- A control run points the shim at the real incumbent first. That proves the harness before it grades anything of ours, and the control's total is what every rate is measured against.
- The target run points the same shim at closeout's drop-in.
What each grade covers
- exit-hook — 21 of 21. Eighteen cases spawn a real process and assert what it did: the code it left with and the bytes that made it out, 20,000 lines of stdout under backpressure among them. Four kill their child after a fixed 1000 ms, and on a heavily loaded machine the child can lose that race against real exit-hook as much as against closeout.
- restore-cursor — 6 of 6. Four cases spawn a child with stdout and stderr forced to each TTY combination and assert which stream the sequence came out on, or that nothing did.
- signal-exit — 134 of 135, level with signal-exit. The control run, signal-exit 4.1.0
against its own suite, also passes 134. The case both fail is
signal-exit-test.ts>does not exit if user handles signal: its fixture re-sends SIGTERM from a timer inside the listener and expects the fourth to kill the process, and on current Node the process exits cleanly after the first. That was measured on Node 22, 24 and 26 with the fixture requiring signal-exit directly; signal-exit's last release, 2023-07-29, predates all three. The suite registers 135 cases on Linux and 127 on macOS, because Linux has four more signals; all eight extra cases pass for both.
Known differences
These are deliberate, and each is written down where the code is:
closeout/exit-hookkeeps exit-hook's signals and codes. It listens on SIGINT and SIGTERM and not SIGHUP, and exits128 + nrather than dying of the signal, because exit-hook does both and its suite grades them.signal.test.tspins both so nobody closes the gap by accident. closeout's ownonExitis where SIGHUP and the re-raise live.closeout/exit-hookkeeps exit-hook's bound. The per-hookwaitis honoured and closeout's 2000 ms deadline is not imposed. Hooks run in closeout's phases — synchronous hooks inflush, asynchronous ones inrelease— so a cursor hidden withhideCursor()comes back after all of them.closeout/signal-exitis CommonJS, the one CommonJS file in the family. signal-exit's suite swaps the globalprocessand re-requires the module, and an ES module is evaluated once per process however the cache is edited.import { onExit } from 'closeout/signal-exit'works from ESM too. It is graded against signal-exit 4: version 3's callable default,require('signal-exit')(handler), is not reproduced.closeout/signal-exitshares signal-exit's emitter, under the global key signal-exit itself uses, so a program with the drop-in and a transitive copy of real signal-exit runs each handler once.closeout/restore-cursorhas no dependencies. restore-cursor depends ononetimeand signal-exit; the drop-in registers its restore in closeout'srestorephase instead of signal-exit'salwaysLast.
Moving one import
- import { onExit } from 'signal-exit';
+ import { onExit } from 'closeout/signal-exit';npx burgee migrate --dry-run lists every import it would rewrite — only drop-ins graded level
with their incumbent — and npx burgee migrate makes the change
(Migrate). An overrides entry is not the
route: it would point the incumbent's name at closeout's root, which is closeout's own API.
Incremental migration moves from a drop-in to onExit
a handler at a time.
Why closeout
closeout against signal-exit, exit-hook and restore-cursor, one capability per row, every cell linked to the test, grade or source that proves it.
Coming from signal-exit
A signal-exit alternative with a drop-in path: import { onExit } from closeout/signal-exit, graded 134 / 135 by signal-exit's own test suite, level with signal-exit itself — then an onExit that runs once on every exit path under a bounded deadline, and reports as --json or an agent event.