lacy 1.7.0-beta.2 → 1.7.1

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.
Files changed (2) hide show
  1. package/index.mjs +60 -9
  2. package/package.json +1 -1
package/index.mjs CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  import * as p from "@clack/prompts";
4
4
  import pc from "picocolors";
5
- import { execSync } from "child_process";
5
+ import { execSync, spawn } from "child_process";
6
6
  import {
7
7
  existsSync,
8
8
  mkdirSync,
@@ -19,6 +19,37 @@ const INSTALL_DIR_OLD = join(homedir(), ".lacy-shell");
19
19
  const CONFIG_FILE = join(INSTALL_DIR, "config.yaml");
20
20
  const REPO_URL = "https://github.com/lacymorrow/lacy.git";
21
21
 
22
+ // ============================================================================
23
+ // Terminal state safety net
24
+ // ============================================================================
25
+ // @clack/prompts puts stdin into raw mode during interactive prompts. If the
26
+ // process exits abnormally (unhandled error, SIGINT during a prompt, etc.),
27
+ // raw mode is never restored and the parent shell's tty is left corrupted —
28
+ // breaking Ctrl+C, paste, and other keyboard shortcuts until a new terminal
29
+ // window is opened. These handlers ensure we always clean up.
30
+
31
+ function restoreTerminalState() {
32
+ try {
33
+ if (process.stdin.isTTY && process.stdin.isRaw) {
34
+ process.stdin.setRawMode(false);
35
+ }
36
+ } catch {
37
+ // stdin may already be destroyed
38
+ }
39
+ // Restore cursor visibility and line wrapping
40
+ process.stdout.write("\x1b[?25h\x1b[?7h");
41
+ }
42
+
43
+ process.on("exit", restoreTerminalState);
44
+
45
+ for (const signal of ["SIGINT", "SIGTERM", "SIGHUP"]) {
46
+ process.on(signal, () => {
47
+ restoreTerminalState();
48
+ // Re-raise so the parent process sees the correct exit code
49
+ process.exit(128 + ({ SIGINT: 2, SIGTERM: 15, SIGHUP: 1 }[signal]));
50
+ });
51
+ }
52
+
22
53
  // Shell detection and per-shell configuration
23
54
  function detectShell() {
24
55
  const shell = process.env.SHELL || "";
@@ -135,15 +166,34 @@ async function restartShell(
135
166
 
136
167
  if (restart) {
137
168
  const cmd = shellCmd || getShellConfig(detectShell()).shellCmd;
138
- // Use exec to replace the current process (no nested shell)
139
169
  p.log.info(`Restarting ${cmd}...`);
140
- try {
141
- execSync(`exec ${cmd} -l`, { stdio: "inherit" });
142
- } catch {
143
- // exec replaces the process so this only runs if it fails
144
- p.log.warn(`Could not restart. Please run: ${cmd} -l`);
145
- }
146
- process.exit(0);
170
+
171
+ // Restore terminal state before handing off to the new shell
172
+ restoreTerminalState();
173
+
174
+ // Spawn a new login shell that inherits our stdio, then exit Node.
175
+ // We use spawn (not execSync) to avoid creating a nested shell —
176
+ // execSync("exec ...") only replaces the *child* process, not Node,
177
+ // leaving the user in a nested shell with corrupted terminal state.
178
+ const child = spawn(cmd, ["-l"], {
179
+ stdio: "inherit",
180
+ // Let the child own the terminal
181
+ detached: false,
182
+ });
183
+
184
+ child.on("error", () => {
185
+ p.log.warn(`Could not restart. Please run: exec ${cmd} -l`);
186
+ process.exit(0);
187
+ });
188
+
189
+ // When the spawned shell exits (user typed 'exit'), exit Node too
190
+ child.on("exit", (code) => {
191
+ process.exit(code ?? 0);
192
+ });
193
+
194
+ // Prevent Node from exiting while the shell is running
195
+ // (the child keeps the event loop alive via stdio, but be explicit)
196
+ return new Promise(() => {});
147
197
  }
148
198
  }
149
199
 
@@ -900,6 +950,7 @@ ${pc.dim("https://github.com/lacymorrow/lacy")}
900
950
  }
901
951
 
902
952
  main().catch((e) => {
953
+ restoreTerminalState();
903
954
  p.log.error(e.message);
904
955
  process.exit(1);
905
956
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lacy",
3
- "version": "1.7.0-beta.2",
3
+ "version": "1.7.1",
4
4
  "description": "Install lacy — talk to your terminal",
5
5
  "type": "module",
6
6
  "bin": {