File size: 2,510 Bytes
cd8bd0a
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
import readline from "node:readline";

export function createPrompt() {
  const rl = readline.createInterface({
    input: process.stdin,
    output: process.stdout,
  });

  // Non-interactive stdin (pipe, CI, EOF via `< /dev/null`) cannot answer an
  // interactive prompt. Without a guard, `rl.question` never fires its callback —
  // the await stays pending and Node warns about an "unsettled top-level await" at
  // exit. Resolving on the readline `close` event (which fires on stdin EOF)
  // returns the default/empty instead of hanging. A genuinely piped line still
  // arrives via the question callback first, so `echo value | omniroute …` keeps
  // working — only the no-input EOF case falls back.
  function ask(question, defaultValue = "") {
    const suffix = defaultValue ? ` (${defaultValue})` : "";
    return new Promise((resolve) => {
      let settled = false;
      const done = (v) => {
        if (!settled) {
          settled = true;
          resolve(v);
        }
      };
      rl.once("close", () => done(defaultValue));
      rl.question(`${question}${suffix}: `, (answer) => {
        const trimmed = answer.trim();
        done(trimmed || defaultValue);
      });
    });
  }

  function askSecret(question) {
    return new Promise((resolve) => {
      let settled = false;
      const saved = rl._writeToOutput.bind(rl);
      const done = (v) => {
        if (!settled) {
          settled = true;
          rl._writeToOutput = saved;
          resolve(v);
        }
      };
      let prompted = false;
      rl._writeToOutput = function (str) {
        if (!prompted) {
          rl.output.write(str);
          if (str.endsWith(": ")) prompted = true;
          return;
        }
        // Suppress character echo; allow only newlines through
        if (str === "\r\n" || str === "\n" || str === "\r") rl.output.write("\n");
      };
      rl.once("close", () => done("")); // non-interactive EOF → empty secret, no hang
      rl.question(`${question}: `, (answer) => {
        done(answer.trim());
      });
    });
  }

  function close() {
    rl.close();
  }

  return { ask, askSecret, close };
}

export function printHeading(title) {
  console.log(`\n\x1b[1m\x1b[36m${title}\x1b[0m\n`);
}

export function printSuccess(message) {
  console.log(`\x1b[32m✔ ${message}\x1b[0m`);
}

export function printInfo(message) {
  console.log(`\x1b[2m${message}\x1b[0m`);
}

export function printError(message) {
  console.log(`\x1b[31m✖ ${message}\x1b[0m`);
}