@topcli/prompts 1.3.1 → 1.5.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 +36 -2
- package/index.d.ts +9 -1
- package/index.js +7 -0
- package/package.json +6 -6
- package/src/abstract-prompt.js +15 -2
- package/src/constants.js +9 -3
- package/src/multiselect-prompt.js +257 -0
- package/src/question-prompt.js +7 -3
- package/src/validators.js +1 -1
package/README.md
CHANGED
|
@@ -27,15 +27,19 @@ $ yarn add @topcli/prompts
|
|
|
27
27
|
You can locally run `node ./demo.js`
|
|
28
28
|
|
|
29
29
|
```js
|
|
30
|
-
import { question, confirm, select } from "@topcli/prompts";
|
|
30
|
+
import { question, confirm, select, multiselect } from "@topcli/prompts";
|
|
31
31
|
|
|
32
32
|
const kTestRunner = ["node", "tap", "tape", "vitest", "mocha", "ava"];
|
|
33
33
|
|
|
34
34
|
const name = await question("Project name ?", { defaultValue: "foo" });
|
|
35
35
|
const runner = await select("Choose a test runner", { choices: kTestRunner, maxVisible: 5 });
|
|
36
36
|
const isCLI = await confirm("Your project is a CLI ?", { initial: true });
|
|
37
|
+
const os = await multiselect("Choose OS", {
|
|
38
|
+
choices: ["linux", "mac", "windows"],
|
|
39
|
+
preSelectedChoices: ["linux"]
|
|
40
|
+
});
|
|
37
41
|
|
|
38
|
-
console.log(name, runner, isCLI);
|
|
42
|
+
console.log(name, runner, isCLI, os);
|
|
39
43
|
```
|
|
40
44
|
|
|
41
45
|
## API
|
|
@@ -47,6 +51,7 @@ question(message: string, options?: PromptOptions): Promise<string>
|
|
|
47
51
|
```
|
|
48
52
|
|
|
49
53
|
Simple prompt, similar to `rl.question()` with an improved UI.
|
|
54
|
+
Use `options.secure` if you need to hide both input and answer.
|
|
50
55
|
Use `options.validators` to handle user input.
|
|
51
56
|
|
|
52
57
|
**Example**
|
|
@@ -83,6 +88,26 @@ select(message: string, options: SelectOptions): Promise<string>
|
|
|
83
88
|
Scrollable select depending `maxVisible` (default `8`).
|
|
84
89
|
Use `ignoreValues` to skip result render & clear lines after a selected one.
|
|
85
90
|
|
|
91
|
+
### `multiselect()`
|
|
92
|
+
|
|
93
|
+
```ts
|
|
94
|
+
multiselect(message: string, options: MultiselectOptions): Promise<[string]>
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
Scrollable multiselect depending `maxVisible` (default `8`).
|
|
98
|
+
Use `preSelectedChoices` to pre-select choices.
|
|
99
|
+
|
|
100
|
+
Use `validators` to handle user input.
|
|
101
|
+
|
|
102
|
+
**Example**
|
|
103
|
+
|
|
104
|
+
```js
|
|
105
|
+
const os = await multiselect('Choose OS', {
|
|
106
|
+
choices: ["linux", "mac", "windows"]
|
|
107
|
+
validators: [required()]
|
|
108
|
+
});
|
|
109
|
+
```
|
|
110
|
+
|
|
86
111
|
### `confirm()`
|
|
87
112
|
|
|
88
113
|
```ts
|
|
@@ -109,6 +134,7 @@ assert.equal(input, "John");
|
|
|
109
134
|
> - When using `question()`, `validators` functions will not be executed.
|
|
110
135
|
> - When using `select()`, the answer can be different from the available choices.
|
|
111
136
|
> - When using `confirm()`, the answer can be any type other than boolean.
|
|
137
|
+
> - etc
|
|
112
138
|
> **Use with caution**
|
|
113
139
|
|
|
114
140
|
## Interfaces
|
|
@@ -131,6 +157,7 @@ export interface Validator {
|
|
|
131
157
|
export interface QuestionOptions extends SharedOptions {
|
|
132
158
|
defaultValue?: string;
|
|
133
159
|
validators?: Validator[];
|
|
160
|
+
secure?: boolean;
|
|
134
161
|
}
|
|
135
162
|
|
|
136
163
|
export interface Choice {
|
|
@@ -145,6 +172,13 @@ export interface SelectOptions extends SharedOptions {
|
|
|
145
172
|
ignoreValues?: (string | number | boolean)[];
|
|
146
173
|
}
|
|
147
174
|
|
|
175
|
+
export interface MultiselectOptions extends SharedOptions {
|
|
176
|
+
choices: (Choice | string)[];
|
|
177
|
+
maxVisible?: number;
|
|
178
|
+
preSelectedChoices?: (Choice | string)[];
|
|
179
|
+
validators?: Validator[];
|
|
180
|
+
}
|
|
181
|
+
|
|
148
182
|
export interface ConfirmOptions extends SharedOptions {
|
|
149
183
|
initial?: boolean;
|
|
150
184
|
}
|
package/index.d.ts
CHANGED
|
@@ -15,9 +15,9 @@ export interface Validator {
|
|
|
15
15
|
export interface QuestionOptions extends SharedOptions {
|
|
16
16
|
defaultValue?: string;
|
|
17
17
|
validators?: Validator[];
|
|
18
|
+
secure?: boolean;
|
|
18
19
|
}
|
|
19
20
|
|
|
20
|
-
|
|
21
21
|
export interface Choice {
|
|
22
22
|
value: any;
|
|
23
23
|
label: string;
|
|
@@ -30,12 +30,20 @@ export interface SelectOptions extends SharedOptions {
|
|
|
30
30
|
ignoreValues?: (string | number | boolean)[];
|
|
31
31
|
}
|
|
32
32
|
|
|
33
|
+
export interface MultiselectOptions extends SharedOptions {
|
|
34
|
+
choices: (Choice | string)[];
|
|
35
|
+
maxVisible?: number;
|
|
36
|
+
preSelectedChoices?: (Choice | string)[];
|
|
37
|
+
validators?: Validator[];
|
|
38
|
+
}
|
|
39
|
+
|
|
33
40
|
export interface ConfirmOptions extends SharedOptions {
|
|
34
41
|
initial?: boolean;
|
|
35
42
|
}
|
|
36
43
|
|
|
37
44
|
export function question(message: string, options?: QuestionOptions): Promise<string>;
|
|
38
45
|
export function select(message: string, options: SelectOptions): Promise<string>;
|
|
46
|
+
export function multiselect(message: string, options: MultiselectOptions): Promise<string[]>;
|
|
39
47
|
export function confirm(message: string, options?: ConfirmOptions): Promise<boolean>;
|
|
40
48
|
|
|
41
49
|
export function required(): Validator;
|
package/index.js
CHANGED
|
@@ -4,6 +4,7 @@ import { SelectPrompt } from "./src/select-prompt.js";
|
|
|
4
4
|
import { QuestionPrompt } from "./src/question-prompt.js";
|
|
5
5
|
import { required } from "./src/validators.js";
|
|
6
6
|
import { PromptAgent } from "./src/prompt-agent.js";
|
|
7
|
+
import { MultiselectPrompt } from "./src/multiselect-prompt.js";
|
|
7
8
|
|
|
8
9
|
export async function question(message, options = {}) {
|
|
9
10
|
const questionPrompt = new QuestionPrompt(message, options);
|
|
@@ -23,4 +24,10 @@ export async function confirm(message, options) {
|
|
|
23
24
|
return confirmPrompt.confirm();
|
|
24
25
|
}
|
|
25
26
|
|
|
27
|
+
export async function multiselect(message, options) {
|
|
28
|
+
const multiselectPrompt = new MultiselectPrompt(message, options);
|
|
29
|
+
|
|
30
|
+
return multiselectPrompt.multiselect();
|
|
31
|
+
}
|
|
32
|
+
|
|
26
33
|
export { required, PromptAgent };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@topcli/prompts",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.5.0",
|
|
4
4
|
"description": "Node.js user input library for command-line interfaces.",
|
|
5
5
|
"scripts": {
|
|
6
6
|
"test": "node --no-warnings=ExperimentalWarning --loader=esmock --test test/",
|
|
@@ -23,11 +23,11 @@
|
|
|
23
23
|
"license": "ISC",
|
|
24
24
|
"type": "module",
|
|
25
25
|
"devDependencies": {
|
|
26
|
-
"@nodesecure/eslint-config": "^1.
|
|
27
|
-
"@types/node": "^20.
|
|
28
|
-
"c8": "^8.0.
|
|
29
|
-
"eslint": "^8.
|
|
30
|
-
"esmock": "^2.
|
|
26
|
+
"@nodesecure/eslint-config": "^1.8.0",
|
|
27
|
+
"@types/node": "^20.8.5",
|
|
28
|
+
"c8": "^8.0.1",
|
|
29
|
+
"eslint": "^8.51.0",
|
|
30
|
+
"esmock": "^2.5.2"
|
|
31
31
|
},
|
|
32
32
|
"dependencies": {
|
|
33
33
|
"is-unicode-supported": "^1.3.0",
|
package/src/abstract-prompt.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
// Import Node.js Dependencies
|
|
2
2
|
import { EOL } from "node:os";
|
|
3
3
|
import { createInterface } from "node:readline";
|
|
4
|
+
import { Writable } from "node:stream";
|
|
4
5
|
|
|
5
6
|
// Import Third-party Dependencies
|
|
6
7
|
import stripAnsi from "strip-ansi";
|
|
@@ -23,11 +24,23 @@ export class AbstractPrompt {
|
|
|
23
24
|
this.message = message;
|
|
24
25
|
this.history = [];
|
|
25
26
|
this.agent = PromptAgent.agent();
|
|
27
|
+
this.mute = false;
|
|
26
28
|
|
|
27
29
|
if (this.stdout.isTTY) {
|
|
28
30
|
this.stdin.setRawMode(true);
|
|
29
31
|
}
|
|
30
|
-
this.rl = createInterface({
|
|
32
|
+
this.rl = createInterface({
|
|
33
|
+
input,
|
|
34
|
+
output: new Writable({
|
|
35
|
+
write: (chunk, encoding, callback) => {
|
|
36
|
+
if (!this.mute) {
|
|
37
|
+
this.stdout.write(chunk, encoding);
|
|
38
|
+
}
|
|
39
|
+
callback();
|
|
40
|
+
}
|
|
41
|
+
}),
|
|
42
|
+
terminal: true
|
|
43
|
+
});
|
|
31
44
|
}
|
|
32
45
|
|
|
33
46
|
write(data) {
|
|
@@ -47,7 +60,7 @@ export class AbstractPrompt {
|
|
|
47
60
|
|
|
48
61
|
const lastLineRows = Math.ceil(stripAnsi(lastLine).length / this.stdout.columns);
|
|
49
62
|
|
|
50
|
-
this.stdout.moveCursor(
|
|
63
|
+
this.stdout.moveCursor(-this.stdout.columns, -lastLineRows);
|
|
51
64
|
this.stdout.clearScreenDown();
|
|
52
65
|
}
|
|
53
66
|
|
package/src/constants.js
CHANGED
|
@@ -7,14 +7,18 @@ const kMainSymbols = {
|
|
|
7
7
|
cross: "✖",
|
|
8
8
|
pointer: "›",
|
|
9
9
|
previous: "⭡",
|
|
10
|
-
next: "⭣"
|
|
10
|
+
next: "⭣",
|
|
11
|
+
active: "●",
|
|
12
|
+
inactive: "○"
|
|
11
13
|
};
|
|
12
14
|
const kFallbackSymbols = {
|
|
13
15
|
tick: "√",
|
|
14
16
|
cross: "×",
|
|
15
17
|
pointer: ">",
|
|
16
18
|
previous: "↑",
|
|
17
|
-
next: "↓"
|
|
19
|
+
next: "↓",
|
|
20
|
+
active: "(+)",
|
|
21
|
+
inactive: "(-)"
|
|
18
22
|
};
|
|
19
23
|
const kSymbols = isUnicodeSupported() ? kMainSymbols : kFallbackSymbols;
|
|
20
24
|
const kPointer = kleur.gray(kSymbols.pointer);
|
|
@@ -27,5 +31,7 @@ export const SYMBOLS = {
|
|
|
27
31
|
Previous: kSymbols.previous,
|
|
28
32
|
Next: kSymbols.next,
|
|
29
33
|
ShowCursor: "\x1B[?25h",
|
|
30
|
-
HideCursor: "\x1B[?25l"
|
|
34
|
+
HideCursor: "\x1B[?25l",
|
|
35
|
+
Active: kleur.cyan(kSymbols.active),
|
|
36
|
+
Inactive: kleur.gray(kSymbols.inactive)
|
|
31
37
|
};
|
|
@@ -0,0 +1,257 @@
|
|
|
1
|
+
// Import Node.js Dependencies
|
|
2
|
+
import { EOL } from "node:os";
|
|
3
|
+
|
|
4
|
+
// Import Third-party Dependencies
|
|
5
|
+
import kleur from "kleur";
|
|
6
|
+
|
|
7
|
+
// Import Internal Dependencies
|
|
8
|
+
import { AbstractPrompt } from "./abstract-prompt.js";
|
|
9
|
+
import { SYMBOLS } from "./constants.js";
|
|
10
|
+
|
|
11
|
+
export class MultiselectPrompt extends AbstractPrompt {
|
|
12
|
+
#validators;
|
|
13
|
+
|
|
14
|
+
activeIndex = 0;
|
|
15
|
+
selectedIndexes = [];
|
|
16
|
+
|
|
17
|
+
get choices() {
|
|
18
|
+
return this.options.choices;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
constructor(message, options) {
|
|
22
|
+
const {
|
|
23
|
+
stdin = process.stdin,
|
|
24
|
+
stdout = process.stdout,
|
|
25
|
+
choices,
|
|
26
|
+
preSelectedChoices,
|
|
27
|
+
validators = []
|
|
28
|
+
} = options ?? {};
|
|
29
|
+
|
|
30
|
+
super(message, stdin, stdout);
|
|
31
|
+
|
|
32
|
+
if (!options) {
|
|
33
|
+
this.destroy();
|
|
34
|
+
throw new TypeError("Missing required options");
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
this.options = options;
|
|
38
|
+
|
|
39
|
+
if (!choices?.length) {
|
|
40
|
+
this.destroy();
|
|
41
|
+
throw new TypeError("Missing required param: choices");
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
this.longestChoice = Math.max(...choices.map((choice) => {
|
|
45
|
+
if (typeof choice === "string") {
|
|
46
|
+
return choice.length;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
const kRequiredChoiceProperties = ["label", "value"];
|
|
50
|
+
|
|
51
|
+
for (const prop of kRequiredChoiceProperties) {
|
|
52
|
+
if (!choice[prop]) {
|
|
53
|
+
this.destroy();
|
|
54
|
+
throw new TypeError(`Missing ${prop} for choice ${JSON.stringify(choice)}`);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
return choice.label.length;
|
|
59
|
+
}));
|
|
60
|
+
|
|
61
|
+
this.#validators = validators;
|
|
62
|
+
|
|
63
|
+
if (!preSelectedChoices) {
|
|
64
|
+
return;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
for (const choice of preSelectedChoices) {
|
|
68
|
+
const choiceIndex = this.choices.findIndex((item) => {
|
|
69
|
+
if (typeof item === "string") {
|
|
70
|
+
return item === choice;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
return item.value === choice;
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
if (choiceIndex === -1) {
|
|
77
|
+
throw new Error(`Invalid pre-selected choice: ${choice.value ?? choice}`);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
this.selectedIndexes.push(choiceIndex);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
#getFormattedChoice(choiceIndex) {
|
|
85
|
+
const choice = this.choices[choiceIndex];
|
|
86
|
+
|
|
87
|
+
if (typeof choice === "string") {
|
|
88
|
+
return { value: choice, label: choice };
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
return choice;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
#getVisibleChoices() {
|
|
95
|
+
const maxVisible = this.options.maxVisible || 8;
|
|
96
|
+
let startIndex = Math.min(this.choices.length - maxVisible, this.activeIndex - Math.floor(maxVisible / 2));
|
|
97
|
+
if (startIndex < 0) {
|
|
98
|
+
startIndex = 0;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
const endIndex = Math.min(startIndex + maxVisible, this.choices.length);
|
|
102
|
+
|
|
103
|
+
return { startIndex, endIndex };
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
#showChoices() {
|
|
107
|
+
const { startIndex, endIndex } = this.#getVisibleChoices();
|
|
108
|
+
this.lastRender = { startIndex, endIndex };
|
|
109
|
+
|
|
110
|
+
for (let choiceIndex = startIndex; choiceIndex < endIndex; choiceIndex++) {
|
|
111
|
+
const choice = this.#getFormattedChoice(choiceIndex);
|
|
112
|
+
const isChoiceActive = choiceIndex === this.activeIndex;
|
|
113
|
+
const isChoiceSelected = this.selectedIndexes.includes(choiceIndex);
|
|
114
|
+
const showPreviousChoicesArrow = startIndex > 0 && choiceIndex === startIndex;
|
|
115
|
+
const showNextChoicesArrow = endIndex < this.choices.length && choiceIndex === endIndex - 1;
|
|
116
|
+
|
|
117
|
+
let prefixArrow = " ";
|
|
118
|
+
if (showPreviousChoicesArrow) {
|
|
119
|
+
prefixArrow = SYMBOLS.Previous + " ";
|
|
120
|
+
}
|
|
121
|
+
else if (showNextChoicesArrow) {
|
|
122
|
+
prefixArrow = SYMBOLS.Next + " ";
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
const prefix = `${prefixArrow}${isChoiceSelected ? SYMBOLS.Active : SYMBOLS.Inactive}`;
|
|
126
|
+
const formattedLabel = choice.label.padEnd(
|
|
127
|
+
this.longestChoice < 10 ? this.longestChoice : 0
|
|
128
|
+
);
|
|
129
|
+
const formattedDescription = choice.description ? ` - ${choice.description}` : "";
|
|
130
|
+
const color = isChoiceActive ? kleur.white().bold : kleur.gray;
|
|
131
|
+
const str = `${prefix} ${color(`${formattedLabel}${formattedDescription}`)}${EOL}`;
|
|
132
|
+
|
|
133
|
+
this.write(str);
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
#showAnsweredQuestion(choices, isAgentAnswer = false) {
|
|
138
|
+
const prefixSymbol = this.selectedIndexes.length === 0 && !isAgentAnswer ? SYMBOLS.Cross : SYMBOLS.Tick;
|
|
139
|
+
const prefix = `${prefixSymbol} ${kleur.bold(this.message)} ${SYMBOLS.Pointer}`;
|
|
140
|
+
const formattedChoice = kleur.yellow(choices);
|
|
141
|
+
|
|
142
|
+
this.write(`${prefix}${choices ? ` ${formattedChoice}` : ""}${EOL}`);
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
async multiselect() {
|
|
146
|
+
if (this.agent.nextAnswers.length > 0) {
|
|
147
|
+
const answer = this.agent.nextAnswers.shift();
|
|
148
|
+
this.#showAnsweredQuestion(answer, true);
|
|
149
|
+
this.destroy();
|
|
150
|
+
|
|
151
|
+
return answer;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
this.write(SYMBOLS.HideCursor);
|
|
155
|
+
this.#showQuestion();
|
|
156
|
+
|
|
157
|
+
const render = (options = {}) => {
|
|
158
|
+
const {
|
|
159
|
+
initialRender = false,
|
|
160
|
+
clearRender = false,
|
|
161
|
+
error = null
|
|
162
|
+
} = options;
|
|
163
|
+
|
|
164
|
+
if (!initialRender) {
|
|
165
|
+
let linesToClear = this.lastRender.endIndex - this.lastRender.startIndex;
|
|
166
|
+
while (linesToClear > 0) {
|
|
167
|
+
this.clearLastLine();
|
|
168
|
+
linesToClear--;
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
if (clearRender) {
|
|
173
|
+
this.stdout.moveCursor(0, -2);
|
|
174
|
+
this.stdout.clearScreenDown();
|
|
175
|
+
|
|
176
|
+
return;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
if (error) {
|
|
180
|
+
this.stdout.moveCursor(0, -2);
|
|
181
|
+
this.stdout.clearScreenDown();
|
|
182
|
+
this.#showQuestion(error);
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
this.#showChoices();
|
|
186
|
+
};
|
|
187
|
+
|
|
188
|
+
render({ initialRender: true });
|
|
189
|
+
|
|
190
|
+
return new Promise((resolve) => {
|
|
191
|
+
const onKeypress = (value, key) => {
|
|
192
|
+
if (key.name === "up") {
|
|
193
|
+
this.activeIndex = this.activeIndex === 0 ? this.choices.length - 1 : this.activeIndex - 1;
|
|
194
|
+
render();
|
|
195
|
+
}
|
|
196
|
+
else if (key.name === "down") {
|
|
197
|
+
this.activeIndex = this.activeIndex === this.choices.length - 1 ? 0 : this.activeIndex + 1;
|
|
198
|
+
render();
|
|
199
|
+
}
|
|
200
|
+
else if (key.name === "a") {
|
|
201
|
+
this.selectedIndexes = this.selectedIndexes.length === this.choices.length ? [] : this.choices.map((_, index) => index);
|
|
202
|
+
render();
|
|
203
|
+
}
|
|
204
|
+
else if (key.name === "space") {
|
|
205
|
+
const isChoiceSelected = this.selectedIndexes.includes(this.activeIndex);
|
|
206
|
+
|
|
207
|
+
if (isChoiceSelected) {
|
|
208
|
+
this.selectedIndexes = this.selectedIndexes.filter((index) => index !== this.activeIndex);
|
|
209
|
+
}
|
|
210
|
+
else {
|
|
211
|
+
this.selectedIndexes.push(this.activeIndex);
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
render();
|
|
215
|
+
}
|
|
216
|
+
else if (key.name === "return") {
|
|
217
|
+
const labels = this.selectedIndexes.map((index) => this.choices[index].label ?? this.choices[index]);
|
|
218
|
+
const values = this.selectedIndexes.map((index) => this.choices[index].value ?? this.choices[index]);
|
|
219
|
+
|
|
220
|
+
for (const validator of this.#validators) {
|
|
221
|
+
if (!validator.validate(values)) {
|
|
222
|
+
const error = validator.error(values);
|
|
223
|
+
render({ error });
|
|
224
|
+
|
|
225
|
+
return;
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
this.stdin.off("keypress", onKeypress);
|
|
230
|
+
|
|
231
|
+
render({ clearRender: true });
|
|
232
|
+
|
|
233
|
+
|
|
234
|
+
this.#showAnsweredQuestion(labels.join(", "));
|
|
235
|
+
|
|
236
|
+
this.write(SYMBOLS.ShowCursor);
|
|
237
|
+
this.destroy();
|
|
238
|
+
|
|
239
|
+
resolve(values);
|
|
240
|
+
}
|
|
241
|
+
};
|
|
242
|
+
|
|
243
|
+
this.stdin.on("keypress", onKeypress);
|
|
244
|
+
});
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
#showQuestion(error = null) {
|
|
248
|
+
let hint = kleur.gray(
|
|
249
|
+
`(Press ${kleur.bold("<a>")} to toggle all, ${kleur.bold("<space>")} to select, ${kleur.bold("<return>")} to submit)`
|
|
250
|
+
);
|
|
251
|
+
if (error) {
|
|
252
|
+
hint += ` ${kleur.red().bold(`[${error}]`)}`;
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
this.write(`${SYMBOLS.QuestionMark} ${kleur.bold(this.message)} ${hint}${EOL}`);
|
|
256
|
+
}
|
|
257
|
+
}
|
package/src/question-prompt.js
CHANGED
|
@@ -10,13 +10,15 @@ import { SYMBOLS } from "./constants.js";
|
|
|
10
10
|
|
|
11
11
|
export class QuestionPrompt extends AbstractPrompt {
|
|
12
12
|
#validators;
|
|
13
|
+
#secure;
|
|
13
14
|
|
|
14
15
|
constructor(message, options = {}) {
|
|
15
16
|
const {
|
|
16
17
|
stdin = process.stdin,
|
|
17
18
|
stdout = process.stdout,
|
|
18
19
|
defaultValue,
|
|
19
|
-
validators = []
|
|
20
|
+
validators = [],
|
|
21
|
+
secure = false
|
|
20
22
|
} = options;
|
|
21
23
|
|
|
22
24
|
super(message, stdin, stdout);
|
|
@@ -28,6 +30,7 @@ export class QuestionPrompt extends AbstractPrompt {
|
|
|
28
30
|
this.defaultValue = defaultValue;
|
|
29
31
|
this.tip = this.defaultValue ? ` (${this.defaultValue})` : "";
|
|
30
32
|
this.#validators = validators;
|
|
33
|
+
this.#secure = Boolean(secure);
|
|
31
34
|
this.questionSuffixError = "";
|
|
32
35
|
}
|
|
33
36
|
|
|
@@ -37,9 +40,11 @@ export class QuestionPrompt extends AbstractPrompt {
|
|
|
37
40
|
|
|
38
41
|
this.rl.question(questionQuery, (answer) => {
|
|
39
42
|
this.history.push(questionQuery + answer);
|
|
43
|
+
this.mute = false;
|
|
40
44
|
|
|
41
45
|
resolve(answer);
|
|
42
46
|
});
|
|
47
|
+
this.mute = this.#secure;
|
|
43
48
|
});
|
|
44
49
|
}
|
|
45
50
|
|
|
@@ -54,8 +59,7 @@ export class QuestionPrompt extends AbstractPrompt {
|
|
|
54
59
|
|
|
55
60
|
#writeAnswer() {
|
|
56
61
|
const prefix = this.answer ? SYMBOLS.Tick : SYMBOLS.Cross;
|
|
57
|
-
const answer = kleur.yellow(this.answer ?? "");
|
|
58
|
-
|
|
62
|
+
const answer = kleur.yellow(this.#secure ? "CONFIDENTIAL" : this.answer ?? "");
|
|
59
63
|
this.write(`${prefix} ${kleur.bold(this.message)} ${SYMBOLS.Pointer} ${answer}${EOL}`);
|
|
60
64
|
}
|
|
61
65
|
|