UNPKG

convex

Version:

Client for the Convex Cloud

155 lines (148 loc) 4.38 kB
import input from "@inquirer/input"; import select from "@inquirer/select"; import search from "@inquirer/search"; import confirm from "@inquirer/confirm"; import { Context } from "../../../bundler/context.js"; import { logOutput } from "../../../bundler/log.js"; /** * Handle ExitPromptError thrown by @inquirer/* packages when the user * presses Ctrl+C. Instead of printing an ugly stack trace, exit cleanly. * For unexpected errors, use ctx.crash to report to Sentry. */ function handlePromptError(ctx: Context) { return async (error: unknown): Promise<never> => { if (error instanceof Error && error.name === "ExitPromptError") { // User pressed Ctrl+C — exit silently with code 130 (standard for SIGINT) process.exit(130); } return ctx.crash({ exitCode: 1, errorType: "fatal", printedMessage: `Unexpected prompt error: ${String(error)}`, errForSentry: error instanceof Error ? error : undefined, }); }; } export const promptString = async ( ctx: Context, options: { message: string; default?: string; }, ): Promise<string> => { if (process.stdin.isTTY) { return input({ message: options.message, ...(options.default !== undefined ? { default: options.default } : {}), }).catch(handlePromptError(ctx)); } else { return ctx.crash({ exitCode: 1, errorType: "fatal", printedMessage: `Cannot prompt for input in non-interactive terminals. (${options.message})`, }); } }; export const promptSecret = async ( ctx: Context, options: { message: string; }, ): Promise<string> => { if (process.stdin.isTTY) { return input({ message: options.message, transformer: (val, { isFinal }) => isFinal ? "*".repeat(val.length) : val, }).catch(handlePromptError(ctx)); } else { return ctx.crash({ exitCode: 1, errorType: "fatal", printedMessage: `Cannot prompt for input in non-interactive terminals. (${options.message})`, }); } }; export const promptOptions = async <V>( ctx: Context, options: { message: string; choices: Array<{ name: string; value: V }>; default?: V; prefix?: string; suffix?: string; }, ): Promise<V> => { if (process.stdin.isTTY) { return select<V>({ message: options.message + (options.suffix ?? ""), choices: options.choices, ...(options.default !== undefined ? { default: options.default } : {}), ...(options.prefix !== undefined ? { theme: { prefix: options.prefix } } : {}), }).catch(handlePromptError(ctx)); } else { return ctx.crash({ exitCode: 1, errorType: "fatal", printedMessage: `Cannot prompt for input in non-interactive terminals. (${options.message})`, }); } }; export const promptSearch = async <V>( ctx: Context, options: { message: string; choices: Array<{ name: string; value: V }>; default?: V; }, ): Promise<V> => { if (process.stdin.isTTY) { return search<V>({ message: options.message, ...(options.default !== undefined ? { default: options.default } : {}), source: (input: string | undefined) => { if (!input) return options.choices; const term = input.toLowerCase(); return options.choices.filter((c) => c.name.toLowerCase().includes(term), ); }, }).catch(handlePromptError(ctx)); } else { return ctx.crash({ exitCode: 1, errorType: "fatal", printedMessage: `Cannot prompt for input in non-interactive terminals. (${options.message})`, }); } }; export const promptYesNo = async ( ctx: Context, options: { message: string; default?: boolean; prefix?: string; nonInteractiveError?: string; }, ): Promise<boolean> => { if (process.stdin.isTTY) { return confirm({ message: options.message, ...(options.default !== undefined ? { default: options.default } : {}), ...(options.prefix !== undefined ? { theme: { prefix: options.prefix } } : {}), }).catch(handlePromptError(ctx)); } else { logOutput(options.message); return ctx.crash({ exitCode: 1, errorType: "fatal", printedMessage: options.nonInteractiveError ?? `Cannot prompt for input in non-interactive terminals. (${options.message})`, }); } };