Skip to content

Commit 76c5879

Browse files
committed
Fix wrapper terminal reset for help output
1 parent b22d244 commit 76c5879

3 files changed

Lines changed: 54 additions & 27 deletions

File tree

cli/release-staging/index.js

Lines changed: 18 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -17,11 +17,9 @@ const packageName = 'codecane'
1717
* Terminal escape sequences to reset terminal state after the child process exits.
1818
* When the binary is SIGKILL'd, it can't clean up its own terminal state.
1919
* The wrapper (this process) survives and must reset these modes.
20-
*
21-
* Keep in sync with TERMINAL_RESET_SEQUENCES in cli/src/utils/renderer-cleanup.ts
2220
*/
23-
const TERMINAL_RESET_SEQUENCES =
24-
'\x1b[?1049l' + // Exit alternate screen buffer
21+
const EXIT_ALTERNATE_SCREEN_SEQUENCE = '\x1b[?1049l'
22+
const SAFE_TERMINAL_RESET_SEQUENCES =
2523
'\x1b[?1000l' + // Disable X10 mouse mode
2624
'\x1b[?1002l' + // Disable button event mouse mode
2725
'\x1b[?1003l' + // Disable any-event mouse mode (all motion)
@@ -30,7 +28,12 @@ const TERMINAL_RESET_SEQUENCES =
3028
'\x1b[?2004l' + // Disable bracketed paste mode
3129
'\x1b[?25h' // Show cursor
3230

33-
function resetTerminal() {
31+
const FULL_TERMINAL_RESET_SEQUENCES =
32+
EXIT_ALTERNATE_SCREEN_SEQUENCE + SAFE_TERMINAL_RESET_SEQUENCES
33+
34+
function resetTerminal(options = {}) {
35+
const { exitAlternateScreen = false } = options
36+
3437
try {
3538
if (process.stdin.isTTY && process.stdin.setRawMode) {
3639
process.stdin.setRawMode(false)
@@ -40,7 +43,13 @@ function resetTerminal() {
4043
}
4144
try {
4245
if (process.stdout.isTTY) {
43-
process.stdout.write(TERMINAL_RESET_SEQUENCES)
46+
// Exiting the alternate screen is only safe after an interactive child.
47+
// Plain CLI paths like --help never enter it, and ?1049l can erase output.
48+
process.stdout.write(
49+
exitAlternateScreen
50+
? FULL_TERMINAL_RESET_SEQUENCES
51+
: SAFE_TERMINAL_RESET_SEQUENCES,
52+
)
4453
}
4554
} catch {
4655
// stdout may be closed
@@ -465,7 +474,7 @@ async function checkForUpdates(runningProcess, exitListener) {
465474
}, 5000)
466475
})
467476

468-
resetTerminal()
477+
resetTerminal({ exitAlternateScreen: true })
469478
console.log(`Update available: ${currentVersion}${latestVersion}`)
470479

471480
await downloadBinary(latestVersion)
@@ -476,7 +485,7 @@ async function checkForUpdates(runningProcess, exitListener) {
476485
})
477486

478487
newChild.on('exit', (code, signal) => {
479-
resetTerminal()
488+
resetTerminal({ exitAlternateScreen: Boolean(signal) })
480489
printCrashDiagnostics(code, signal)
481490
process.exit(signal ? 1 : (code || 0))
482491
})
@@ -557,7 +566,7 @@ async function main() {
557566
})
558567

559568
const exitListener = (code, signal) => {
560-
resetTerminal()
569+
resetTerminal({ exitAlternateScreen: Boolean(signal) })
561570
printCrashDiagnostics(code, signal)
562571
process.exit(signal ? 1 : (code || 0))
563572
}

cli/release/index.js

Lines changed: 18 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -17,11 +17,9 @@ const packageName = 'codebuff'
1717
* Terminal escape sequences to reset terminal state after the child process exits.
1818
* When the binary is SIGKILL'd, it can't clean up its own terminal state.
1919
* The wrapper (this process) survives and must reset these modes.
20-
*
21-
* Keep in sync with TERMINAL_RESET_SEQUENCES in cli/src/utils/renderer-cleanup.ts
2220
*/
23-
const TERMINAL_RESET_SEQUENCES =
24-
'\x1b[?1049l' + // Exit alternate screen buffer
21+
const EXIT_ALTERNATE_SCREEN_SEQUENCE = '\x1b[?1049l'
22+
const SAFE_TERMINAL_RESET_SEQUENCES =
2523
'\x1b[?1000l' + // Disable X10 mouse mode
2624
'\x1b[?1002l' + // Disable button event mouse mode
2725
'\x1b[?1003l' + // Disable any-event mouse mode (all motion)
@@ -30,7 +28,12 @@ const TERMINAL_RESET_SEQUENCES =
3028
'\x1b[?2004l' + // Disable bracketed paste mode
3129
'\x1b[?25h' // Show cursor
3230

33-
function resetTerminal() {
31+
const FULL_TERMINAL_RESET_SEQUENCES =
32+
EXIT_ALTERNATE_SCREEN_SEQUENCE + SAFE_TERMINAL_RESET_SEQUENCES
33+
34+
function resetTerminal(options = {}) {
35+
const { exitAlternateScreen = false } = options
36+
3437
try {
3538
if (process.stdin.isTTY && process.stdin.setRawMode) {
3639
process.stdin.setRawMode(false)
@@ -40,7 +43,13 @@ function resetTerminal() {
4043
}
4144
try {
4245
if (process.stdout.isTTY) {
43-
process.stdout.write(TERMINAL_RESET_SEQUENCES)
46+
// Exiting the alternate screen is only safe after an interactive child.
47+
// Plain CLI paths like --help never enter it, and ?1049l can erase output.
48+
process.stdout.write(
49+
exitAlternateScreen
50+
? FULL_TERMINAL_RESET_SEQUENCES
51+
: SAFE_TERMINAL_RESET_SEQUENCES,
52+
)
4453
}
4554
} catch {
4655
// stdout may be closed
@@ -485,15 +494,15 @@ async function checkForUpdates(runningProcess, exitListener) {
485494
}, 5000)
486495
})
487496

488-
resetTerminal()
497+
resetTerminal({ exitAlternateScreen: true })
489498
console.log(`Update available: ${currentVersion}${latestVersion}`)
490499

491500
await downloadBinary(latestVersion)
492501

493502
const newChild = spawnInstalledBinary({ detached: false })
494503

495504
newChild.on('exit', (code, signal) => {
496-
resetTerminal()
505+
resetTerminal({ exitAlternateScreen: Boolean(signal) })
497506
printCrashDiagnostics(code, signal)
498507
process.exit(signal ? 1 : (code || 0))
499508
})
@@ -625,7 +634,7 @@ async function main() {
625634
const child = spawnInstalledBinary()
626635

627636
const exitListener = (code, signal) => {
628-
resetTerminal()
637+
resetTerminal({ exitAlternateScreen: Boolean(signal) })
629638
printCrashDiagnostics(code, signal)
630639
process.exit(signal ? 1 : (code || 0))
631640
}

freebuff/cli/release/index.js

Lines changed: 18 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -17,11 +17,9 @@ const packageName = 'freebuff'
1717
* Terminal escape sequences to reset terminal state after the child process exits.
1818
* When the binary is SIGKILL'd, it can't clean up its own terminal state.
1919
* The wrapper (this process) survives and must reset these modes.
20-
*
21-
* Keep in sync with TERMINAL_RESET_SEQUENCES in cli/src/utils/renderer-cleanup.ts
2220
*/
23-
const TERMINAL_RESET_SEQUENCES =
24-
'\x1b[?1049l' + // Exit alternate screen buffer
21+
const EXIT_ALTERNATE_SCREEN_SEQUENCE = '\x1b[?1049l'
22+
const SAFE_TERMINAL_RESET_SEQUENCES =
2523
'\x1b[?1000l' + // Disable X10 mouse mode
2624
'\x1b[?1002l' + // Disable button event mouse mode
2725
'\x1b[?1003l' + // Disable any-event mouse mode (all motion)
@@ -30,7 +28,12 @@ const TERMINAL_RESET_SEQUENCES =
3028
'\x1b[?2004l' + // Disable bracketed paste mode
3129
'\x1b[?25h' // Show cursor
3230

33-
function resetTerminal() {
31+
const FULL_TERMINAL_RESET_SEQUENCES =
32+
EXIT_ALTERNATE_SCREEN_SEQUENCE + SAFE_TERMINAL_RESET_SEQUENCES
33+
34+
function resetTerminal(options = {}) {
35+
const { exitAlternateScreen = false } = options
36+
3437
try {
3538
if (process.stdin.isTTY && process.stdin.setRawMode) {
3639
process.stdin.setRawMode(false)
@@ -40,7 +43,13 @@ function resetTerminal() {
4043
}
4144
try {
4245
if (process.stdout.isTTY) {
43-
process.stdout.write(TERMINAL_RESET_SEQUENCES)
46+
// Exiting the alternate screen is only safe after an interactive child.
47+
// Plain CLI paths like --help never enter it, and ?1049l can erase output.
48+
process.stdout.write(
49+
exitAlternateScreen
50+
? FULL_TERMINAL_RESET_SEQUENCES
51+
: SAFE_TERMINAL_RESET_SEQUENCES,
52+
)
4453
}
4554
} catch {
4655
// stdout may be closed
@@ -472,15 +481,15 @@ async function checkForUpdates(runningProcess, exitListener) {
472481
}, 5000)
473482
})
474483

475-
resetTerminal()
484+
resetTerminal({ exitAlternateScreen: true })
476485
console.log(`Update available: ${currentVersion}${latestVersion}`)
477486

478487
await downloadBinary(latestVersion)
479488

480489
const newChild = spawnInstalledBinary({ detached: false })
481490

482491
newChild.on('exit', (code, signal) => {
483-
resetTerminal()
492+
resetTerminal({ exitAlternateScreen: Boolean(signal) })
484493
printCrashDiagnostics(code, signal)
485494
process.exit(signal ? 1 : (code || 0))
486495
})
@@ -612,7 +621,7 @@ async function main() {
612621
const child = spawnInstalledBinary()
613622

614623
const exitListener = (code, signal) => {
615-
resetTerminal()
624+
resetTerminal({ exitAlternateScreen: Boolean(signal) })
616625
printCrashDiagnostics(code, signal)
617626
process.exit(signal ? 1 : (code || 0))
618627
}

0 commit comments

Comments
 (0)