@amr-m-abdelgawad/devctl 0.8.1 → 0.10.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/bin/devctl.cjs CHANGED
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  "use strict";
3
3
 
4
- const { closeSync, openSync, readSync } = require("fs");
4
+ const { closeSync, openSync, readSync, writeSync } = require("fs");
5
5
  const { resolve } = require("path");
6
6
  const { spawn } = require("child_process");
7
7
 
@@ -103,6 +103,55 @@ function launch(options) {
103
103
  });
104
104
  }
105
105
 
106
+ // Mirror of TERMINAL_RESTORE_SEQUENCE in app/src/shared/terminal-restore.ts.
107
+ // This launcher is a standalone .cjs that cannot import the app's TypeScript,
108
+ // so the escape sequence is duplicated and kept in sync by hand.
109
+ const TERMINAL_RESTORE_SEQUENCE =
110
+ "\x1b[?1003l\x1b[?1002l\x1b[?1000l\x1b[?1006l\x1b[?1015l\x1b[?1016l" +
111
+ "\x1b[?2004l\x1b[?1049l\x1b[?25h\x1b[<u\x1b[0m";
112
+
113
+ // When the Bun child dies abnormally — a native panic exits via a fatal signal
114
+ // (SIGSEGV/SIGABRT) or a non-zero code — OpenTUI never reached its own
115
+ // destroy(), so the terminal is left in raw mode and on the alternate screen.
116
+ // This parent process survives the child's crash, so it is the one place that
117
+ // can still un-raw the inherited TTY. A clean exit (code 0, no signal) means
118
+ // the TUI already restored the terminal itself, so we leave it alone.
119
+ //
120
+ // The bytes are written synchronously to the terminal file descriptors, not
121
+ // through process.stdout.write: a TTY stream write is asynchronous on Windows,
122
+ // and the caller re-raises the child's fatal signal immediately afterwards, so
123
+ // an async write could be dropped when the self-signal kills the wrapper before
124
+ // it flushes. writeSync hands the bytes to the OS before we return, mirroring
125
+ // restoreTerminalModes() in app/src/shared/terminal-restore.ts.
126
+ function restoreTerminalAfterCrash(code, signal, options) {
127
+ const crashed = Boolean(signal) || (typeof code === "number" && code !== 0);
128
+ if (!crashed) {
129
+ return false;
130
+ }
131
+ const opts = options !== undefined ? options : {};
132
+ const write = opts.write !== undefined ? opts.write : writeSync;
133
+ const targets =
134
+ opts.targets !== undefined
135
+ ? opts.targets
136
+ : [
137
+ { fd: 1, isTTY: Boolean(process.stdout && process.stdout.isTTY) },
138
+ { fd: 2, isTTY: Boolean(process.stderr && process.stderr.isTTY) },
139
+ ];
140
+ const buffer = Buffer.from(TERMINAL_RESTORE_SEQUENCE);
141
+ let wrote = false;
142
+ for (const target of targets) {
143
+ if (target && target.isTTY) {
144
+ try {
145
+ write(target.fd, buffer);
146
+ wrote = true;
147
+ } catch (_) {
148
+ // fd already closed
149
+ }
150
+ }
151
+ }
152
+ return wrote;
153
+ }
154
+
106
155
  function preserveChildExit(hostProcess, code, signal) {
107
156
  if (typeof code === "number") {
108
157
  hostProcess.exitCode = code;
@@ -150,6 +199,7 @@ function runMain() {
150
199
  for (const [registeredSignal, handler] of handlers) {
151
200
  process.off(registeredSignal, handler);
152
201
  }
202
+ restoreTerminalAfterCrash(code, signal);
153
203
  preserveChildExit(process, code, signal);
154
204
  });
155
205
  }
@@ -164,6 +214,8 @@ module.exports = {
164
214
  assertSupportedTarget,
165
215
  launch,
166
216
  preserveChildExit,
217
+ restoreTerminalAfterCrash,
218
+ TERMINAL_RESTORE_SEQUENCE,
167
219
  readFilePrefix,
168
220
  resolveBunExecutable,
169
221
  targetFor,