@topcli/prompts 1.7.0 → 1.8.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/LICENSE +13 -13
- package/README.md +211 -214
- package/dist/index.cjs +871 -0
- package/dist/index.d.cts +77 -0
- package/dist/index.d.ts +77 -0
- package/dist/index.js +830 -0
- package/package.json +55 -43
- package/index.d.ts +0 -77
- package/index.js +0 -30
- package/src/constants.js +0 -39
- package/src/prompt-agent.js +0 -56
- package/src/prompts/abstract.js +0 -68
- package/src/prompts/confirm.js +0 -135
- package/src/prompts/index.js +0 -12
- package/src/prompts/multiselect.js +0 -346
- package/src/prompts/question.js +0 -115
- package/src/prompts/select.js +0 -215
- package/src/utils.js +0 -43
- package/src/validators.js +0 -6
|
@@ -1,346 +0,0 @@
|
|
|
1
|
-
// Import Node.js Dependencies
|
|
2
|
-
import { EOL } from "node:os";
|
|
3
|
-
|
|
4
|
-
// Import Third-party Dependencies
|
|
5
|
-
import kleur from "kleur";
|
|
6
|
-
import wcwidth from "@topcli/wcwidth";
|
|
7
|
-
|
|
8
|
-
// Import Internal Dependencies
|
|
9
|
-
import { AbstractPrompt } from "./abstract.js";
|
|
10
|
-
import { stripAnsi } from "../utils.js";
|
|
11
|
-
import { SYMBOLS } from "../constants.js";
|
|
12
|
-
|
|
13
|
-
// CONSTANTS
|
|
14
|
-
const kRequiredChoiceProperties = ["label", "value"];
|
|
15
|
-
|
|
16
|
-
export class MultiselectPrompt extends AbstractPrompt {
|
|
17
|
-
#boundExitEvent = () => void 0;
|
|
18
|
-
#boundKeyPressEvent = () => void 0;
|
|
19
|
-
#validators;
|
|
20
|
-
|
|
21
|
-
activeIndex = 0;
|
|
22
|
-
selectedIndexes = [];
|
|
23
|
-
questionMessage;
|
|
24
|
-
autocompleteValue = "";
|
|
25
|
-
|
|
26
|
-
get choices() {
|
|
27
|
-
return this.options.choices;
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
get filteredChoices() {
|
|
31
|
-
if (!(this.options.autocomplete && this.autocompleteValue.length > 0)) {
|
|
32
|
-
return this.choices;
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
const isCaseSensitive = this.options.caseSensitive;
|
|
36
|
-
const autocompleteValue = isCaseSensitive ? this.autocompleteValue : this.autocompleteValue.toLowerCase();
|
|
37
|
-
|
|
38
|
-
return this.choices.filter((choice) => this.#filterChoice(choice, autocompleteValue, isCaseSensitive));
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
#filterChoice(choice, autocompleteValue, isCaseSensitive) {
|
|
42
|
-
// eslint-disable-next-line no-nested-ternary
|
|
43
|
-
const choiceValue = typeof choice === "string" ?
|
|
44
|
-
(isCaseSensitive ? choice : choice.toLowerCase()) :
|
|
45
|
-
(isCaseSensitive ? choice.label : choice.label.toLowerCase());
|
|
46
|
-
|
|
47
|
-
if (autocompleteValue.includes(" ")) {
|
|
48
|
-
return this.#filterMultipleWords(choiceValue, autocompleteValue, isCaseSensitive);
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
return choiceValue.includes(autocompleteValue);
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
#filterMultipleWords(choiceValue, autocompleteValue, isCaseSensitive) {
|
|
55
|
-
return autocompleteValue.split(" ").every((word) => {
|
|
56
|
-
const wordValue = isCaseSensitive ? word : word.toLowerCase();
|
|
57
|
-
|
|
58
|
-
return choiceValue.includes(wordValue) || choiceValue.includes(autocompleteValue);
|
|
59
|
-
});
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
get longestChoice() {
|
|
63
|
-
return Math.max(...this.filteredChoices.map((choice) => {
|
|
64
|
-
if (typeof choice === "string") {
|
|
65
|
-
return choice.length;
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
return choice.label.length;
|
|
69
|
-
}));
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
constructor(message, options) {
|
|
73
|
-
const {
|
|
74
|
-
stdin = process.stdin,
|
|
75
|
-
stdout = process.stdout,
|
|
76
|
-
choices,
|
|
77
|
-
preSelectedChoices,
|
|
78
|
-
validators = []
|
|
79
|
-
} = options ?? {};
|
|
80
|
-
|
|
81
|
-
super(message, stdin, stdout);
|
|
82
|
-
|
|
83
|
-
if (!options) {
|
|
84
|
-
this.destroy();
|
|
85
|
-
throw new TypeError("Missing required options");
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
this.options = options;
|
|
89
|
-
|
|
90
|
-
if (!choices?.length) {
|
|
91
|
-
this.destroy();
|
|
92
|
-
throw new TypeError("Missing required param: choices");
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
this.#validators = validators;
|
|
96
|
-
|
|
97
|
-
for (const choice of choices) {
|
|
98
|
-
if (typeof choice === "string") {
|
|
99
|
-
continue;
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
for (const prop of kRequiredChoiceProperties) {
|
|
103
|
-
if (!choice[prop]) {
|
|
104
|
-
this.destroy();
|
|
105
|
-
throw new TypeError(`Missing ${prop} for choice ${JSON.stringify(choice)}`);
|
|
106
|
-
}
|
|
107
|
-
}
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
if (!preSelectedChoices) {
|
|
111
|
-
return;
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
for (const choice of preSelectedChoices) {
|
|
115
|
-
const choiceIndex = this.filteredChoices.findIndex((item) => {
|
|
116
|
-
if (typeof item === "string") {
|
|
117
|
-
return item === choice;
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
return item.value === choice;
|
|
121
|
-
});
|
|
122
|
-
|
|
123
|
-
if (choiceIndex === -1) {
|
|
124
|
-
this.destroy();
|
|
125
|
-
throw new Error(`Invalid pre-selected choice: ${choice.value ?? choice}`);
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
this.selectedIndexes.push(choiceIndex);
|
|
129
|
-
}
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
#getFormattedChoice(choiceIndex) {
|
|
133
|
-
const choice = this.filteredChoices[choiceIndex];
|
|
134
|
-
|
|
135
|
-
if (typeof choice === "string") {
|
|
136
|
-
return { value: choice, label: choice };
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
return choice;
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
#getVisibleChoices() {
|
|
143
|
-
const maxVisible = this.options.maxVisible || 8;
|
|
144
|
-
let startIndex = Math.min(this.filteredChoices.length - maxVisible, this.activeIndex - Math.floor(maxVisible / 2));
|
|
145
|
-
if (startIndex < 0) {
|
|
146
|
-
startIndex = 0;
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
const endIndex = Math.min(startIndex + maxVisible, this.filteredChoices.length);
|
|
150
|
-
|
|
151
|
-
return { startIndex, endIndex };
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
#showChoices() {
|
|
155
|
-
const { startIndex, endIndex } = this.#getVisibleChoices();
|
|
156
|
-
this.lastRender = { startIndex, endIndex };
|
|
157
|
-
|
|
158
|
-
if (this.options.autocomplete) {
|
|
159
|
-
this.write(`${SYMBOLS.Pointer} ${this.autocompleteValue}${EOL}`);
|
|
160
|
-
}
|
|
161
|
-
for (let choiceIndex = startIndex; choiceIndex < endIndex; choiceIndex++) {
|
|
162
|
-
const choice = this.#getFormattedChoice(choiceIndex);
|
|
163
|
-
const isChoiceActive = choiceIndex === this.activeIndex;
|
|
164
|
-
const isChoiceSelected = this.selectedIndexes.includes(choiceIndex);
|
|
165
|
-
const showPreviousChoicesArrow = startIndex > 0 && choiceIndex === startIndex;
|
|
166
|
-
const showNextChoicesArrow = endIndex < this.filteredChoices.length && choiceIndex === endIndex - 1;
|
|
167
|
-
|
|
168
|
-
let prefixArrow = " ";
|
|
169
|
-
if (showPreviousChoicesArrow) {
|
|
170
|
-
prefixArrow = SYMBOLS.Previous + " ";
|
|
171
|
-
}
|
|
172
|
-
else if (showNextChoicesArrow) {
|
|
173
|
-
prefixArrow = SYMBOLS.Next + " ";
|
|
174
|
-
}
|
|
175
|
-
|
|
176
|
-
const prefix = `${prefixArrow}${isChoiceSelected ? SYMBOLS.Active : SYMBOLS.Inactive}`;
|
|
177
|
-
const formattedLabel = choice.label.padEnd(
|
|
178
|
-
this.longestChoice < 10 ? this.longestChoice : 0
|
|
179
|
-
);
|
|
180
|
-
const formattedDescription = choice.description ? ` - ${choice.description}` : "";
|
|
181
|
-
const color = isChoiceActive ? kleur.white().bold : kleur.gray;
|
|
182
|
-
const str = `${prefix} ${color(`${formattedLabel}${formattedDescription}`)}${EOL}`;
|
|
183
|
-
|
|
184
|
-
this.write(str);
|
|
185
|
-
}
|
|
186
|
-
}
|
|
187
|
-
|
|
188
|
-
#showAnsweredQuestion(choices, isAgentAnswer = false) {
|
|
189
|
-
const prefixSymbol = this.selectedIndexes.length === 0 && !isAgentAnswer ? SYMBOLS.Cross : SYMBOLS.Tick;
|
|
190
|
-
const prefix = `${prefixSymbol} ${kleur.bold(this.message)} ${SYMBOLS.Pointer}`;
|
|
191
|
-
const formattedChoice = kleur.yellow(choices);
|
|
192
|
-
|
|
193
|
-
this.write(`${prefix}${choices ? ` ${formattedChoice}` : ""}${EOL}`);
|
|
194
|
-
}
|
|
195
|
-
|
|
196
|
-
#onProcessExit() {
|
|
197
|
-
this.stdin.off("keypress", this.#boundKeyPressEvent);
|
|
198
|
-
this.stdout.moveCursor(-this.stdout.columns, 0);
|
|
199
|
-
this.stdout.clearScreenDown();
|
|
200
|
-
this.write(SYMBOLS.ShowCursor);
|
|
201
|
-
}
|
|
202
|
-
|
|
203
|
-
#onKeypress(...args) {
|
|
204
|
-
const [resolve, render, _, key] = args;
|
|
205
|
-
if (key.name === "up") {
|
|
206
|
-
this.activeIndex = this.activeIndex === 0 ? this.filteredChoices.length - 1 : this.activeIndex - 1;
|
|
207
|
-
render();
|
|
208
|
-
}
|
|
209
|
-
else if (key.name === "down") {
|
|
210
|
-
this.activeIndex = this.activeIndex === this.filteredChoices.length - 1 ? 0 : this.activeIndex + 1;
|
|
211
|
-
render();
|
|
212
|
-
}
|
|
213
|
-
else if (key.ctrl && key.name === "a") {
|
|
214
|
-
// eslint-disable-next-line max-len
|
|
215
|
-
this.selectedIndexes = this.selectedIndexes.length === this.filteredChoices.length ? [] : this.filteredChoices.map((_, index) => index);
|
|
216
|
-
render();
|
|
217
|
-
}
|
|
218
|
-
else if (key.name === "right") {
|
|
219
|
-
this.selectedIndexes.push(this.activeIndex);
|
|
220
|
-
render();
|
|
221
|
-
}
|
|
222
|
-
else if (key.name === "left") {
|
|
223
|
-
this.selectedIndexes = this.selectedIndexes.filter((index) => index !== this.activeIndex);
|
|
224
|
-
render();
|
|
225
|
-
}
|
|
226
|
-
else if (key.name === "return") {
|
|
227
|
-
const labels = this.selectedIndexes.map((index) => this.filteredChoices[index].label ?? this.filteredChoices[index]);
|
|
228
|
-
const values = this.selectedIndexes.map((index) => this.filteredChoices[index].value ?? this.filteredChoices[index]);
|
|
229
|
-
|
|
230
|
-
for (const validator of this.#validators) {
|
|
231
|
-
if (!validator.validate(values)) {
|
|
232
|
-
const error = validator.error(values);
|
|
233
|
-
render({ error });
|
|
234
|
-
|
|
235
|
-
return;
|
|
236
|
-
}
|
|
237
|
-
}
|
|
238
|
-
|
|
239
|
-
render({ clearRender: true });
|
|
240
|
-
|
|
241
|
-
this.#showAnsweredQuestion(labels.join(", "));
|
|
242
|
-
|
|
243
|
-
this.write(SYMBOLS.ShowCursor);
|
|
244
|
-
this.destroy();
|
|
245
|
-
|
|
246
|
-
this.#onProcessExit();
|
|
247
|
-
process.off("exit", this.#boundExitEvent);
|
|
248
|
-
|
|
249
|
-
resolve(values);
|
|
250
|
-
}
|
|
251
|
-
else {
|
|
252
|
-
if (!key.ctrl && this.options.autocomplete) {
|
|
253
|
-
// reset selected choices when user type
|
|
254
|
-
this.selectedIndexes = [];
|
|
255
|
-
this.activeIndex = 0;
|
|
256
|
-
if (key.name === "backspace" && this.autocompleteValue.length > 0) {
|
|
257
|
-
this.autocompleteValue = this.autocompleteValue.slice(0, -1);
|
|
258
|
-
}
|
|
259
|
-
else if (key.name !== "backspace") {
|
|
260
|
-
this.autocompleteValue += key.sequence;
|
|
261
|
-
}
|
|
262
|
-
}
|
|
263
|
-
render();
|
|
264
|
-
}
|
|
265
|
-
}
|
|
266
|
-
|
|
267
|
-
async multiselect() {
|
|
268
|
-
if (this.agent.nextAnswers.length > 0) {
|
|
269
|
-
const answer = this.agent.nextAnswers.shift();
|
|
270
|
-
this.#showAnsweredQuestion(answer, true);
|
|
271
|
-
this.destroy();
|
|
272
|
-
|
|
273
|
-
return answer;
|
|
274
|
-
}
|
|
275
|
-
|
|
276
|
-
this.write(SYMBOLS.HideCursor);
|
|
277
|
-
this.#showQuestion();
|
|
278
|
-
|
|
279
|
-
const render = (options = {}) => {
|
|
280
|
-
const {
|
|
281
|
-
initialRender = false,
|
|
282
|
-
clearRender = false,
|
|
283
|
-
error = null
|
|
284
|
-
} = options;
|
|
285
|
-
|
|
286
|
-
if (!initialRender) {
|
|
287
|
-
let linesToClear = this.lastRender.endIndex - this.lastRender.startIndex;
|
|
288
|
-
while (linesToClear > 0) {
|
|
289
|
-
this.clearLastLine();
|
|
290
|
-
linesToClear--;
|
|
291
|
-
}
|
|
292
|
-
if (this.options.autocomplete) {
|
|
293
|
-
let linesToClear = Math.ceil(
|
|
294
|
-
wcwidth(`${SYMBOLS.Pointer} ${this.autocompleteValue}`) / this.stdout.columns
|
|
295
|
-
);
|
|
296
|
-
while (linesToClear > 0) {
|
|
297
|
-
this.clearLastLine();
|
|
298
|
-
linesToClear--;
|
|
299
|
-
}
|
|
300
|
-
}
|
|
301
|
-
}
|
|
302
|
-
|
|
303
|
-
if (clearRender) {
|
|
304
|
-
const questionLineCount = Math.ceil(
|
|
305
|
-
wcwidth(stripAnsi(this.questionMessage)) / this.stdout.columns
|
|
306
|
-
);
|
|
307
|
-
this.stdout.moveCursor(-this.stdout.columns, -(1 + questionLineCount));
|
|
308
|
-
this.stdout.clearScreenDown();
|
|
309
|
-
|
|
310
|
-
return;
|
|
311
|
-
}
|
|
312
|
-
|
|
313
|
-
if (error) {
|
|
314
|
-
const linesToClear = Math.ceil(wcwidth(this.questionMessage) / this.stdout.columns) + 1;
|
|
315
|
-
this.stdout.moveCursor(0, -linesToClear);
|
|
316
|
-
this.stdout.clearScreenDown();
|
|
317
|
-
this.#showQuestion(error);
|
|
318
|
-
}
|
|
319
|
-
|
|
320
|
-
this.#showChoices();
|
|
321
|
-
};
|
|
322
|
-
|
|
323
|
-
render({ initialRender: true });
|
|
324
|
-
|
|
325
|
-
return new Promise((resolve) => {
|
|
326
|
-
this.#boundKeyPressEvent = this.#onKeypress.bind(this, resolve, render);
|
|
327
|
-
this.stdin.on("keypress", this.#boundKeyPressEvent);
|
|
328
|
-
|
|
329
|
-
this.#boundExitEvent = this.#onProcessExit.bind(this);
|
|
330
|
-
process.once("exit", this.#boundExitEvent);
|
|
331
|
-
});
|
|
332
|
-
}
|
|
333
|
-
|
|
334
|
-
#showQuestion(error = null) {
|
|
335
|
-
let hint = kleur.gray(
|
|
336
|
-
// eslint-disable-next-line max-len
|
|
337
|
-
`(Press ${kleur.bold("<Ctrl+A>")} to toggle all, ${kleur.bold("<Ctrl+Space>")} to select, ${kleur.bold("<Left/Right>")} to toggle, ${kleur.bold("<Return>")} to submit)`
|
|
338
|
-
);
|
|
339
|
-
if (error) {
|
|
340
|
-
hint += ` ${kleur.red().bold(`[${error}]`)}`;
|
|
341
|
-
}
|
|
342
|
-
this.questionMessage = `${SYMBOLS.QuestionMark} ${kleur.bold(this.message)} ${hint}`;
|
|
343
|
-
|
|
344
|
-
this.write(`${this.questionMessage}${EOL}`);
|
|
345
|
-
}
|
|
346
|
-
}
|
package/src/prompts/question.js
DELETED
|
@@ -1,115 +0,0 @@
|
|
|
1
|
-
// Import Node.js Dependencies
|
|
2
|
-
import { EOL } from "node:os";
|
|
3
|
-
|
|
4
|
-
// Import Third-party Dependencies
|
|
5
|
-
import kleur from "kleur";
|
|
6
|
-
import wcwidth from "@topcli/wcwidth";
|
|
7
|
-
|
|
8
|
-
// Import Internal Dependencies
|
|
9
|
-
import { AbstractPrompt } from "./abstract.js";
|
|
10
|
-
import { stripAnsi } from "../utils.js";
|
|
11
|
-
import { SYMBOLS } from "../constants.js";
|
|
12
|
-
|
|
13
|
-
export class QuestionPrompt extends AbstractPrompt {
|
|
14
|
-
#validators;
|
|
15
|
-
#secure;
|
|
16
|
-
|
|
17
|
-
constructor(message, options = {}) {
|
|
18
|
-
const {
|
|
19
|
-
stdin = process.stdin,
|
|
20
|
-
stdout = process.stdout,
|
|
21
|
-
defaultValue,
|
|
22
|
-
validators = [],
|
|
23
|
-
secure = false
|
|
24
|
-
} = options;
|
|
25
|
-
|
|
26
|
-
super(message, stdin, stdout);
|
|
27
|
-
|
|
28
|
-
if (defaultValue && typeof defaultValue !== "string") {
|
|
29
|
-
throw new TypeError("defaultValue must be a string");
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
this.defaultValue = defaultValue;
|
|
33
|
-
this.tip = this.defaultValue ? ` (${this.defaultValue})` : "";
|
|
34
|
-
this.#validators = validators;
|
|
35
|
-
this.#secure = Boolean(secure);
|
|
36
|
-
this.questionSuffixError = "";
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
#question() {
|
|
40
|
-
return new Promise((resolve) => {
|
|
41
|
-
const questionQuery = this.#getQuestionQuery();
|
|
42
|
-
|
|
43
|
-
this.rl.question(questionQuery, (answer) => {
|
|
44
|
-
this.history.push(questionQuery + answer);
|
|
45
|
-
this.mute = false;
|
|
46
|
-
|
|
47
|
-
resolve(answer);
|
|
48
|
-
});
|
|
49
|
-
this.mute = this.#secure;
|
|
50
|
-
});
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
#getQuestionQuery() {
|
|
54
|
-
return `${kleur.bold(`${SYMBOLS.QuestionMark} ${this.message}${this.tip}`)} ${this.questionSuffixError}`;
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
#setQuestionSuffixError(error) {
|
|
58
|
-
const suffix = kleur.red(`[${error}] `);
|
|
59
|
-
this.questionSuffixError = suffix;
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
#writeAnswer() {
|
|
63
|
-
const prefix = this.answer ? SYMBOLS.Tick : SYMBOLS.Cross;
|
|
64
|
-
const answer = kleur.yellow(this.#secure ? "CONFIDENTIAL" : this.answer ?? "");
|
|
65
|
-
this.write(`${prefix} ${kleur.bold(this.message)} ${SYMBOLS.Pointer} ${answer}${EOL}`);
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
#onQuestionAnswer() {
|
|
69
|
-
const questionLineCount = Math.ceil(
|
|
70
|
-
wcwidth(stripAnsi(this.#getQuestionQuery() + this.answer)) / this.stdout.columns
|
|
71
|
-
);
|
|
72
|
-
|
|
73
|
-
this.stdout.moveCursor(-this.stdout.columns, -questionLineCount);
|
|
74
|
-
this.stdout.clearScreenDown();
|
|
75
|
-
|
|
76
|
-
for (const validator of this.#validators) {
|
|
77
|
-
if (!validator.validate(this.answer)) {
|
|
78
|
-
const error = validator.error(this.answer);
|
|
79
|
-
this.#setQuestionSuffixError(error);
|
|
80
|
-
this.answer = this.#question();
|
|
81
|
-
|
|
82
|
-
return;
|
|
83
|
-
}
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
this.#writeAnswer();
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
async question() {
|
|
90
|
-
if (this.agent.nextAnswers.length > 0) {
|
|
91
|
-
this.answer = this.agent.nextAnswers.shift();
|
|
92
|
-
this.#writeAnswer();
|
|
93
|
-
this.destroy();
|
|
94
|
-
|
|
95
|
-
return this.answer;
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
this.answer = await this.#question();
|
|
99
|
-
|
|
100
|
-
if (this.answer === "" && this.defaultValue) {
|
|
101
|
-
this.answer = this.defaultValue;
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
this.#onQuestionAnswer();
|
|
105
|
-
|
|
106
|
-
while (this.answer?.constructor.name === "Promise") {
|
|
107
|
-
this.answer = await this.answer;
|
|
108
|
-
this.#onQuestionAnswer();
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
this.destroy();
|
|
112
|
-
|
|
113
|
-
return this.answer;
|
|
114
|
-
}
|
|
115
|
-
}
|
package/src/prompts/select.js
DELETED
|
@@ -1,215 +0,0 @@
|
|
|
1
|
-
// Import Node.js Dependencies
|
|
2
|
-
import { EOL } from "node:os";
|
|
3
|
-
|
|
4
|
-
// Import Third-party Dependencies
|
|
5
|
-
import kleur from "kleur";
|
|
6
|
-
import wcwidth from "@topcli/wcwidth";
|
|
7
|
-
|
|
8
|
-
// Import Internal Dependencies
|
|
9
|
-
import { AbstractPrompt } from "./abstract.js";
|
|
10
|
-
import { stripAnsi } from "../utils.js";
|
|
11
|
-
import { SYMBOLS } from "../constants.js";
|
|
12
|
-
|
|
13
|
-
export class SelectPrompt extends AbstractPrompt {
|
|
14
|
-
#boundExitEvent = () => void 0;
|
|
15
|
-
#boundKeyPressEvent = () => void 0;
|
|
16
|
-
activeIndex = 0;
|
|
17
|
-
|
|
18
|
-
get choices() {
|
|
19
|
-
return this.options.choices;
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
constructor(message, options) {
|
|
23
|
-
const {
|
|
24
|
-
stdin = process.stdin,
|
|
25
|
-
stdout = process.stdout,
|
|
26
|
-
choices
|
|
27
|
-
} = options ?? {};
|
|
28
|
-
|
|
29
|
-
super(message, stdin, stdout);
|
|
30
|
-
|
|
31
|
-
if (!options) {
|
|
32
|
-
this.destroy();
|
|
33
|
-
throw new TypeError("Missing required options");
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
this.options = options;
|
|
37
|
-
|
|
38
|
-
if (!choices?.length) {
|
|
39
|
-
this.destroy();
|
|
40
|
-
throw new TypeError("Missing required param: choices");
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
this.longestChoice = Math.max(...choices.map((choice) => {
|
|
44
|
-
if (typeof choice === "string") {
|
|
45
|
-
return choice.length;
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
const kRequiredChoiceProperties = ["label", "value"];
|
|
49
|
-
|
|
50
|
-
for (const prop of kRequiredChoiceProperties) {
|
|
51
|
-
if (!choice[prop]) {
|
|
52
|
-
this.destroy();
|
|
53
|
-
throw new TypeError(`Missing ${prop} for choice ${JSON.stringify(choice)}`);
|
|
54
|
-
}
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
return choice.label.length;
|
|
58
|
-
}));
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
#getFormattedChoice(choiceIndex) {
|
|
62
|
-
const choice = this.choices[choiceIndex];
|
|
63
|
-
|
|
64
|
-
if (typeof choice === "string") {
|
|
65
|
-
return { value: choice, label: choice };
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
return choice;
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
#getVisibleChoices() {
|
|
72
|
-
const maxVisible = this.options.maxVisible || 8;
|
|
73
|
-
let startIndex = Math.min(this.choices.length - maxVisible, this.activeIndex - Math.floor(maxVisible / 2));
|
|
74
|
-
if (startIndex < 0) {
|
|
75
|
-
startIndex = 0;
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
const endIndex = Math.min(startIndex + maxVisible, this.choices.length);
|
|
79
|
-
|
|
80
|
-
return { startIndex, endIndex };
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
#showChoices() {
|
|
84
|
-
const { startIndex, endIndex } = this.#getVisibleChoices();
|
|
85
|
-
this.lastRender = { startIndex, endIndex };
|
|
86
|
-
|
|
87
|
-
for (let choiceIndex = startIndex; choiceIndex < endIndex; choiceIndex++) {
|
|
88
|
-
const choice = this.#getFormattedChoice(choiceIndex);
|
|
89
|
-
const isChoiceSelected = choiceIndex === this.activeIndex;
|
|
90
|
-
const showPreviousChoicesArrow = startIndex > 0 && choiceIndex === startIndex;
|
|
91
|
-
const showNextChoicesArrow = endIndex < this.choices.length && choiceIndex === endIndex - 1;
|
|
92
|
-
|
|
93
|
-
let prefixArrow = " ";
|
|
94
|
-
if (showPreviousChoicesArrow) {
|
|
95
|
-
prefixArrow = SYMBOLS.Previous;
|
|
96
|
-
}
|
|
97
|
-
else if (showNextChoicesArrow) {
|
|
98
|
-
prefixArrow = SYMBOLS.Next;
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
const prefix = `${prefixArrow}${isChoiceSelected ? `${SYMBOLS.Pointer} ` : " "}`;
|
|
102
|
-
const formattedLabel = choice.label.padEnd(
|
|
103
|
-
this.longestChoice < 10 ? this.longestChoice : 0
|
|
104
|
-
);
|
|
105
|
-
const formattedDescription = choice.description ? ` - ${choice.description}` : "";
|
|
106
|
-
const color = isChoiceSelected ? kleur.white().bold : kleur.gray;
|
|
107
|
-
const str = color(`${prefix}${formattedLabel}${formattedDescription}${EOL}`);
|
|
108
|
-
|
|
109
|
-
this.write(str);
|
|
110
|
-
}
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
#showAnsweredQuestion(choice) {
|
|
114
|
-
const prefix = `${SYMBOLS.Tick} ${kleur.bold(this.message)} ${SYMBOLS.Pointer}`;
|
|
115
|
-
const formattedChoice = kleur.yellow(choice.label ?? choice);
|
|
116
|
-
|
|
117
|
-
this.write(`${prefix} ${formattedChoice}${EOL}`);
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
#onProcessExit() {
|
|
121
|
-
this.stdin.off("keypress", this.#boundKeyPressEvent);
|
|
122
|
-
this.stdout.moveCursor(-this.stdout.columns, 0);
|
|
123
|
-
this.stdout.clearScreenDown();
|
|
124
|
-
this.write(SYMBOLS.ShowCursor);
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
#onKeypress(...args) {
|
|
128
|
-
const [resolve, render, _, key] = args;
|
|
129
|
-
|
|
130
|
-
if (key.name === "up") {
|
|
131
|
-
this.activeIndex = this.activeIndex === 0 ? this.choices.length - 1 : this.activeIndex - 1;
|
|
132
|
-
render();
|
|
133
|
-
}
|
|
134
|
-
else if (key.name === "down") {
|
|
135
|
-
this.activeIndex = this.activeIndex === this.choices.length - 1 ? 0 : this.activeIndex + 1;
|
|
136
|
-
render();
|
|
137
|
-
}
|
|
138
|
-
else if (key.name === "return") {
|
|
139
|
-
render({ clearRender: true });
|
|
140
|
-
|
|
141
|
-
const currentChoice = this.choices[this.activeIndex];
|
|
142
|
-
const value = currentChoice.value ?? currentChoice;
|
|
143
|
-
|
|
144
|
-
if (!this.options.ignoreValues?.includes(value)) {
|
|
145
|
-
this.#showAnsweredQuestion(currentChoice);
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
this.write(SYMBOLS.ShowCursor);
|
|
149
|
-
this.destroy();
|
|
150
|
-
|
|
151
|
-
this.#onProcessExit();
|
|
152
|
-
process.off("exit", this.#boundExitEvent);
|
|
153
|
-
resolve(value);
|
|
154
|
-
}
|
|
155
|
-
else {
|
|
156
|
-
render();
|
|
157
|
-
}
|
|
158
|
-
}
|
|
159
|
-
|
|
160
|
-
async select() {
|
|
161
|
-
if (this.agent.nextAnswers.length > 0) {
|
|
162
|
-
const answer = this.agent.nextAnswers.shift();
|
|
163
|
-
this.#showAnsweredQuestion(answer);
|
|
164
|
-
this.destroy();
|
|
165
|
-
|
|
166
|
-
return answer;
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
this.write(SYMBOLS.HideCursor);
|
|
170
|
-
this.#showQuestion();
|
|
171
|
-
|
|
172
|
-
const render = (options = {}) => {
|
|
173
|
-
const {
|
|
174
|
-
initialRender = false,
|
|
175
|
-
clearRender = false
|
|
176
|
-
} = options;
|
|
177
|
-
|
|
178
|
-
if (!initialRender) {
|
|
179
|
-
let linesToClear = this.lastRender.endIndex - this.lastRender.startIndex;
|
|
180
|
-
while (linesToClear > 0) {
|
|
181
|
-
this.clearLastLine();
|
|
182
|
-
linesToClear--;
|
|
183
|
-
}
|
|
184
|
-
}
|
|
185
|
-
|
|
186
|
-
if (clearRender) {
|
|
187
|
-
const questionLineCount = Math.ceil(
|
|
188
|
-
wcwidth(stripAnsi(this.questionMessage)) / this.stdout.columns
|
|
189
|
-
);
|
|
190
|
-
|
|
191
|
-
this.stdout.moveCursor(-this.stdout.columns, -(1 + questionLineCount));
|
|
192
|
-
this.stdout.clearScreenDown();
|
|
193
|
-
|
|
194
|
-
return;
|
|
195
|
-
}
|
|
196
|
-
|
|
197
|
-
this.#showChoices();
|
|
198
|
-
};
|
|
199
|
-
|
|
200
|
-
render({ initialRender: true });
|
|
201
|
-
|
|
202
|
-
return new Promise((resolve) => {
|
|
203
|
-
this.#boundKeyPressEvent = this.#onKeypress.bind(this, resolve, render);
|
|
204
|
-
this.stdin.on("keypress", this.#boundKeyPressEvent);
|
|
205
|
-
|
|
206
|
-
this.#boundExitEvent = this.#onProcessExit.bind(this);
|
|
207
|
-
process.once("exit", this.#boundExitEvent);
|
|
208
|
-
});
|
|
209
|
-
}
|
|
210
|
-
|
|
211
|
-
#showQuestion() {
|
|
212
|
-
this.questionMessage = `${SYMBOLS.QuestionMark} ${kleur.bold(this.message)}`;
|
|
213
|
-
this.write(`${this.questionMessage}${EOL}`);
|
|
214
|
-
}
|
|
215
|
-
}
|