Skip to content

Commit

Permalink
configurable strategy
Browse files Browse the repository at this point in the history
  • Loading branch information
pi0 committed Dec 19, 2024
1 parent 554f949 commit 81a03d5
Show file tree
Hide file tree
Showing 2 changed files with 73 additions and 20 deletions.
10 changes: 9 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -82,10 +82,18 @@ Log to all reporters.

Example: `consola.info('Message')`

#### `await prompt(message, { type })`
#### `await prompt(message, { type, cancel })`

Show an input prompt. Type can either of `text`, `confirm`, `select` or `multiselect`.

If prompt is canceled by user (with Ctrol+C), default value will be resolved by default. This strategy can be configured by setting `{ cancel: "..." }` option:

- `"default"` - Resolve the promise with the `default` value or `initial` value.
- `"undefined`" - Resolve the promise with `undefined`.
- `"null"` - Resolve the promise with `null`.
- `"symbol"` - Resolve the promise with a symbol `Symbol.for("cancel")`.
- `"reject"` - Reject the promise with an error.

See [examples/prompt.ts](./examples/prompt.ts) for usage examples.

#### `addReporter(reporter)`
Expand Down
83 changes: 64 additions & 19 deletions src/prompt.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,24 @@ type SelectOption = {
hint?: string;
};

export type TextPromptOptions = {
const kCancel = Symbol.for("cancel");

export type PromptCommonOptions = {
/**
* Specify how to handle a cancelled prompt (e.g. by pressing Ctrl+C).
*
* Default strategy is `"default"`.
*
* - `"default"` - Resolve the promise with the `default` value or `initial` value.
* - `"undefined`" - Resolve the promise with `undefined`.
* - `"null"` - Resolve the promise with `null`.
* - `"symbol"` - Resolve the promise with a symbol `Symbol.for("cancel")`.
* - `"reject"` - Reject the promise with an error.
*/
cancel?: "reject" | "default" | "undefined" | "null" | "symbol";
};

export type TextPromptOptions = PromptCommonOptions & {
/**
* Specifies the prompt type as text.
* @optional
Expand All @@ -33,7 +50,7 @@ export type TextPromptOptions = {
initial?: string;
};

export type ConfirmPromptOptions = {
export type ConfirmPromptOptions = PromptCommonOptions & {
/**
* Specifies the prompt type as confirm.
*/
Expand All @@ -46,7 +63,7 @@ export type ConfirmPromptOptions = {
initial?: boolean;
};

export type SelectPromptOptions = {
export type SelectPromptOptions = PromptCommonOptions & {
/**
* Specifies the prompt type as select.
*/
Expand All @@ -64,7 +81,7 @@ export type SelectPromptOptions = {
options: (string | SelectOption)[];
};

export type MultiSelectOptions = {
export type MultiSelectOptions = PromptCommonOptions & {
/**
* Specifies the prompt type as multiselect.
*/
Expand Down Expand Up @@ -108,6 +125,20 @@ type inferPromptReturnType<T extends PromptOptions> =
? T["options"]
: unknown;

type inferPromptCancalReturnType<T extends PromptOptions> = T extends {
cancel: "reject";
}
? never
: T extends { cancel: "default" }
? inferPromptReturnType<T>
: T extends { cancel: "undefined" }
? undefined
: T extends { cancel: "null" }
? null
: T extends { cancel: "symbol" }
? typeof kCancel
: inferPromptReturnType<T> /* default */;

/**
* Asynchronously prompts the user for input based on specified options.
* Supports text, confirm, select and multi-select prompts.
Expand All @@ -123,7 +154,35 @@ export async function prompt<
>(
message: string,
opts: PromptOptions = {},
): Promise<inferPromptReturnType<T>> {
): Promise<inferPromptReturnType<T> | inferPromptCancalReturnType<T>> {
const handleCancel = (value: unknown) => {
if (
typeof value !== "symbol" ||
value.toString() !== "Symbol(clack:cancel)"
) {
return value;
}

switch (opts.cancel) {
case "reject":

Check failure on line 167 in src/prompt.ts

View workflow job for this annotation

GitHub Actions / ci

Missing braces in case clause
const error = new Error("Prompt cancelled.");

Check failure on line 168 in src/prompt.ts

View workflow job for this annotation

GitHub Actions / ci

Unexpected lexical declaration in case block
error.name = "ConsolaPromptCancelledError";
if (Error.captureStackTrace) {
Error.captureStackTrace(error, prompt);
}
throw error;
case "undefined":

Check failure on line 174 in src/prompt.ts

View workflow job for this annotation

GitHub Actions / ci

Missing braces in case clause
return undefined;
case "null":

Check failure on line 176 in src/prompt.ts

View workflow job for this annotation

GitHub Actions / ci

Missing braces in case clause
return null;
case "symbol":

Check failure on line 178 in src/prompt.ts

View workflow job for this annotation

GitHub Actions / ci

Missing braces in case clause
return kCancel;
default:
case "default":

Check failure on line 181 in src/prompt.ts

View workflow job for this annotation

GitHub Actions / ci

Missing braces in case clause
return (opts as TextPromptOptions).default ?? opts.initial;
}
};

if (!opts.type || opts.type === "text") {
return (await text({
message,
Expand Down Expand Up @@ -163,17 +222,3 @@ export async function prompt<

throw new Error(`Unknown prompt type: ${opts.type}`);
}

function handleCancel(value: any) {
if (
typeof value === "symbol" &&
value.toString() === "Symbol(clack:cancel)"
) {
const error = new Error("Prompt cancelled.");
error.name = "ConsolaPromptCancelledError";
if (Error.captureStackTrace) {
Error.captureStackTrace(error, prompt);
}
throw error;
}
}

0 comments on commit 81a03d5

Please sign in to comment.