|
| 1 | +// ─── TTY utilities ──────────────────────────────────────────────────────────── |
| 2 | +// |
| 3 | +// When stdin is piped (e.g. `cat results.md | github-issue-ops issue create`), |
| 4 | +// process.stdin is consumed by readStdin() and left at EOF. |
| 5 | +// Interactive prompt libraries (clack, inquirer, etc.) all read from |
| 6 | +// process.stdin, so they immediately receive EOF and exit silently. |
| 7 | +// |
| 8 | +// reopenStdinAsTty() replaces process.stdin with a fresh ReadStream opened |
| 9 | +// on /dev/tty, restoring the interactive terminal connection so that prompts |
| 10 | +// work normally even after piped input has been consumed. |
| 11 | + |
| 12 | +import { ReadStream } from "node:tty"; |
| 13 | +import { openSync } from "node:fs"; |
| 14 | + |
| 15 | +/** |
| 16 | + * If stdin was piped (and therefore consumed), re-open /dev/tty and reassign |
| 17 | + * process.stdin so interactive prompts can still read from the terminal. |
| 18 | + * No-op on Windows or when stdin is already a TTY. |
| 19 | + */ |
| 20 | +export function reopenStdinAsTty(): void { |
| 21 | + // Already a live TTY — nothing to do |
| 22 | + if (process.stdin.isTTY) return; |
| 23 | + // /dev/tty is POSIX-only (macOS + Linux) |
| 24 | + if (process.platform === "win32") return; |
| 25 | + try { |
| 26 | + const fd = openSync("/dev/tty", "r+"); |
| 27 | + const tty = new ReadStream(fd); |
| 28 | + // @ts-expect-error — intentionally replacing the global stdin stream |
| 29 | + process.stdin = tty; |
| 30 | + process.stdin.resume(); |
| 31 | + } catch { |
| 32 | + // /dev/tty unavailable (CI without terminal, container, etc.) — prompts |
| 33 | + // will still fail gracefully; caller should fall back to --non-interactive. |
| 34 | + } |
| 35 | +} |
0 commit comments