@topcli/prompts 3.1.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 +20 -1
- package/dist/index.d.mts +7 -6
- package/dist/index.mjs +38 -42
- package/package.json +7 -13
- package/dist/index.cjs +0 -1205
- package/dist/index.d.cts +0 -136
- package/dist/index.d.cts.map +0 -1
- package/dist/index.d.mts.map +0 -1
- package/dist/index.mjs.map +0 -1
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)).
|
|
@@ -245,14 +247,30 @@ Use `options.skip` to skip prompt. It will return `options.initial` (`false` by
|
|
|
245
247
|
|
|
246
248
|
The `PromptAgent` class allows to programmatically set the next answers for any prompt function, this can be useful for testing.
|
|
247
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
|
+
|
|
248
254
|
```ts
|
|
249
|
-
const agent = PromptAgent.
|
|
255
|
+
const agent = PromptAgent.shared();
|
|
250
256
|
agent.nextAnswer("John");
|
|
251
257
|
|
|
252
258
|
const input = await question("What's your name?");
|
|
253
259
|
assert.equal(input, "John");
|
|
254
260
|
```
|
|
255
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
|
+
|
|
256
274
|
> [!WARNING]
|
|
257
275
|
> Answers set with `PromptAgent` will **bypass** any logical & validation rules.
|
|
258
276
|
> Examples:
|
|
@@ -311,6 +329,7 @@ export type TransformationResponse<T> = InvalidResponse | ValidTransformationRes
|
|
|
311
329
|
|
|
312
330
|
export interface QuestionOptions<T = string> extends AbstractPromptOptions {
|
|
313
331
|
defaultValue?: string;
|
|
332
|
+
hint?: string;
|
|
314
333
|
validators?: PromptValidator<string>[];
|
|
315
334
|
transformer?: PromptTransformer<T>;
|
|
316
335
|
secure?: boolean | { placeholder: string };
|
package/dist/index.d.mts
CHANGED
|
@@ -34,8 +34,7 @@ declare class PromptAgent<T = string> {
|
|
|
34
34
|
* When not empty, any prompt will be answered by the first answer in this list.
|
|
35
35
|
*/
|
|
36
36
|
nextAnswers: T[];
|
|
37
|
-
static
|
|
38
|
-
constructor(instancier: symbol);
|
|
37
|
+
static shared<T>(): PromptAgent<any>;
|
|
39
38
|
/**
|
|
40
39
|
* Programmatically set the next answer for any prompt (`question()`, `confirm()`, `select()`)
|
|
41
40
|
*
|
|
@@ -43,14 +42,15 @@ declare class PromptAgent<T = string> {
|
|
|
43
42
|
*
|
|
44
43
|
* @example
|
|
45
44
|
* ```js
|
|
46
|
-
* const promptAgent = PromptAgent.
|
|
45
|
+
* const promptAgent = PromptAgent.shared();
|
|
47
46
|
* promptAgent.nextAnswer("toto");
|
|
48
47
|
*
|
|
49
48
|
* const input = await question("what is your name?");
|
|
50
49
|
* assert.equal(input, "toto");
|
|
51
50
|
* ```
|
|
52
51
|
*/
|
|
53
|
-
nextAnswer(value: T): void;
|
|
52
|
+
nextAnswer(value: T | T[]): void;
|
|
53
|
+
clear(): void;
|
|
54
54
|
}
|
|
55
55
|
//#endregion
|
|
56
56
|
//#region src/transformers.d.ts
|
|
@@ -83,11 +83,13 @@ interface AbstractPromptOptions {
|
|
|
83
83
|
message: string;
|
|
84
84
|
skip?: boolean;
|
|
85
85
|
signal?: AbortSignal;
|
|
86
|
+
agent?: PromptAgent<string | boolean>;
|
|
86
87
|
}
|
|
87
88
|
//#endregion
|
|
88
89
|
//#region src/prompts/question.d.ts
|
|
89
90
|
interface QuestionOptions<T = string> extends AbstractPromptOptions {
|
|
90
91
|
defaultValue?: string;
|
|
92
|
+
hint?: string;
|
|
91
93
|
validators?: PromptValidator<string>[];
|
|
92
94
|
transformer?: PromptTransformer<T>;
|
|
93
95
|
secure?: boolean | {
|
|
@@ -135,5 +137,4 @@ declare const transformers: {
|
|
|
135
137
|
url: typeof url;
|
|
136
138
|
};
|
|
137
139
|
//#endregion
|
|
138
|
-
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 };
|
|
139
|
-
//# sourceMappingURL=index.d.mts.map
|
|
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 };
|
package/dist/index.mjs
CHANGED
|
@@ -28,22 +28,16 @@ function resultError(result) {
|
|
|
28
28
|
}
|
|
29
29
|
//#endregion
|
|
30
30
|
//#region src/prompt-agent.ts
|
|
31
|
-
const kPrivateInstancier = Symbol("instancier");
|
|
32
31
|
var PromptAgent = class PromptAgent {
|
|
33
32
|
/**
|
|
34
33
|
* The prompts answers queue.
|
|
35
34
|
* When not empty, any prompt will be answered by the first answer in this list.
|
|
36
35
|
*/
|
|
37
36
|
nextAnswers = [];
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
static agent() {
|
|
43
|
-
return this.#this ??= new PromptAgent(kPrivateInstancier);
|
|
44
|
-
}
|
|
45
|
-
constructor(instancier) {
|
|
46
|
-
if (instancier !== kPrivateInstancier) throw new Error("Cannot instanciate PromptAgent, use PromptAgent.agent() instead");
|
|
37
|
+
static #sharedInstance;
|
|
38
|
+
static shared() {
|
|
39
|
+
this.#sharedInstance ??= new PromptAgent();
|
|
40
|
+
return this.#sharedInstance;
|
|
47
41
|
}
|
|
48
42
|
/**
|
|
49
43
|
* Programmatically set the next answer for any prompt (`question()`, `confirm()`, `select()`)
|
|
@@ -52,7 +46,7 @@ var PromptAgent = class PromptAgent {
|
|
|
52
46
|
*
|
|
53
47
|
* @example
|
|
54
48
|
* ```js
|
|
55
|
-
* const promptAgent = PromptAgent.
|
|
49
|
+
* const promptAgent = PromptAgent.shared();
|
|
56
50
|
* promptAgent.nextAnswer("toto");
|
|
57
51
|
*
|
|
58
52
|
* const input = await question("what is your name?");
|
|
@@ -60,11 +54,11 @@ var PromptAgent = class PromptAgent {
|
|
|
60
54
|
* ```
|
|
61
55
|
*/
|
|
62
56
|
nextAnswer(value) {
|
|
63
|
-
if (Array.isArray(value))
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
this.nextAnswers
|
|
57
|
+
if (Array.isArray(value)) this.nextAnswers.push(...value);
|
|
58
|
+
else this.nextAnswers.push(value);
|
|
59
|
+
}
|
|
60
|
+
clear() {
|
|
61
|
+
this.nextAnswers = [];
|
|
68
62
|
}
|
|
69
63
|
};
|
|
70
64
|
//#endregion
|
|
@@ -105,28 +99,25 @@ function integer() {
|
|
|
105
99
|
}
|
|
106
100
|
function url() {
|
|
107
101
|
return { transform(input) {
|
|
102
|
+
if (URL.canParse(input)) return {
|
|
103
|
+
isValid: true,
|
|
104
|
+
transformed: new URL(input)
|
|
105
|
+
};
|
|
108
106
|
try {
|
|
107
|
+
const parsed = new URL(`https://${input}`);
|
|
108
|
+
if (!parsed.hostname.includes(".") && parsed.hostname !== "localhost") return {
|
|
109
|
+
isValid: false,
|
|
110
|
+
error: "invalid URL"
|
|
111
|
+
};
|
|
109
112
|
return {
|
|
110
113
|
isValid: true,
|
|
111
|
-
transformed:
|
|
114
|
+
transformed: parsed
|
|
112
115
|
};
|
|
113
116
|
} catch {
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
error: "invalid URL"
|
|
119
|
-
};
|
|
120
|
-
return {
|
|
121
|
-
isValid: true,
|
|
122
|
-
transformed: parsed
|
|
123
|
-
};
|
|
124
|
-
} catch {
|
|
125
|
-
return {
|
|
126
|
-
isValid: false,
|
|
127
|
-
error: "invalid URL"
|
|
128
|
-
};
|
|
129
|
-
}
|
|
117
|
+
return {
|
|
118
|
+
isValid: false,
|
|
119
|
+
error: "invalid URL"
|
|
120
|
+
};
|
|
130
121
|
}
|
|
131
122
|
} };
|
|
132
123
|
}
|
|
@@ -157,7 +148,7 @@ var AbstractPrompt = class AbstractPrompt extends EventEmitter {
|
|
|
157
148
|
constructor(options) {
|
|
158
149
|
super();
|
|
159
150
|
if (this.constructor === AbstractPrompt) throw new Error("AbstractPrompt can't be instantiated.");
|
|
160
|
-
const { stdin: input = process.stdin, stdout: output = process.stdout, message, signal, skip = false } = options;
|
|
151
|
+
const { stdin: input = process.stdin, stdout: output = process.stdout, message, signal, skip = false, agent = PromptAgent.shared() } = options;
|
|
161
152
|
if (typeof message !== "string") throw new TypeError(`message must be string, ${typeof message} given.`);
|
|
162
153
|
if (!output.isTTY) Object.assign(output, {
|
|
163
154
|
moveCursor: () => void 0,
|
|
@@ -169,7 +160,7 @@ var AbstractPrompt = class AbstractPrompt extends EventEmitter {
|
|
|
169
160
|
this.signal = signal;
|
|
170
161
|
this.skip = skip;
|
|
171
162
|
this.history = [];
|
|
172
|
-
this.agent =
|
|
163
|
+
this.agent = agent;
|
|
173
164
|
if (this.stdout.isTTY) this.stdin.setRawMode(true);
|
|
174
165
|
this.rl = readline.createInterface({
|
|
175
166
|
input,
|
|
@@ -270,6 +261,7 @@ const SYMBOLS = {
|
|
|
270
261
|
//#region src/prompts/question.ts
|
|
271
262
|
var QuestionPrompt = class extends AbstractPrompt {
|
|
272
263
|
defaultValue;
|
|
264
|
+
hint;
|
|
273
265
|
tip;
|
|
274
266
|
questionSuffixError;
|
|
275
267
|
answer;
|
|
@@ -280,11 +272,12 @@ var QuestionPrompt = class extends AbstractPrompt {
|
|
|
280
272
|
#secure;
|
|
281
273
|
#securePlaceholder = null;
|
|
282
274
|
constructor(options) {
|
|
283
|
-
const { defaultValue, validators = [], transformer, secure = false, ...baseOptions } = options;
|
|
275
|
+
const { defaultValue, hint, validators = [], transformer, secure = false, ...baseOptions } = options;
|
|
284
276
|
super({ ...baseOptions });
|
|
285
277
|
if (validators.length > 0 && transformer !== void 0) throw new Error("validators and transformer are mutually exclusive");
|
|
286
278
|
if (defaultValue && typeof defaultValue !== "string") throw new TypeError("defaultValue must be a string");
|
|
287
279
|
this.defaultValue = defaultValue;
|
|
280
|
+
this.hint = hint;
|
|
288
281
|
this.tip = this.defaultValue ? ` (${this.defaultValue})` : "";
|
|
289
282
|
this.#validators = validators;
|
|
290
283
|
this.#transformer = transformer;
|
|
@@ -294,6 +287,9 @@ var QuestionPrompt = class extends AbstractPrompt {
|
|
|
294
287
|
} else this.#secure = Boolean(secure);
|
|
295
288
|
this.questionSuffixError = "";
|
|
296
289
|
}
|
|
290
|
+
get formattedHint() {
|
|
291
|
+
return this.hint ? styleText("gray", this.hint) : "";
|
|
292
|
+
}
|
|
297
293
|
#question() {
|
|
298
294
|
const { resolve, promise } = Promise.withResolvers();
|
|
299
295
|
const questionQuery = this.#getQuestionQuery();
|
|
@@ -307,10 +303,12 @@ var QuestionPrompt = class extends AbstractPrompt {
|
|
|
307
303
|
return promise;
|
|
308
304
|
}
|
|
309
305
|
#getQuestionQuery() {
|
|
310
|
-
|
|
306
|
+
const hintPart = this.formattedHint ? ` ${this.formattedHint}` : "";
|
|
307
|
+
return `${styleText("bold", `${SYMBOLS.QuestionMark} ${this.message}${this.tip}`)}${hintPart} ${this.questionSuffixError}`;
|
|
311
308
|
}
|
|
312
309
|
#setQuestionSuffixError(error) {
|
|
313
|
-
|
|
310
|
+
const suffix = styleText("red", `[${error}] `);
|
|
311
|
+
this.questionSuffixError = suffix;
|
|
314
312
|
}
|
|
315
313
|
#writeAnswer() {
|
|
316
314
|
const prefix = this.answer ? SYMBOLS.Tick : SYMBOLS.Cross;
|
|
@@ -320,7 +318,7 @@ var QuestionPrompt = class extends AbstractPrompt {
|
|
|
320
318
|
this.write(`${prefix} ${styleText("bold", this.message)} ${SYMBOLS.Pointer} ${stylizedAnswer}${EOL}`);
|
|
321
319
|
}
|
|
322
320
|
#getValidatingQuery(dotCount) {
|
|
323
|
-
return `${styleText("bold", `${SYMBOLS.QuestionMark} ${this.message}${this.tip}`)} ${styleText("yellow", `[validating${".".repeat(dotCount)}]`)}${EOL}`;
|
|
321
|
+
return `${styleText("bold", `${SYMBOLS.QuestionMark} ${this.message}${this.tip}`)}${this.formattedHint ? ` ${this.formattedHint}` : ""} ${styleText("yellow", `[validating${".".repeat(dotCount)}]`)}${EOL}`;
|
|
324
322
|
}
|
|
325
323
|
async #runTransformer() {
|
|
326
324
|
const result = this.#transformer.transform(this.answer);
|
|
@@ -420,7 +418,7 @@ var QuestionPrompt = class extends AbstractPrompt {
|
|
|
420
418
|
};
|
|
421
419
|
//#endregion
|
|
422
420
|
//#region src/prompts/confirm.ts
|
|
423
|
-
const kToggleKeys = new Set([
|
|
421
|
+
const kToggleKeys = /* @__PURE__ */ new Set([
|
|
424
422
|
"left",
|
|
425
423
|
"right",
|
|
426
424
|
"tab",
|
|
@@ -1171,5 +1169,3 @@ const transformers = {
|
|
|
1171
1169
|
};
|
|
1172
1170
|
//#endregion
|
|
1173
1171
|
export { PromptAgent, confirm, multiselect, question, select, transformers, validators };
|
|
1174
|
-
|
|
1175
|
-
//# sourceMappingURL=index.mjs.map
|
package/package.json
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@topcli/prompts",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "4.0.0",
|
|
4
4
|
"description": "Node.js user input library for command-line interfaces.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"scripts": {
|
|
7
|
-
"build": "tsdown src/index.ts --
|
|
7
|
+
"build": "tsdown src/index.ts --dts --clean",
|
|
8
8
|
"prepublishOnly": "npm run build",
|
|
9
9
|
"test-only": "node --test ./test/**/*.test.ts",
|
|
10
|
-
"test-types": "npm run build && tsd && attw --pack .",
|
|
10
|
+
"test-types": "npm run build && tsd && attw --pack . --profile esm-only",
|
|
11
11
|
"test": "c8 -r html npm run test-only && npm run test-types",
|
|
12
12
|
"lint": "eslint src test",
|
|
13
13
|
"lint:fix": "eslint . --fix"
|
|
@@ -21,14 +21,8 @@
|
|
|
21
21
|
"types": "./dist/index.d.mts",
|
|
22
22
|
"exports": {
|
|
23
23
|
".": {
|
|
24
|
-
"
|
|
25
|
-
|
|
26
|
-
"default": "./dist/index.mjs"
|
|
27
|
-
},
|
|
28
|
-
"require": {
|
|
29
|
-
"types": "./dist/index.d.cts",
|
|
30
|
-
"default": "./dist/index.cjs"
|
|
31
|
-
}
|
|
24
|
+
"types": "./dist/index.d.mts",
|
|
25
|
+
"default": "./dist/index.mjs"
|
|
32
26
|
}
|
|
33
27
|
},
|
|
34
28
|
"keywords": [
|
|
@@ -45,10 +39,10 @@
|
|
|
45
39
|
"@arethetypeswrong/cli": "^0.18.2",
|
|
46
40
|
"@openally/config.eslint": "^2.2.0",
|
|
47
41
|
"@openally/config.typescript": "^1.2.1",
|
|
48
|
-
"@types/node": "^
|
|
42
|
+
"@types/node": "^26.0.1",
|
|
49
43
|
"c8": "^11.0.0",
|
|
50
44
|
"tsd": "^0.33.0",
|
|
51
|
-
"tsdown": "0.
|
|
45
|
+
"tsdown": "0.22.3",
|
|
52
46
|
"typescript": "^6.0.2"
|
|
53
47
|
},
|
|
54
48
|
"engines": {
|