@topcli/prompts 3.0.0 → 4.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -55,6 +55,8 @@ Simple prompt, similar to `rl.question()` with an improved UI.
55
55
 
56
56
  Use `options.defaultValue` to set a default value.
57
57
 
58
+ Use `options.hint` to display a hint alongside the question.
59
+
58
60
  Use `options.secure` if you need to hide both input and answer. You can provide either a **boolean** or an **object** which allows to configure a `placeholder` such as `*`.
59
61
 
60
62
  Use `options.signal` to set an `AbortSignal` (throws a [AbortError](#aborterror)).
@@ -170,7 +172,36 @@ Use `options.caseSensitive` to make autocomplete filters case sensitive. Default
170
172
 
171
173
  Use `options.signal` to set an `AbortSignal` (throws a [AbortError](#aborterror)).
172
174
 
173
- Use `options.skip` to skip prompt. It will return the first choice.
175
+ Use `options.skip` to skip prompt. It will return the first non-separator choice.
176
+
177
+ Use `Separator` items in `choices` to visually group options. Separators are not selectable and are skipped during keyboard navigation. When `autocomplete` is active and the user has typed a filter, separators are hidden.
178
+
179
+ ```js
180
+ import { select } from "@topcli/prompts";
181
+
182
+ const framework = await select("Choose a framework", {
183
+ choices: [
184
+ { type: "separator", label: "Frontend" },
185
+ { value: "react", label: "React" },
186
+ { value: "vue", label: "Vue" },
187
+ { type: "separator", label: "Backend" },
188
+ { value: "express", label: "Express" },
189
+ { value: "fastify", label: "Fastify" },
190
+ ]
191
+ });
192
+ ```
193
+
194
+ Use `choice.disabled` to mark a choice as non-selectable.
195
+
196
+ ```js
197
+ const runner = await select("Choose a runner", {
198
+ choices: [
199
+ { value: "node", label: "Node.js" },
200
+ { value: "bun", label: "Bun", disabled: "not available" },
201
+ { value: "deno", label: "Deno", disabled: true }
202
+ ]
203
+ });
204
+ ```
174
205
 
175
206
  ### `multiselect()`
176
207
 
@@ -193,6 +224,10 @@ Use `options.signal` to set an `AbortSignal` (throws a [AbortError](#aborterror)
193
224
 
194
225
  Use `options.skip` to skip prompt. It will return `options.preSelectedChoices` if given, `[]` otherwise.
195
226
 
227
+ Use `Separator` items in `choices` to visually group options. Separators are not selectable and are skipped during keyboard navigation. When `autocomplete` is active and the user has typed a filter, separators are hidden.
228
+
229
+ Use `choice.disabled` to mark a choice as non-selectable.
230
+
196
231
  ### `confirm()`
197
232
 
198
233
  ```ts
@@ -212,14 +247,30 @@ Use `options.skip` to skip prompt. It will return `options.initial` (`false` by
212
247
 
213
248
  The `PromptAgent` class allows to programmatically set the next answers for any prompt function, this can be useful for testing.
214
249
 
250
+ #### Shared instance
251
+
252
+ `PromptAgent.shared()` returns a singleton shared across all prompt calls that do not receive an explicit `agent` option. Suitable for simple test setups, but beware of state leaking between tests.
253
+
215
254
  ```ts
216
- const agent = PromptAgent.agent();
255
+ const agent = PromptAgent.shared();
217
256
  agent.nextAnswer("John");
218
257
 
219
258
  const input = await question("What's your name?");
220
259
  assert.equal(input, "John");
221
260
  ```
222
261
 
262
+ #### Individual instance
263
+
264
+ For better isolation (e.g. in unit tests), create a dedicated `PromptAgent` and pass it via the `agent` option. This avoids any state leaking from the shared instance.
265
+
266
+ ```ts
267
+ const agent = new PromptAgent();
268
+ agent.nextAnswer("John");
269
+
270
+ const input = await question("What's your name?", { agent });
271
+ assert.equal(input, "John");
272
+ ```
273
+
223
274
  > [!WARNING]
224
275
  > Answers set with `PromptAgent` will **bypass** any logical & validation rules.
225
276
  > Examples:
@@ -278,6 +329,7 @@ export type TransformationResponse<T> = InvalidResponse | ValidTransformationRes
278
329
 
279
330
  export interface QuestionOptions<T = string> extends AbstractPromptOptions {
280
331
  defaultValue?: string;
332
+ hint?: string;
281
333
  validators?: PromptValidator<string>[];
282
334
  transformer?: PromptTransformer<T>;
283
335
  secure?: boolean | { placeholder: string };
@@ -287,10 +339,16 @@ export interface Choice<T = any> {
287
339
  value: T;
288
340
  label: string;
289
341
  description?: string;
342
+ disabled?: boolean | string;
343
+ }
344
+
345
+ export interface Separator {
346
+ type: "separator";
347
+ label?: string;
290
348
  }
291
349
 
292
350
  export interface SelectOptions<T extends string> extends AbstractPromptOptions {
293
- choices: (Choice<T> | T)[];
351
+ choices: (Choice<T> | T | Separator)[];
294
352
  maxVisible?: number;
295
353
  ignoreValues?: (T | number | boolean)[];
296
354
  validators?: PromptValidator<string>[];
@@ -299,7 +357,7 @@ export interface SelectOptions<T extends string> extends AbstractPromptOptions {
299
357
  }
300
358
 
301
359
  export interface MultiselectOptions<T extends string> extends AbstractPromptOptions {
302
- choices: (Choice<T> | T)[];
360
+ choices: (Choice<T> | T | Separator)[];
303
361
  maxVisible?: number;
304
362
  preSelectedChoices?: (Choice<T> | T)[];
305
363
  validators?: PromptValidator<string[]>[];
@@ -0,0 +1,140 @@
1
+ import EventEmitter from "node:events";
2
+ import readline from "node:readline";
3
+
4
+ //#region src/validators.d.ts
5
+ type ValidResponseObject = {
6
+ isValid?: true;
7
+ };
8
+ type InvalidResponseObject = {
9
+ isValid: false;
10
+ error: string;
11
+ };
12
+ type ValidationResponseObject = ValidResponseObject | InvalidResponseObject;
13
+ type ValidationResponse = InvalidResponse | ValidResponse;
14
+ type InvalidResponse = string | InvalidResponseObject;
15
+ type ValidResponse = null | undefined | true | ValidResponseObject;
16
+ type ValidTransformationResponse<T> = {
17
+ isValid: true;
18
+ transformed: T;
19
+ };
20
+ type TransformationResponse<T> = InvalidResponse | ValidTransformationResponse<T>;
21
+ interface PromptValidator<T extends string | string[]> {
22
+ validate: (input: T) => ValidationResponse | Promise<ValidationResponse>;
23
+ }
24
+ interface PromptTransformer<T> {
25
+ transform: (input: string) => TransformationResponse<T> | Promise<TransformationResponse<T>>;
26
+ }
27
+ declare function required(): PromptValidator<any>;
28
+ //#endregion
29
+ //#region src/prompt-agent.d.ts
30
+ declare class PromptAgent<T = string> {
31
+ #private;
32
+ /**
33
+ * The prompts answers queue.
34
+ * When not empty, any prompt will be answered by the first answer in this list.
35
+ */
36
+ nextAnswers: T[];
37
+ static shared<T>(): PromptAgent<any>;
38
+ /**
39
+ * Programmatically set the next answer for any prompt (`question()`, `confirm()`, `select()`)
40
+ *
41
+ * This is useful for testing.
42
+ *
43
+ * @example
44
+ * ```js
45
+ * const promptAgent = PromptAgent.shared();
46
+ * promptAgent.nextAnswer("toto");
47
+ *
48
+ * const input = await question("what is your name?");
49
+ * assert.equal(input, "toto");
50
+ * ```
51
+ */
52
+ nextAnswer(value: T | T[]): void;
53
+ clear(): void;
54
+ }
55
+ //#endregion
56
+ //#region src/transformers.d.ts
57
+ declare function number(): PromptTransformer<number>;
58
+ declare function integer(): PromptTransformer<number>;
59
+ declare function url(): PromptTransformer<URL>;
60
+ //#endregion
61
+ //#region src/types.d.ts
62
+ interface Choice<T extends string> {
63
+ value: T;
64
+ label: string;
65
+ description?: string;
66
+ disabled?: boolean | string;
67
+ }
68
+ interface Separator {
69
+ type: "separator";
70
+ label?: string;
71
+ }
72
+ //#endregion
73
+ //#region src/prompts/abstract.d.ts
74
+ type Stdin = NodeJS.ReadStream & {
75
+ fd: 0;
76
+ };
77
+ type Stdout = NodeJS.WriteStream & {
78
+ fd: 1;
79
+ };
80
+ interface AbstractPromptOptions {
81
+ stdin?: Stdin;
82
+ stdout?: Stdout;
83
+ message: string;
84
+ skip?: boolean;
85
+ signal?: AbortSignal;
86
+ agent?: PromptAgent<string | boolean>;
87
+ }
88
+ //#endregion
89
+ //#region src/prompts/question.d.ts
90
+ interface QuestionOptions<T = string> extends AbstractPromptOptions {
91
+ defaultValue?: string;
92
+ hint?: string;
93
+ validators?: PromptValidator<string>[];
94
+ transformer?: PromptTransformer<T>;
95
+ secure?: boolean | {
96
+ placeholder: string;
97
+ };
98
+ }
99
+ //#endregion
100
+ //#region src/prompts/confirm.d.ts
101
+ interface ConfirmOptions extends AbstractPromptOptions {
102
+ initial?: boolean;
103
+ }
104
+ //#endregion
105
+ //#region src/prompts/select.d.ts
106
+ interface SelectOptions<T extends string> extends AbstractPromptOptions {
107
+ choices: (Choice<T> | T | Separator)[];
108
+ maxVisible?: number;
109
+ ignoreValues?: (T | number | boolean)[];
110
+ validators?: PromptValidator<string>[];
111
+ autocomplete?: boolean;
112
+ caseSensitive?: boolean;
113
+ }
114
+ //#endregion
115
+ //#region src/prompts/multiselect.d.ts
116
+ interface MultiselectOptions<T extends string> extends AbstractPromptOptions {
117
+ choices: (Choice<T> | T | Separator)[];
118
+ maxVisible?: number;
119
+ preSelectedChoices?: (Choice<T> | T)[];
120
+ validators?: PromptValidator<string[]>[];
121
+ autocomplete?: boolean;
122
+ caseSensitive?: boolean;
123
+ showHint?: boolean;
124
+ }
125
+ //#endregion
126
+ //#region src/index.d.ts
127
+ declare function question<T = string>(message: string, options?: Omit<QuestionOptions<T>, "message">): Promise<T>;
128
+ declare function select<T extends string>(message: string, options: Omit<SelectOptions<T>, "message">): Promise<T>;
129
+ declare function confirm(message: string, options?: Omit<ConfirmOptions, "message">): Promise<boolean>;
130
+ declare function multiselect<T extends string>(message: string, options: Omit<MultiselectOptions<T>, "message">): Promise<T[]>;
131
+ declare const validators: {
132
+ required: typeof required;
133
+ };
134
+ declare const transformers: {
135
+ number: typeof number;
136
+ integer: typeof integer;
137
+ url: typeof url;
138
+ };
139
+ //#endregion
140
+ export { type AbstractPromptOptions, type Choice, type ConfirmOptions, type InvalidResponse, type InvalidResponseObject, type MultiselectOptions, PromptAgent, type PromptTransformer, type PromptValidator, type QuestionOptions, type SelectOptions, type Separator, type TransformationResponse, type ValidResponse, type ValidResponseObject, type ValidTransformationResponse, type ValidationResponse, type ValidationResponseObject, confirm, multiselect, question, select, transformers, validators };