next-anteater 0.2.1 → 0.2.2

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/lib/setup.mjs CHANGED
@@ -9,7 +9,7 @@ import { execSync } from "node:child_process";
9
9
  import {
10
10
  bold, dim, green, red, yellow, cyan,
11
11
  ok, fail, warn, info, heading, blank,
12
- ask, confirm, select, spinner,
12
+ ask, confirm, select, spinner, closeRL,
13
13
  } from "./ui.mjs";
14
14
  import { detectProject } from "./detect.mjs";
15
15
  import { scaffoldFiles } from "./scaffold.mjs";
@@ -314,6 +314,7 @@ export async function main() {
314
314
  else warn("Test dispatch failed \u2014 check GitHub Actions");
315
315
 
316
316
  // ─── Done! ──────────────────────────────────────────────────
317
+ closeRL();
317
318
  blank();
318
319
  console.log(` ${bold(green("\u{1F41C} Anteater is ready."))}`);
319
320
  blank();
package/lib/ui.mjs CHANGED
@@ -1,5 +1,9 @@
1
1
  /**
2
2
  * Terminal UI helpers — zero dependencies.
3
+ *
4
+ * Uses a single shared readline interface so piped input works correctly.
5
+ * Creating multiple interfaces on the same stdin consumes the buffer on
6
+ * the first close, breaking subsequent reads.
3
7
  */
4
8
  import * as readline from "node:readline";
5
9
 
@@ -20,37 +24,58 @@ export const heading = (msg) => console.log(`\n ${bold(msg)}\n ${"─".repeat(
20
24
  export const blank = () => console.log();
21
25
 
22
26
  /**
23
- * Prompt the user for text input.
27
+ * Shared readline interface created lazily, closed once at process exit.
24
28
  */
25
- export function ask(question, { mask = false } = {}) {
26
- return new Promise((resolve) => {
27
- const rl = readline.createInterface({
29
+ let _rl = null;
30
+
31
+ function getRL() {
32
+ if (!_rl) {
33
+ _rl = readline.createInterface({
28
34
  input: process.stdin,
29
35
  output: process.stdout,
30
36
  });
37
+ // Don't let the readline keep the process alive
38
+ _rl.on("close", () => { _rl = null; });
39
+ }
40
+ return _rl;
41
+ }
42
+
43
+ /** Close the shared readline (call at end of setup). */
44
+ export function closeRL() {
45
+ if (_rl) {
46
+ _rl.close();
47
+ _rl = null;
48
+ }
49
+ }
50
+
51
+ /**
52
+ * Prompt the user for text input.
53
+ */
54
+ export function ask(question, { mask = false } = {}) {
55
+ return new Promise((resolve) => {
56
+ const rl = getRL();
31
57
 
32
- // If masking, mute output and write dots
33
58
  if (mask) {
34
59
  const origWrite = process.stdout.write.bind(process.stdout);
60
+ const restore = () => { process.stdout.write = origWrite; };
61
+
35
62
  process.stdout.write = (chunk, encoding, cb) => {
36
- // Let the question prompt through, mask everything after
37
63
  if (typeof chunk === "string" && chunk.includes(question)) {
38
64
  return origWrite(chunk, encoding, cb);
39
65
  }
40
- // Replace characters with bullets
41
66
  const masked = typeof chunk === "string" ? chunk.replace(/[^\r\n]/g, "•") : chunk;
42
67
  return origWrite(masked, encoding, cb);
43
68
  };
44
69
 
45
- rl.on("close", () => {
46
- process.stdout.write = origWrite;
70
+ rl.question(` ${cyan("?")} ${question} `, (answer) => {
71
+ restore();
72
+ resolve(answer.trim());
73
+ });
74
+ } else {
75
+ rl.question(` ${cyan("?")} ${question} `, (answer) => {
76
+ resolve(answer.trim());
47
77
  });
48
78
  }
49
-
50
- rl.question(` ${cyan("?")} ${question} `, (answer) => {
51
- rl.close();
52
- resolve(answer.trim());
53
- });
54
79
  });
55
80
  }
56
81
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "next-anteater",
3
- "version": "0.2.1",
3
+ "version": "0.2.2",
4
4
  "description": "AI-powered live editing for your Next.js app",
5
5
  "bin": {
6
6
  "anteater": "bin/setup-anteater.mjs",