import { ConfirmPromptConfig, InputPromptConfig, PromptConfig, PromptResult, SelectChoice } from "../schema/prompt.mjs"; import { WriteFn } from "../output/writer.mjs"; import "../output/index.mjs"; //#region src/core/prompt/index.d.ts /** * A select prompt config with choices guaranteed non-empty. * * The resolution chain populates choices from `FlagSchema.enumValues` * when the user's {@linkcode PromptConfig} omits them. */ interface ResolvedSelectPromptConfig { /** Discriminant — single-choice selection prompt. */ readonly kind: 'select'; /** User-facing question text. */ readonly message: string; /** Non-empty list of selectable options (populated from flag enum values when omitted). */ readonly choices: readonly [SelectChoice, ...SelectChoice[]]; } /** * A multiselect prompt config with choices guaranteed non-empty. * * Same guarantee as {@linkcode ResolvedSelectPromptConfig} — choices are always * present and non-empty. */ interface ResolvedMultiselectPromptConfig { /** Discriminant — multiple-choice selection prompt. */ readonly kind: 'multiselect'; /** User-facing question text. */ readonly message: string; /** Non-empty list of selectable options (populated from flag enum values when omitted). */ readonly choices: readonly [SelectChoice, ...SelectChoice[]]; /** Minimum number of selections required (validated after input). */ readonly min?: number; /** Maximum number of selections allowed (validated after input). */ readonly max?: number; } /** * Prompt config variant where select/multiselect choices are guaranteed * present. The prompt engine receives this (not raw {@linkcode PromptConfig}), * so it never needs to merge enum values from `FlagSchema`. * * confirm and input configs pass through unchanged. */ type ResolvedPromptConfig = ConfirmPromptConfig | InputPromptConfig | ResolvedSelectPromptConfig | ResolvedMultiselectPromptConfig; /** * Prompt engine interface. * * Implementations render a single prompt to the user and return the * result. The engine is stateless per call — each `promptOne` is * independent. * * The resolution chain calls `promptOne` for each flag that needs * interactive input. Engines do not need schema knowledge — all * relevant context (message, choices, validation) is in the config. * * @example * ```ts * // Custom engine (e.g. wrapping @clack/prompts) * const engine: PromptEngine = { * async promptOne(config) { * // ... render with your library * return { answered: true, value: userInput }; * } * }; * ``` */ interface PromptEngine { /** * Present a single prompt and return the user's response. * * @param config - The resolved prompt configuration (choices guaranteed * for select/multiselect). * @returns The user's answer, or `{ answered: false }` if cancelled. */ promptOne(config: ResolvedPromptConfig): Promise; } /** * A function that reads a single line of user input. * * Returns `null` on EOF (Ctrl+D on Unix, Ctrl+Z on Windows), * indicating the user closed the input stream (treated as cancel). * * The terminal prompter uses this as its sole input seam. The * resolution chain (prompt-adapter-1) will wire this to the * runtime adapter's stdin. */ type ReadFn = () => Promise; /** * Sentinel value representing a cancelled/aborted prompt in the test * prompter's answer queue. * * Uses `Symbol.for()` for cross-bundle safety — the same symbol is * returned regardless of which copy of the module is loaded. * * @example * ```ts * const prompter = createTestPrompter([ * 'us', // first prompt answered 'us' * PROMPT_CANCEL, // second prompt cancelled * ]); * ``` */ declare const PROMPT_CANCEL: unique symbol; /** * A queued answer consumed by {@link createTestPrompter}. * * The test prompter returns these values exactly as provided; it does not * coerce them. The one exception mirrors the terminal prompter: a string answer * to an `input` prompt is run through the prompt's `validate` function, and a * failing answer is rejected as a cancellation (see {@link createTestPrompter}). * The normal resolution pipeline performs any later type coercion, so tests can * supply values in the same shapes real prompts would yield: * * - `string` for `input` and `select` * - `boolean` for `confirm` * - `string[]` for `multiselect` * - `PROMPT_CANCEL` to simulate user cancellation * * Because the type is intentionally `unknown`, tests may also inject malformed * answers to exercise downstream validation and error reporting. */ type TestAnswer = unknown; /** * Options for `createTestPrompter`. */ interface TestPrompterOptions { /** * Behavior when all answers have been consumed. * * - `'throw'` (default) — throws an error, making the test fail * loudly if more prompts fire than expected. * - `'cancel'` — returns `{ answered: false }`, simulating the * user cancelling all subsequent prompts. */ readonly onExhausted?: 'throw' | 'cancel'; } /** * Create a prompt engine that returns pre-configured answers. * * Each call to `promptOne` consumes the next answer from the queue. * Pass `PROMPT_CANCEL` as an answer to simulate the user cancelling * that prompt. * * For `input` prompts that declare a `validate` function, a string answer is * validated just as the terminal prompter does. An answer that fails validation * is rejected as a cancellation (`{ answered: false }`) — mirroring the terminal * prompter exhausting its retries — so prompt validation is integration-testable * via {@linkcode runCommand} rather than silently accepted as the resolved value. * * @param answers - Ordered queue of answers. Use `PROMPT_CANCEL` for * cancellation. * @param options - Controls behavior when the queue is exhausted. * @returns A {@linkcode PromptEngine} suitable for testing. * * @example * ```ts * const prompter = createTestPrompter(['eu', true, PROMPT_CANCEL]); * * // First promptOne → { answered: true, value: 'eu' } * // Second promptOne → { answered: true, value: true } * // Third promptOne → { answered: false } * ``` */ declare function createTestPrompter(answers: readonly TestAnswer[], options?: TestPrompterOptions): PromptEngine; /** * Create a prompt engine backed by line-based terminal I/O. * * Uses a {@linkcode ReadFn} for input and a {@linkcode WriteFn} for output. This is the * built-in renderer — sufficient for most CLI use cases. For richer * TUI experiences, users can implement {@linkcode PromptEngine} with a library * like `@clack/prompts` or `inquirer`. * * The prompter does **not** use raw mode — all input is line-based. * This keeps the implementation portable across Node, Bun, and Deno * without platform-specific stdin configuration. * * @param read - Line reader function (returns `null` on EOF) * @param write - Output writer function * @returns A {@linkcode PromptEngine} that prompts via terminal I/O * * @example * ```ts * import { createInterface } from 'readline'; * * const rl = createInterface({ input: process.stdin, output: process.stdout }); * const read = () => new Promise((resolve) => { * rl.question('', (answer) => resolve(answer)); * }); * const write: WriteFn = (s) => process.stdout.write(s); * * const prompter = createTerminalPrompter(read, write); * ``` */ declare function createTerminalPrompter(read: ReadFn, write: WriteFn): PromptEngine; /** * Prepare a {@linkcode ResolvedPromptConfig} from a raw {@linkcode PromptConfig} and optional * enum values from the flag schema. * * For select/multiselect prompts without explicit choices, this merges * the flag's enum values into the choices list. Throws if no choices * are available. * * This function is called by the resolution chain (prompt-resolve-1), * not by the engine itself — keeping the engine free of schema knowledge. * * @param config - Raw prompt config from `FlagSchema.prompt` * @param enumValues - Optional enum values from `FlagSchema.enumValues` * @returns Resolved config with choices guaranteed for select kinds * @throws Error if select/multiselect has no choices and no enum values */ declare function resolvePromptConfig(config: PromptConfig, enumValues: readonly string[] | undefined): ResolvedPromptConfig; //#endregion export { PROMPT_CANCEL, type PromptEngine, type ReadFn, type ResolvedMultiselectPromptConfig, type ResolvedPromptConfig, type ResolvedSelectPromptConfig, type TestAnswer, type TestPrompterOptions, createTerminalPrompter, createTestPrompter, resolvePromptConfig };