@forge/cli-shared

9.5.1-next.59.6.0-next.6
out/ui/command-line-ui.js
~out/ui/command-line-ui.jsModified
+100−6
Index: package/out/ui/command-line-ui.js
===================================================================
--- package/out/ui/command-line-ui.js
+++ package/out/ui/command-line-ui.js
@@ -1,8 +1,10 @@
 "use strict";
 Object.defineProperty(exports, "__esModule", { value: true });
 exports.CommandLineUI = void 0;
 const tslib_1 = require("tslib");
+const fs = tslib_1.__importStar(require("fs"));
+const tty = tslib_1.__importStar(require("tty"));
 const cli_table3_1 = tslib_1.__importDefault(require("cli-table3"));
 const inquirer_1 = tslib_1.__importDefault(require("inquirer"));
 const inquirer_autocomplete_prompt_1 = tslib_1.__importDefault(require("inquirer-autocomplete-prompt"));
 const ora_1 = tslib_1.__importDefault(require("ora"));
@@ -41,14 +43,17 @@
     addedProgressPadding = false;
     static ANSI_MOVE_CURSOR_UP_ONE = '\x1B[1A';
     static ANSI_CLEAR_CURSOR_LINE = '\x1B[2K';
     static NON_TTY_ENV_DEFAULT_COLUMNS = 100;
+    static STDIN_CHUNK_BYTES = 256;
+    static STDIN_RETRY_MS = 10;
+    static STDIN_READ_TIMEOUT_MS = 30_000;
     constructor(verbose, statsigService, spinner, logger, customEffectsEnabled) {
         this.verbose = verbose;
         this.customEffectsEnabled = customEffectsEnabled;
         this.spinner = spinner || (0, ora_1.default)({ discardStdin: false });
         this.logger = logger || console;
-        this.promptInternal = inquirer_1.default.createPromptModule({ skipTTYChecks: false });
+        this.promptInternal = this.createPromptModule();
         this.statsigService = statsigService || null;
         this.registerCustomUIElements();
     }
     setStatsigService(statsigService) {
@@ -237,15 +242,15 @@
     }
     emptyLine() {
         this.log('');
     }
-    async confirm(message) {
+    async confirm(message, defaultChoice = false) {
         const { choice } = await this.prompt([
             {
                 type: 'confirm',
                 name: 'choice',
                 message,
-                default: false
+                default: defaultChoice
             }
         ]);
         return choice;
     }
@@ -254,8 +259,46 @@
     }
     promptForSecret(message) {
         return this.promptForString(message, true);
     }
+    readFromStdin() {
+        const chunk = Buffer.alloc(CommandLineUI.STDIN_CHUNK_BYTES);
+        const collected = [];
+        const deadline = Date.now() + CommandLineUI.STDIN_READ_TIMEOUT_MS;
+        for (;;) {
+            let bytesRead;
+            try {
+                bytesRead = fs.readSync(0, chunk, 0, chunk.length, null);
+            }
+            catch (err) {
+                if (err.code === 'EAGAIN') {
+                    if (Date.now() >= deadline) {
+                        throw new Error('Failed to read from stdin: timed out waiting for input');
+                    }
+                    CommandLineUI.sleepSync(CommandLineUI.STDIN_RETRY_MS);
+                    continue;
+                }
+                if (err.code === 'EOF') {
+                    break;
+                }
+                throw new Error(`Failed to read from stdin: ${err.message}`);
+            }
+            if (bytesRead === 0) {
+                break;
+            }
+            const read = chunk.subarray(0, bytesRead);
+            const newlineIndex = read.indexOf(0x0a);
+            if (newlineIndex !== -1) {
+                collected.push(Buffer.from(read.subarray(0, newlineIndex)));
+                break;
+            }
+            collected.push(Buffer.from(read));
+        }
+        return Buffer.concat(collected).toString('utf8').trim();
+    }
+    static sleepSync(milliseconds) {
+        Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, milliseconds);
+    }
     async promptForList(message, choices, config, pageSize) {
         const formattedChoices = config?.format ? choices.map((choice) => config?.format?.(choice)) : choices;
         const { choice } = await this.prompt([
             {
@@ -515,11 +558,62 @@
             }
         ]);
         return text;
     }
-    registerCustomUIElements() {
-        this.promptInternal.registerPrompt('multiple-choice-table', multiple_table_prompt_1.MultipleChoiceTablePrompt);
-        this.promptInternal.registerPrompt('single-choice-table', single_table_prompt_1.SingleChoiceTablePrompt);
+    createPromptModule() {
+        if (process.stdin.isTTY) {
+            return inquirer_1.default.createPromptModule({ skipTTYChecks: false });
+        }
+        return this.createTtyBackedPromptModule();
+    }
+    createTtyBackedPromptModule() {
+        let resolvedPromptModule;
+        const getOrCreatePromptModule = () => {
+            if (resolvedPromptModule)
+                return resolvedPromptModule;
+            resolvedPromptModule = this.openTtyPromptModule();
+            this.registerCustomUIElements(resolvedPromptModule);
+            return resolvedPromptModule;
+        };
+        return Object.assign((questions, initialAnswers) => getOrCreatePromptModule()(questions, initialAnswers), inquirer_1.default.createPromptModule({ skipTTYChecks: true }));
+    }
+    openTtyPromptModule() {
+        let ttyFd;
+        try {
+            ttyFd = fs.openSync('/dev/tty', 'r+');
+        }
+        catch {
+        }
+        if (ttyFd === undefined) {
+            return inquirer_1.default.createPromptModule({ skipTTYChecks: false });
+        }
+        let ttyInput;
+        try {
+            ttyInput = new tty.ReadStream(ttyFd);
+        }
+        catch (err) {
+            try {
+                fs.closeSync(ttyFd);
+            }
+            catch {
+            }
+            throw err;
+        }
+        const ttyPromptModule = inquirer_1.default.createPromptModule({ input: ttyInput, skipTTYChecks: true });
+        return Object.assign((questions, initialAnswers) => {
+            ttyInput.ref();
+            return ttyPromptModule(questions, initialAnswers).then((answers) => {
+                ttyInput.unref();
+                return answers;
+            }, (err) => {
+                ttyInput.unref();
+                throw err;
+            });
+        }, ttyPromptModule);
+    }
+    registerCustomUIElements(target = this.promptInternal) {
+        target.registerPrompt('multiple-choice-table', multiple_table_prompt_1.MultipleChoiceTablePrompt);
+        target.registerPrompt('single-choice-table', single_table_prompt_1.SingleChoiceTablePrompt);
         this.promptInternal.registerPrompt('autocomplete', inquirer_autocomplete_prompt_1.default);
     }
     formatKeyValueList(items, indent, addNewLine) {
         const formattedItems = items.map(({ key, value }) => {