Files
vue-tui/packages/runtime/package.json
T
Yunfei He 216a7021a0 fix(runtime): restore terminal on signal exit via signal-exit (Ink parity, G18, HIGH) (#47)
* fix(runtime): restore terminal on signal exit via signal-exit (Ink parity, G18)

Previously nothing routed a process signal to teardown(): SIGINT-as-signal,
SIGTERM or SIGHUP killed the process with the cursor hidden, the alternate
screen active and raw mode on, leaving the terminal corrupted.

Mirror Ink (ink.tsx:426): register signal-exit's onExit(teardown,
{alwaysLast:false}) at interactive mount, storing the unsubscribe fn, and
call it first thing in teardown() (ink.tsx:765) so the handler is removed on
unmount()/exit() and can't leak or double-run. teardown() stays idempotent
(teardownStarted guard) so a signal-triggered teardown plus a later unmount
won't double-run, and we don't prevent the process from exiting. Only the
live interactive, non-debug mount registers — render-to-string /
non-interactive paths never touch process signal handlers; registration is
guarded against double-registration.

Uses signal-exit v4 (named onExit export; ships ESM + types, so no
@types/signal-exit needed). PTY test sends SIGINT/SIGTERM/SIGHUP to a mounted
alt-screen app and asserts the captured output ends with show-cursor
(\x1b[?25h) + leave-alt-screen (\x1b[?1049l).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

Review follow-ups (3 fixes): register signal-exit whenever interactive
(drop the !debug gate so debug-but-interactive apps, which still enter the
alt-screen/hide the cursor, restore on signal — Ink ink.tsx:426); add
!teardownStarted to the registration so a spent app instance does not
re-register on a same-instance remount (the next unmount() returns early at
the teardownStarted guard before it could unsubscribe — a leak); and make
the PTY test prove the SIGNAL drove teardown (fixture never self-unmounts, so
restore bytes can only come from the signal path) with a debug-mode signal
test, an exit-anchored waitForOutput drain, and a bounded retry for the
async-flush race under saturated runners.

Review follow-ups (2 fixes): synchronous restore flush on signal — the
signal-exit teardown path now writes the restore escapes (show-cursor,
leave-alt-screen, disable-kitty) via fs.writeSync to the stdout fd so they
reach the terminal before signal-exit re-raises the signal (a buffered async
stream.write could be lost on abrupt exit); the normal unmount path keeps async
writes. Removed the config-wide retry:3 from vitest.pty.config.ts (it masked
the whole PTY suite) and scoped a retry:2 to the signal-teardown describe only,
for the residual parent-side node-pty onData read-race under a saturated runner.

* chore(parity): ledger — G18 pr-open

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
2026-05-30 06:04:51 +08:00

55 lines
1.3 KiB
JSON

{
"name": "@vue-tui/runtime",
"version": "0.0.2",
"description": "Vue 3 terminal renderer with Yoga flexbox layout, inspired by React Ink.",
"license": "MIT",
"files": [
"dist"
],
"type": "module",
"exports": {
".": "./dist/index.mjs",
"./internal": "./dist/internal.mjs",
"./package.json": "./package.json"
},
"publishConfig": {
"access": "public"
},
"scripts": {
"build": "vp pack",
"dev": "vp pack --watch",
"test": "vp test",
"check:type": "tsc --noEmit",
"prepublishOnly": "vp run build"
},
"dependencies": {
"@alcalzone/ansi-tokenize": "catalog:",
"@vue/runtime-core": "^3.4.0",
"ansi-escapes": "catalog:",
"chalk": "^5.3.0",
"cli-boxes": "^3.0.0",
"cli-truncate": "^6.0.0",
"is-in-ci": "catalog:",
"patch-console": "catalog:",
"signal-exit": "^4.1.0",
"slice-ansi": "^9.0.0",
"string-width": "^8.0.0",
"terminal-size": "catalog:",
"wrap-ansi": "^10.0.0",
"yoga-layout": "^3.1.0"
},
"devDependencies": {
"@types/node": "^25.6.2",
"@vitejs/plugin-vue-jsx": "catalog:",
"typescript": "^6.0.3",
"vite-plus": "^0.1.20",
"vue": "^3.4.0"
},
"peerDependencies": {
"vue": "^3.4.0"
},
"engines": {
"node": ">=22"
}
}