@topcli/prompts 2.4.0 → 3.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/dist/index.js CHANGED
@@ -1,16 +1,36 @@
1
- // src/prompts/question.ts
2
- import { EOL as EOL2 } from "node:os";
3
- import { styleText as styleText2 } from "node:util";
1
+ // src/index.ts
2
+ import { once } from "events";
4
3
 
5
- // src/prompts/abstract.ts
6
- import { EOL } from "node:os";
7
- import readline from "node:readline";
8
- import { Writable } from "node:stream";
9
- import EventEmitter from "node:events";
10
- import { stripVTControlCharacters } from "node:util";
4
+ // src/validators.ts
5
+ function required() {
6
+ return {
7
+ validate: (input) => {
8
+ const isValid2 = Array.isArray(input) ? input.length > 0 : Boolean(input);
9
+ return isValid2 ? null : { isValid: isValid2, error: "required" };
10
+ }
11
+ };
12
+ }
13
+ function isValid(result) {
14
+ if (typeof result === "object") {
15
+ return result?.isValid !== false;
16
+ }
17
+ if (typeof result === "string") {
18
+ return result.length === 0;
19
+ }
20
+ return true;
21
+ }
22
+ function isValidTransformation(result) {
23
+ return typeof result === "object" && result.isValid === true;
24
+ }
25
+ function resultError(result) {
26
+ if (typeof result === "object") {
27
+ return result.error;
28
+ }
29
+ return result;
30
+ }
11
31
 
12
32
  // src/prompt-agent.ts
13
- var kPrivateInstancier = Symbol("instancier");
33
+ var kPrivateInstancier = /* @__PURE__ */ Symbol("instancier");
14
34
  var PromptAgent = class _PromptAgent {
15
35
  /**
16
36
  * The prompts answers queue.
@@ -52,6 +72,62 @@ var PromptAgent = class _PromptAgent {
52
72
  }
53
73
  };
54
74
 
75
+ // src/transformers.ts
76
+ function number() {
77
+ return {
78
+ transform(input) {
79
+ if (input.trim() === "") {
80
+ return { isValid: false, error: "not a number" };
81
+ }
82
+ const parsed = Number(input.replace(",", "."));
83
+ if (Number.isNaN(parsed)) {
84
+ return { isValid: false, error: "not a number" };
85
+ }
86
+ return { isValid: true, transformed: parsed };
87
+ }
88
+ };
89
+ }
90
+ function integer() {
91
+ return {
92
+ transform(input) {
93
+ if (input.trim() === "") {
94
+ return { isValid: false, error: "not an integer" };
95
+ }
96
+ const parsed = Number(input);
97
+ if (Number.isNaN(parsed) || !Number.isInteger(parsed)) {
98
+ return { isValid: false, error: "not an integer" };
99
+ }
100
+ return { isValid: true, transformed: parsed };
101
+ }
102
+ };
103
+ }
104
+ function url() {
105
+ return {
106
+ transform(input) {
107
+ try {
108
+ return { isValid: true, transformed: new URL(input) };
109
+ } catch {
110
+ try {
111
+ const parsed = new URL(`https://${input}`);
112
+ if (!parsed.hostname.includes(".") && parsed.hostname !== "localhost") {
113
+ return { isValid: false, error: "invalid URL" };
114
+ }
115
+ return { isValid: true, transformed: parsed };
116
+ } catch {
117
+ return { isValid: false, error: "invalid URL" };
118
+ }
119
+ }
120
+ }
121
+ };
122
+ }
123
+
124
+ // src/prompts/abstract.ts
125
+ import { EOL } from "os";
126
+ import readline from "readline";
127
+ import { Writable } from "stream";
128
+ import EventEmitter from "events";
129
+ import { stripVTControlCharacters } from "util";
130
+
55
131
  // src/errors/abort.ts
56
132
  var AbortError = class extends Error {
57
133
  constructor(message) {
@@ -164,9 +240,13 @@ var AbstractPrompt = class _AbstractPrompt extends EventEmitter {
164
240
  }
165
241
  };
166
242
 
243
+ // src/prompts/question.ts
244
+ import { EOL as EOL2 } from "os";
245
+ import { styleText as styleText2 } from "util";
246
+
167
247
  // src/utils.ts
168
- import process2 from "node:process";
169
- import { stripVTControlCharacters as stripVTControlCharacters2 } from "node:util";
248
+ import process2 from "process";
249
+ import { stripVTControlCharacters as stripVTControlCharacters2 } from "util";
170
250
  var kLenSegmenter = new Intl.Segmenter();
171
251
  function isUnicodeSupported() {
172
252
  if (process2.platform !== "win32") {
@@ -188,7 +268,7 @@ function stringLength(string) {
188
268
  }
189
269
 
190
270
  // src/constants.ts
191
- import { styleText } from "node:util";
271
+ import { styleText } from "util";
192
272
  var kMainSymbols = {
193
273
  tick: "\u2714",
194
274
  cross: "\u2716",
@@ -221,31 +301,7 @@ var SYMBOLS = {
221
301
  Active: styleText("cyan", kSymbols.active),
222
302
  Inactive: styleText("gray", kSymbols.inactive)
223
303
  };
224
-
225
- // src/validators.ts
226
- function required() {
227
- return {
228
- validate: (input) => {
229
- const isValid2 = Array.isArray(input) ? input.length > 0 : Boolean(input);
230
- return isValid2 ? null : { isValid: isValid2, error: "required" };
231
- }
232
- };
233
- }
234
- function isValid(result) {
235
- if (typeof result === "object") {
236
- return result?.isValid !== false;
237
- }
238
- if (typeof result === "string") {
239
- return result.length > 0;
240
- }
241
- return true;
242
- }
243
- function resultError(result) {
244
- if (typeof result === "object") {
245
- return result.error;
246
- }
247
- return result;
248
- }
304
+ var VALIDATION_SPINNER_INTERVAL = 300;
249
305
 
250
306
  // src/prompts/question.ts
251
307
  var QuestionPrompt = class extends AbstractPrompt {
@@ -255,22 +311,29 @@ var QuestionPrompt = class extends AbstractPrompt {
255
311
  answer;
256
312
  answerBuffer;
257
313
  #validators;
314
+ #transformer;
315
+ #transformedAnswer;
258
316
  #secure;
259
317
  #securePlaceholder = null;
260
318
  constructor(options) {
261
319
  const {
262
320
  defaultValue,
263
- validators = [],
321
+ validators: validators2 = [],
322
+ transformer,
264
323
  secure = false,
265
324
  ...baseOptions
266
325
  } = options;
267
326
  super({ ...baseOptions });
327
+ if (validators2.length > 0 && transformer !== void 0) {
328
+ throw new Error("validators and transformer are mutually exclusive");
329
+ }
268
330
  if (defaultValue && typeof defaultValue !== "string") {
269
331
  throw new TypeError("defaultValue must be a string");
270
332
  }
271
333
  this.defaultValue = defaultValue;
272
334
  this.tip = this.defaultValue ? ` (${this.defaultValue})` : "";
273
- this.#validators = validators;
335
+ this.#validators = validators2;
336
+ this.#transformer = transformer;
274
337
  if (typeof secure === "object") {
275
338
  this.#secure = true;
276
339
  this.#securePlaceholder = secure.placeholder;
@@ -280,18 +343,18 @@ var QuestionPrompt = class extends AbstractPrompt {
280
343
  this.questionSuffixError = "";
281
344
  }
282
345
  #question() {
283
- return new Promise((resolve) => {
284
- const questionQuery = this.#getQuestionQuery();
285
- this.history.push(questionQuery);
286
- this.rl.question(questionQuery, (answer) => {
287
- this.history.push(questionQuery + answer);
288
- this.reset();
289
- resolve(answer);
290
- });
291
- if (this.#securePlaceholder !== null) {
292
- this.transformer = (input) => Buffer.from(this.#securePlaceholder.repeat(input.length), "utf-8");
293
- }
346
+ const { resolve, promise } = Promise.withResolvers();
347
+ const questionQuery = this.#getQuestionQuery();
348
+ this.history.push(questionQuery);
349
+ this.rl.question(questionQuery, (answer) => {
350
+ this.history.push(questionQuery + answer);
351
+ this.reset();
352
+ resolve(answer);
294
353
  });
354
+ if (this.#securePlaceholder !== null) {
355
+ this.transformer = (input) => Buffer.from(this.#securePlaceholder.repeat(input.length), "utf-8");
356
+ }
357
+ return promise;
295
358
  }
296
359
  #getQuestionQuery() {
297
360
  return `${styleText2("bold", `${SYMBOLS.QuestionMark} ${this.message}${this.tip}`)} ${this.questionSuffixError}`;
@@ -310,14 +373,68 @@ var QuestionPrompt = class extends AbstractPrompt {
310
373
  );
311
374
  this.write(`${prefix} ${styleText2("bold", this.message)} ${SYMBOLS.Pointer} ${stylizedAnswer}${EOL2}`);
312
375
  }
313
- #onQuestionAnswer() {
376
+ #getValidatingQuery(dotCount) {
377
+ const question2 = styleText2("bold", `${SYMBOLS.QuestionMark} ${this.message}${this.tip}`);
378
+ const hint = styleText2("yellow", `[validating${".".repeat(dotCount)}]`);
379
+ return `${question2} ${hint}${EOL2}`;
380
+ }
381
+ async #runTransformer() {
382
+ const result = this.#transformer.transform(this.answer);
383
+ if (result instanceof Promise) {
384
+ let dotCount = 1;
385
+ this.write(this.#getValidatingQuery(dotCount));
386
+ const spinnerInterval = setInterval(() => {
387
+ dotCount = dotCount % 3 + 1;
388
+ this.clearLastLine();
389
+ this.write(this.#getValidatingQuery(dotCount));
390
+ }, VALIDATION_SPINNER_INTERVAL);
391
+ try {
392
+ return await result;
393
+ } finally {
394
+ clearInterval(spinnerInterval);
395
+ this.clearLastLine();
396
+ }
397
+ }
398
+ return result;
399
+ }
400
+ async #onQuestionAnswer() {
314
401
  const questionLineCount = Math.ceil(
315
402
  stringLength(this.#getQuestionQuery() + this.answer) / this.stdout.columns
316
403
  );
317
404
  this.stdout.moveCursor(-this.stdout.columns, -questionLineCount);
318
405
  this.stdout.clearScreenDown();
406
+ if (this.#transformer) {
407
+ const result = await this.#runTransformer();
408
+ if (isValidTransformation(result) === false) {
409
+ this.#setQuestionSuffixError(resultError(result));
410
+ this.answerBuffer = this.#question();
411
+ return;
412
+ }
413
+ this.#transformedAnswer = result.transformed;
414
+ this.answerBuffer = void 0;
415
+ this.#writeAnswer();
416
+ return;
417
+ }
319
418
  for (const validator of this.#validators) {
320
- const validationResult = validator.validate(this.answer);
419
+ let validationResult;
420
+ const result = validator.validate(this.answer);
421
+ if (result instanceof Promise) {
422
+ let dotCount = 1;
423
+ this.write(this.#getValidatingQuery(dotCount));
424
+ const spinnerInterval = setInterval(() => {
425
+ dotCount = dotCount % 3 + 1;
426
+ this.clearLastLine();
427
+ this.write(this.#getValidatingQuery(dotCount));
428
+ }, VALIDATION_SPINNER_INTERVAL);
429
+ try {
430
+ validationResult = await result;
431
+ } finally {
432
+ clearInterval(spinnerInterval);
433
+ this.clearLastLine();
434
+ }
435
+ } else {
436
+ validationResult = result;
437
+ }
321
438
  if (isValid(validationResult) === false) {
322
439
  this.#setQuestionSuffixError(resultError(validationResult));
323
440
  this.answerBuffer = this.#question();
@@ -327,40 +444,50 @@ var QuestionPrompt = class extends AbstractPrompt {
327
444
  this.answerBuffer = void 0;
328
445
  this.#writeAnswer();
329
446
  }
330
- async question() {
447
+ async listen() {
331
448
  if (this.skip) {
332
449
  this.destroy();
450
+ if (this.#transformer) {
451
+ const rawValue = this.defaultValue ?? "";
452
+ const result = await this.#transformer.transform(rawValue);
453
+ if (isValidTransformation(result) === false) {
454
+ throw new Error(`Transformer failed for default value "${rawValue}": ${resultError(result)}`);
455
+ }
456
+ return result.transformed;
457
+ }
333
458
  return this.defaultValue ?? "";
334
459
  }
335
- return new Promise(async (resolve, reject) => {
336
- this.answer = this.agent.nextAnswers.shift();
337
- if (this.answer !== void 0) {
338
- this.#writeAnswer();
339
- this.destroy();
340
- resolve(this.answer);
341
- return;
342
- }
343
- this.once("error", (error) => {
344
- reject(error);
345
- });
346
- this.answer = await this.#question();
347
- if (this.answer === "" && this.defaultValue) {
348
- this.answer = this.defaultValue;
349
- }
350
- this.#onQuestionAnswer();
351
- while (this.answerBuffer !== void 0) {
352
- this.answer = await this.answerBuffer;
353
- this.#onQuestionAnswer();
354
- }
460
+ const agentAnswer = this.agent.nextAnswers.shift();
461
+ if (agentAnswer !== void 0) {
462
+ this.answer = agentAnswer;
463
+ this.#writeAnswer();
355
464
  this.destroy();
356
- resolve(this.answer);
357
- });
465
+ if (this.#transformer) {
466
+ const result = await this.#transformer.transform(agentAnswer);
467
+ if (isValidTransformation(result) === false) {
468
+ throw new Error(`(PromptAgent) transformer failed for answer "${agentAnswer}": ${resultError(result)}`);
469
+ }
470
+ return result.transformed;
471
+ }
472
+ return this.answer;
473
+ }
474
+ this.answer = await this.#question();
475
+ if (this.answer === "" && this.defaultValue) {
476
+ this.answer = this.defaultValue;
477
+ }
478
+ await this.#onQuestionAnswer();
479
+ while (this.answerBuffer !== void 0) {
480
+ this.answer = await this.answerBuffer;
481
+ await this.#onQuestionAnswer();
482
+ }
483
+ this.destroy();
484
+ return this.#transformedAnswer ?? this.answer;
358
485
  }
359
486
  };
360
487
 
361
488
  // src/prompts/confirm.ts
362
- import { EOL as EOL3 } from "node:os";
363
- import { styleText as styleText3 } from "node:util";
489
+ import { EOL as EOL3 } from "os";
490
+ import { styleText as styleText3 } from "util";
364
491
  var kToggleKeys = /* @__PURE__ */ new Set([
365
492
  "left",
366
493
  "right",
@@ -397,16 +524,6 @@ var ConfirmPrompt = class extends AbstractPrompt {
397
524
  #render() {
398
525
  this.write(this.#getQuestionQuery());
399
526
  }
400
- #question() {
401
- return new Promise((resolve) => {
402
- const questionQuery = this.#getQuestionQuery();
403
- this.write(questionQuery);
404
- this.#boundKeyPressEvent = this.#onKeypress.bind(this, resolve);
405
- this.stdin.on("keypress", this.#boundKeyPressEvent);
406
- this.#boundExitEvent = this.#onProcessExit.bind(this);
407
- process.once("exit", this.#boundExitEvent);
408
- });
409
- }
410
527
  #onKeypress(resolve, _value, key) {
411
528
  this.stdout.moveCursor(
412
529
  -this.stdout.columns,
@@ -449,46 +566,48 @@ var ConfirmPrompt = class extends AbstractPrompt {
449
566
  this.stdout.clearScreenDown();
450
567
  this.write(`${this.selectedValue ? SYMBOLS.Tick : SYMBOLS.Cross} ${styleText3("bold", this.message)}${EOL3}`);
451
568
  }
452
- async confirm() {
569
+ async listen() {
453
570
  if (this.skip) {
454
571
  this.destroy();
455
572
  return this.initial;
456
573
  }
457
- return new Promise(async (resolve, reject) => {
458
- const answer = this.agent.nextAnswers.shift();
459
- if (answer !== void 0) {
460
- this.selectedValue = answer;
461
- this.#onQuestionAnswer();
462
- this.destroy();
463
- resolve(answer);
464
- return;
465
- }
466
- this.once("error", (error) => {
467
- reject(error);
468
- });
469
- this.write(SYMBOLS.HideCursor);
470
- try {
471
- await this.#question();
472
- this.#onQuestionAnswer();
473
- resolve(this.selectedValue);
474
- } finally {
475
- this.write(SYMBOLS.ShowCursor);
476
- this.#onProcessExit();
477
- process.off("exit", this.#boundExitEvent);
478
- this.destroy();
479
- }
480
- });
574
+ const answer = this.agent.nextAnswers.shift();
575
+ if (answer !== void 0) {
576
+ this.selectedValue = answer;
577
+ this.#onQuestionAnswer();
578
+ this.destroy();
579
+ return answer;
580
+ }
581
+ this.write(SYMBOLS.HideCursor);
582
+ try {
583
+ const { resolve, promise } = Promise.withResolvers();
584
+ const questionQuery = this.#getQuestionQuery();
585
+ this.write(questionQuery);
586
+ this.#boundKeyPressEvent = this.#onKeypress.bind(this, resolve);
587
+ this.stdin.on("keypress", this.#boundKeyPressEvent);
588
+ this.#boundExitEvent = this.#onProcessExit.bind(this);
589
+ process.once("exit", this.#boundExitEvent);
590
+ await promise;
591
+ this.#onQuestionAnswer();
592
+ } finally {
593
+ this.write(SYMBOLS.ShowCursor);
594
+ this.#onProcessExit();
595
+ process.off("exit", this.#boundExitEvent);
596
+ this.destroy();
597
+ }
598
+ return this.selectedValue;
481
599
  }
482
600
  };
483
601
 
484
602
  // src/prompts/select.ts
485
- import { EOL as EOL4 } from "node:os";
486
- import { styleText as styleText4 } from "node:util";
603
+ import { EOL as EOL4 } from "os";
604
+ import { styleText as styleText4 } from "util";
487
605
  var kRequiredChoiceProperties = ["label", "value"];
488
606
  var SelectPrompt = class extends AbstractPrompt {
489
607
  #boundExitEvent = () => void 0;
490
608
  #boundKeyPressEvent = () => void 0;
491
609
  #validators;
610
+ #isValidating = false;
492
611
  activeIndex = 0;
493
612
  questionMessage;
494
613
  autocompleteValue = "";
@@ -529,7 +648,7 @@ var SelectPrompt = class extends AbstractPrompt {
529
648
  constructor(options) {
530
649
  const {
531
650
  choices,
532
- validators = [],
651
+ validators: validators2 = [],
533
652
  ...baseOptions
534
653
  } = options;
535
654
  super({ ...baseOptions });
@@ -538,7 +657,7 @@ var SelectPrompt = class extends AbstractPrompt {
538
657
  this.destroy();
539
658
  throw new TypeError("Missing required param: choices");
540
659
  }
541
- this.#validators = validators;
660
+ this.#validators = validators2;
542
661
  for (const choice of choices) {
543
662
  if (typeof choice === "string") {
544
663
  continue;
@@ -594,6 +713,48 @@ var SelectPrompt = class extends AbstractPrompt {
594
713
  this.write(str);
595
714
  }
596
715
  }
716
+ async #handleReturn(resolve, render) {
717
+ this.#isValidating = true;
718
+ try {
719
+ const choice = this.filteredChoices[this.activeIndex] || "";
720
+ const label = typeof choice === "string" ? choice : choice.label;
721
+ const value = typeof choice === "string" ? choice : choice.value;
722
+ for (const validator of this.#validators) {
723
+ let validationResult;
724
+ const result = validator.validate(value);
725
+ if (result instanceof Promise) {
726
+ let dotCount = 1;
727
+ render({ validating: `validating${".".repeat(dotCount)}` });
728
+ const spinnerInterval = setInterval(() => {
729
+ dotCount = dotCount % 3 + 1;
730
+ render({ validating: `validating${".".repeat(dotCount)}` });
731
+ }, VALIDATION_SPINNER_INTERVAL);
732
+ try {
733
+ validationResult = await result;
734
+ } finally {
735
+ clearInterval(spinnerInterval);
736
+ }
737
+ } else {
738
+ validationResult = result;
739
+ }
740
+ if (isValid(validationResult) === false) {
741
+ render({ error: resultError(validationResult) });
742
+ return;
743
+ }
744
+ }
745
+ render({ clearRender: true });
746
+ if (!this.options.ignoreValues?.includes(value)) {
747
+ this.#showAnsweredQuestion(label);
748
+ }
749
+ this.write(SYMBOLS.ShowCursor);
750
+ this.destroy();
751
+ this.#onProcessExit();
752
+ process.off("exit", this.#boundExitEvent);
753
+ resolve(value);
754
+ } finally {
755
+ this.#isValidating = false;
756
+ }
757
+ }
597
758
  #showAnsweredQuestion(label) {
598
759
  const symbolPrefix = label === "" ? SYMBOLS.Cross : SYMBOLS.Tick;
599
760
  const prefix = `${symbolPrefix} ${styleText4("bold", this.message)} ${SYMBOLS.Pointer}`;
@@ -608,6 +769,9 @@ var SelectPrompt = class extends AbstractPrompt {
608
769
  }
609
770
  #onKeypress(...args) {
610
771
  const [resolve, render, , key] = args;
772
+ if (this.#isValidating) {
773
+ return;
774
+ }
611
775
  if (key.name === "up") {
612
776
  this.activeIndex = this.activeIndex === 0 ? this.filteredChoices.length - 1 : this.activeIndex - 1;
613
777
  render();
@@ -615,25 +779,7 @@ var SelectPrompt = class extends AbstractPrompt {
615
779
  this.activeIndex = this.activeIndex === this.filteredChoices.length - 1 ? 0 : this.activeIndex + 1;
616
780
  render();
617
781
  } else if (key.name === "return") {
618
- const choice = this.filteredChoices[this.activeIndex] || "";
619
- const label = typeof choice === "string" ? choice : choice.label;
620
- const value = typeof choice === "string" ? choice : choice.value;
621
- for (const validator of this.#validators) {
622
- const validationResult = validator.validate(value);
623
- if (isValid(validationResult) === false) {
624
- render({ error: resultError(validationResult) });
625
- return;
626
- }
627
- }
628
- render({ clearRender: true });
629
- if (!this.options.ignoreValues?.includes(value)) {
630
- this.#showAnsweredQuestion(label);
631
- }
632
- this.write(SYMBOLS.ShowCursor);
633
- this.destroy();
634
- this.#onProcessExit();
635
- process.off("exit", this.#boundExitEvent);
636
- resolve(value);
782
+ void this.#handleReturn(resolve, render);
637
783
  } else {
638
784
  if (!key.ctrl && this.options.autocomplete) {
639
785
  this.activeIndex = 0;
@@ -646,89 +792,84 @@ var SelectPrompt = class extends AbstractPrompt {
646
792
  render();
647
793
  }
648
794
  }
649
- async select() {
795
+ async listen() {
650
796
  if (this.skip) {
651
797
  this.destroy();
652
- const answer = this.options.choices[0];
653
- return typeof answer === "string" ? answer : answer.value;
798
+ const answer2 = this.options.choices[0];
799
+ return typeof answer2 === "string" ? answer2 : answer2.value;
654
800
  }
655
- return new Promise((resolve, reject) => {
656
- const answer = this.agent.nextAnswers.shift();
657
- if (answer !== void 0) {
658
- this.#showAnsweredQuestion(answer);
659
- this.destroy();
660
- resolve(answer);
661
- return;
662
- }
663
- this.once("error", (error) => {
664
- reject(error);
665
- });
666
- this.write(SYMBOLS.HideCursor);
667
- this.#showQuestion();
668
- const render = (options = {}) => {
669
- const {
670
- initialRender = false,
671
- clearRender = false,
672
- error = null
673
- } = options;
674
- if (!initialRender) {
675
- let linesToClear = this.lastRender.endIndex - this.lastRender.startIndex;
676
- while (linesToClear > 0) {
677
- this.clearLastLine();
678
- linesToClear--;
679
- }
680
- if (this.options.autocomplete) {
681
- let linesToClear2 = Math.ceil(
682
- stringLength(`${SYMBOLS.Pointer} ${this.autocompleteValue}`) / this.stdout.columns
683
- );
684
- while (linesToClear2 > 0) {
685
- this.clearLastLine();
686
- linesToClear2--;
687
- }
688
- }
801
+ const answer = this.agent.nextAnswers.shift();
802
+ if (answer !== void 0) {
803
+ this.#showAnsweredQuestion(answer);
804
+ this.destroy();
805
+ return answer;
806
+ }
807
+ this.transformer = () => null;
808
+ this.write(SYMBOLS.HideCursor);
809
+ this.#showQuestion();
810
+ const render = (options = {}) => {
811
+ const {
812
+ initialRender = false,
813
+ clearRender = false,
814
+ error = null,
815
+ validating = null
816
+ } = options;
817
+ if (!initialRender) {
818
+ let linesToClear = this.lastRender.endIndex - this.lastRender.startIndex;
819
+ while (linesToClear > 0) {
820
+ this.clearLastLine();
821
+ linesToClear--;
689
822
  }
690
- if (clearRender) {
691
- const questionLineCount = Math.ceil(
692
- stringLength(this.questionMessage) / this.stdout.columns
823
+ if (this.options.autocomplete) {
824
+ let linesToClear2 = Math.ceil(
825
+ stringLength(`${SYMBOLS.Pointer} ${this.autocompleteValue}`) / this.stdout.columns
693
826
  );
694
- this.stdout.moveCursor(-this.stdout.columns, -(1 + questionLineCount));
695
- this.stdout.clearScreenDown();
696
- return;
697
- }
698
- if (error) {
699
- const linesToClear = Math.ceil(stringLength(this.questionMessage) / this.stdout.columns) + 1;
700
- this.stdout.moveCursor(0, -linesToClear);
701
- this.stdout.clearScreenDown();
702
- this.#showQuestion(error);
827
+ while (linesToClear2 > 0) {
828
+ this.clearLastLine();
829
+ linesToClear2--;
830
+ }
703
831
  }
704
- this.#showChoices();
705
- };
706
- render({ initialRender: true });
707
- this.#boundKeyPressEvent = this.#onKeypress.bind(this, resolve, render);
708
- this.stdin.on("keypress", this.#boundKeyPressEvent);
709
- this.#boundExitEvent = this.#onProcessExit.bind(this);
710
- process.once("exit", this.#boundExitEvent);
711
- });
832
+ }
833
+ if (clearRender) {
834
+ this.clearLastLine();
835
+ return;
836
+ }
837
+ if (error || validating) {
838
+ this.clearLastLine();
839
+ this.#showQuestion(error, validating);
840
+ }
841
+ this.#showChoices();
842
+ };
843
+ render({ initialRender: true });
844
+ this.#boundExitEvent = this.#onProcessExit.bind(this);
845
+ process.once("exit", this.#boundExitEvent);
846
+ const { resolve, promise } = Promise.withResolvers();
847
+ this.#boundKeyPressEvent = this.#onKeypress.bind(this, resolve, render);
848
+ this.stdin.on("keypress", this.#boundKeyPressEvent);
849
+ return promise;
712
850
  }
713
- #showQuestion(error = null) {
851
+ #showQuestion(error = null, validating = null) {
714
852
  let hint = "";
715
- if (error) {
716
- hint = ` ${hint.length > 0 ? " " : ""}${styleText4(["red", "bold"], `[${error}]`)}`;
853
+ if (validating) {
854
+ hint = styleText4("yellow", `[${validating}]`);
855
+ } else if (error) {
856
+ hint += `${hint.length > 0 ? " " : ""}${styleText4(["red", "bold"], `[${error}]`)}`;
717
857
  }
718
- this.questionMessage = `${SYMBOLS.QuestionMark} ${styleText4("bold", this.message)}${hint}`;
858
+ this.questionMessage = `${SYMBOLS.QuestionMark} ${styleText4("bold", this.message)}${hint.length > 0 ? ` ${hint}` : ""}`;
719
859
  this.write(`${this.questionMessage}${EOL4}`);
720
860
  }
721
861
  };
722
862
 
723
863
  // src/prompts/multiselect.ts
724
- import { EOL as EOL5 } from "node:os";
725
- import { styleText as styleText5 } from "node:util";
864
+ import { EOL as EOL5 } from "os";
865
+ import { styleText as styleText5 } from "util";
726
866
  var kRequiredChoiceProperties2 = ["label", "value"];
727
867
  var MultiselectPrompt = class extends AbstractPrompt {
728
868
  #boundExitEvent = () => void 0;
729
869
  #boundKeyPressEvent = () => void 0;
730
870
  #validators;
731
871
  #showHint;
872
+ #isValidating = false;
732
873
  activeIndex = 0;
733
874
  selectedIndexes = /* @__PURE__ */ new Set();
734
875
  questionMessage;
@@ -771,7 +912,7 @@ var MultiselectPrompt = class extends AbstractPrompt {
771
912
  const {
772
913
  choices,
773
914
  preSelectedChoices = [],
774
- validators = [],
915
+ validators: validators2 = [],
775
916
  showHint = true,
776
917
  ...baseOptions
777
918
  } = options;
@@ -781,7 +922,7 @@ var MultiselectPrompt = class extends AbstractPrompt {
781
922
  this.destroy();
782
923
  throw new TypeError("Missing required param: choices");
783
924
  }
784
- this.#validators = validators;
925
+ this.#validators = validators2;
785
926
  this.#showHint = showHint;
786
927
  for (const choice of choices) {
787
928
  if (typeof choice === "string") {
@@ -852,6 +993,44 @@ var MultiselectPrompt = class extends AbstractPrompt {
852
993
  this.write(str);
853
994
  }
854
995
  }
996
+ async #handleReturn(resolve, render) {
997
+ this.#isValidating = true;
998
+ try {
999
+ const { values, labels } = this.#selectedChoices();
1000
+ for (const validator of this.#validators) {
1001
+ let validationResult;
1002
+ const result = validator.validate(values);
1003
+ if (result instanceof Promise) {
1004
+ let dotCount = 1;
1005
+ render({ validating: `validating${".".repeat(dotCount)}` });
1006
+ const spinnerInterval = setInterval(() => {
1007
+ dotCount = dotCount % 3 + 1;
1008
+ render({ validating: `validating${".".repeat(dotCount)}` });
1009
+ }, VALIDATION_SPINNER_INTERVAL);
1010
+ try {
1011
+ validationResult = await result;
1012
+ } finally {
1013
+ clearInterval(spinnerInterval);
1014
+ }
1015
+ } else {
1016
+ validationResult = result;
1017
+ }
1018
+ if (isValid(validationResult) === false) {
1019
+ render({ error: resultError(validationResult) });
1020
+ return;
1021
+ }
1022
+ }
1023
+ render({ clearRender: true });
1024
+ this.#showAnsweredQuestion(labels.join(", "));
1025
+ this.write(SYMBOLS.ShowCursor);
1026
+ this.destroy();
1027
+ this.#onProcessExit();
1028
+ process.off("exit", this.#boundExitEvent);
1029
+ resolve(values);
1030
+ } finally {
1031
+ this.#isValidating = false;
1032
+ }
1033
+ }
855
1034
  #showAnsweredQuestion(choices, isAgentAnswer = false) {
856
1035
  const prefixSymbol = this.selectedIndexes.size === 0 && !isAgentAnswer ? SYMBOLS.Cross : SYMBOLS.Tick;
857
1036
  const prefix = `${prefixSymbol} ${styleText5("bold", this.message)} ${SYMBOLS.Pointer}`;
@@ -885,6 +1064,9 @@ var MultiselectPrompt = class extends AbstractPrompt {
885
1064
  }
886
1065
  #onKeypress(...args) {
887
1066
  const [resolve, render, , key] = args;
1067
+ if (this.#isValidating) {
1068
+ return;
1069
+ }
888
1070
  if (key.name === "up") {
889
1071
  this.activeIndex = this.activeIndex === 0 ? this.filteredChoices.length - 1 : this.activeIndex - 1;
890
1072
  render();
@@ -901,21 +1083,7 @@ var MultiselectPrompt = class extends AbstractPrompt {
901
1083
  this.selectedIndexes = new Set([...this.selectedIndexes].filter((index) => index !== this.activeIndex));
902
1084
  render();
903
1085
  } else if (key.name === "return") {
904
- const { values, labels } = this.#selectedChoices();
905
- for (const validator of this.#validators) {
906
- const validationResult = validator.validate(values);
907
- if (isValid(validationResult) === false) {
908
- render({ error: resultError(validationResult) });
909
- return;
910
- }
911
- }
912
- render({ clearRender: true });
913
- this.#showAnsweredQuestion(labels.join(", "));
914
- this.write(SYMBOLS.ShowCursor);
915
- this.destroy();
916
- this.#onProcessExit();
917
- process.off("exit", this.#boundExitEvent);
918
- resolve(values);
1086
+ void this.#handleReturn(resolve, render);
919
1087
  } else {
920
1088
  if (!key.ctrl && this.options.autocomplete) {
921
1089
  this.selectedIndexes.clear();
@@ -929,78 +1097,72 @@ var MultiselectPrompt = class extends AbstractPrompt {
929
1097
  render();
930
1098
  }
931
1099
  }
932
- async multiselect() {
1100
+ async listen() {
933
1101
  if (this.skip) {
934
1102
  this.destroy();
935
1103
  const { values } = this.#selectedChoices();
936
1104
  return values;
937
1105
  }
938
- return new Promise((resolve, reject) => {
939
- const answer = this.agent.nextAnswers.shift();
940
- if (answer !== void 0) {
941
- const formatedAnser = Array.isArray(answer) ? answer.join(", ") : answer;
942
- this.#showAnsweredQuestion(formatedAnser, true);
943
- this.destroy();
944
- resolve(Array.isArray(answer) ? answer : [answer]);
945
- return;
946
- }
947
- this.once("error", (error) => {
948
- reject(error);
949
- });
950
- this.write(SYMBOLS.HideCursor);
951
- this.#showQuestion();
952
- const render = (options = {}) => {
953
- const {
954
- initialRender = false,
955
- clearRender = false,
956
- error = null
957
- } = options;
958
- if (!initialRender) {
959
- let linesToClear = this.lastRender.endIndex - this.lastRender.startIndex;
960
- while (linesToClear > 0) {
961
- this.clearLastLine();
962
- linesToClear--;
963
- }
964
- if (this.options.autocomplete) {
965
- let linesToClear2 = Math.ceil(
966
- stringLength(`${SYMBOLS.Pointer} ${this.autocompleteValue}`) / this.stdout.columns
967
- );
968
- while (linesToClear2 > 0) {
969
- this.clearLastLine();
970
- linesToClear2--;
971
- }
972
- }
1106
+ const answer = this.agent.nextAnswers.shift();
1107
+ if (answer !== void 0) {
1108
+ const formatedAnser = Array.isArray(answer) ? answer.join(", ") : answer;
1109
+ this.#showAnsweredQuestion(formatedAnser, true);
1110
+ this.destroy();
1111
+ return Array.isArray(answer) ? answer : [answer];
1112
+ }
1113
+ this.transformer = () => null;
1114
+ this.write(SYMBOLS.HideCursor);
1115
+ this.#showQuestion();
1116
+ const render = (options = {}) => {
1117
+ const {
1118
+ initialRender = false,
1119
+ clearRender = false,
1120
+ error = null,
1121
+ validating = null
1122
+ } = options;
1123
+ if (!initialRender) {
1124
+ let linesToClear = this.lastRender.endIndex - this.lastRender.startIndex;
1125
+ while (linesToClear > 0) {
1126
+ this.clearLastLine();
1127
+ linesToClear--;
973
1128
  }
974
- if (clearRender) {
975
- const questionLineCount = Math.ceil(
976
- stringLength(this.questionMessage) / this.stdout.columns
1129
+ if (this.options.autocomplete) {
1130
+ let linesToClear2 = Math.ceil(
1131
+ stringLength(`${SYMBOLS.Pointer} ${this.autocompleteValue}`) / this.stdout.columns
977
1132
  );
978
- this.stdout.moveCursor(-this.stdout.columns, -(1 + questionLineCount));
979
- this.stdout.clearScreenDown();
980
- return;
981
- }
982
- if (error) {
983
- const linesToClear = Math.ceil(stringLength(this.questionMessage) / this.stdout.columns) + 1;
984
- this.stdout.moveCursor(0, -linesToClear);
985
- this.stdout.clearScreenDown();
986
- this.#showQuestion(error);
1133
+ while (linesToClear2 > 0) {
1134
+ this.clearLastLine();
1135
+ linesToClear2--;
1136
+ }
987
1137
  }
988
- this.#showChoices();
989
- };
990
- render({ initialRender: true });
991
- this.#boundKeyPressEvent = this.#onKeypress.bind(this, resolve, render);
992
- this.stdin.on("keypress", this.#boundKeyPressEvent);
993
- this.#boundExitEvent = this.#onProcessExit.bind(this);
994
- process.once("exit", this.#boundExitEvent);
995
- });
1138
+ }
1139
+ if (clearRender) {
1140
+ this.clearLastLine();
1141
+ return;
1142
+ }
1143
+ if (error || validating) {
1144
+ this.clearLastLine();
1145
+ this.#showQuestion(error, validating);
1146
+ }
1147
+ this.#showChoices();
1148
+ };
1149
+ render({ initialRender: true });
1150
+ this.#boundExitEvent = this.#onProcessExit.bind(this);
1151
+ process.once("exit", this.#boundExitEvent);
1152
+ const { promise, resolve } = Promise.withResolvers();
1153
+ this.#boundKeyPressEvent = this.#onKeypress.bind(this, resolve, render);
1154
+ this.stdin.on("keypress", this.#boundKeyPressEvent);
1155
+ return promise;
996
1156
  }
997
- #showQuestion(error = null) {
1157
+ #showQuestion(error = null, validating = null) {
998
1158
  let hint = this.#showHint ? styleText5(
999
1159
  "gray",
1000
1160
  // eslint-disable-next-line @stylistic/max-len
1001
1161
  `(Press ${styleText5("bold", "<Ctrl+A>")} to toggle all, ${styleText5("bold", "<Left/Right>")} to toggle, ${styleText5("bold", "<Return>")} to submit)`
1002
1162
  ) : "";
1003
- if (error) {
1163
+ if (validating) {
1164
+ hint += `${hint.length > 0 ? " " : ""}${styleText5("yellow", `[${validating}]`)}`;
1165
+ } else if (error) {
1004
1166
  hint += `${hint.length > 0 ? " " : ""}${styleText5(["red", "bold"], `[${error}]`)}`;
1005
1167
  }
1006
1168
  this.questionMessage = `${SYMBOLS.QuestionMark} ${styleText5("bold", this.message)}${hint.length > 0 ? ` ${hint}` : ""}`;
@@ -1008,27 +1170,102 @@ var MultiselectPrompt = class extends AbstractPrompt {
1008
1170
  }
1009
1171
  };
1010
1172
 
1011
- // index.ts
1012
- function question(message, options = {}) {
1013
- return new QuestionPrompt({ ...options, message }).question();
1173
+ // src/index.ts
1174
+ async function question(message, options = {}) {
1175
+ const prompt = new QuestionPrompt(
1176
+ { ...options, message }
1177
+ );
1178
+ const onErrorSignal = new AbortController();
1179
+ const onError = once(
1180
+ prompt,
1181
+ "error",
1182
+ { signal: onErrorSignal.signal }
1183
+ );
1184
+ const result = await Promise.race([
1185
+ prompt.listen(),
1186
+ onError
1187
+ ]);
1188
+ if (isAbortError(result)) {
1189
+ prompt.destroy();
1190
+ throw result[0];
1191
+ }
1192
+ onErrorSignal.abort();
1193
+ return result;
1194
+ }
1195
+ async function select(message, options) {
1196
+ const prompt = new SelectPrompt(
1197
+ { ...options, message }
1198
+ );
1199
+ const onErrorSignal = new AbortController();
1200
+ const onError = once(
1201
+ prompt,
1202
+ "error",
1203
+ { signal: onErrorSignal.signal }
1204
+ );
1205
+ const result = await Promise.race([
1206
+ prompt.listen(),
1207
+ onError
1208
+ ]);
1209
+ if (isAbortError(result)) {
1210
+ prompt.destroy();
1211
+ throw result[0];
1212
+ }
1213
+ onErrorSignal.abort();
1214
+ return result;
1014
1215
  }
1015
- function select(message, options) {
1016
- const selectPrompt = new SelectPrompt({ ...options, message });
1017
- return selectPrompt.select();
1216
+ async function confirm(message, options = {}) {
1217
+ const prompt = new ConfirmPrompt(
1218
+ { ...options, message }
1219
+ );
1220
+ const onErrorSignal = new AbortController();
1221
+ const onError = once(
1222
+ prompt,
1223
+ "error",
1224
+ { signal: onErrorSignal.signal }
1225
+ );
1226
+ const result = await Promise.race([
1227
+ prompt.listen(),
1228
+ onError
1229
+ ]);
1230
+ if (isAbortError(result)) {
1231
+ prompt.destroy();
1232
+ throw result[0];
1233
+ }
1234
+ onErrorSignal.abort();
1235
+ return result;
1018
1236
  }
1019
- function confirm(message, options = {}) {
1020
- const confirmPrompt = new ConfirmPrompt({ ...options, message });
1021
- return confirmPrompt.confirm();
1237
+ async function multiselect(message, options) {
1238
+ const prompt = new MultiselectPrompt(
1239
+ { ...options, message }
1240
+ );
1241
+ const onErrorSignal = new AbortController();
1242
+ const onError = once(
1243
+ prompt,
1244
+ "error",
1245
+ { signal: onErrorSignal.signal }
1246
+ );
1247
+ const result = await Promise.race([
1248
+ prompt.listen(),
1249
+ onError
1250
+ ]);
1251
+ if (isAbortError(result)) {
1252
+ prompt.destroy();
1253
+ throw result[0];
1254
+ }
1255
+ onErrorSignal.abort();
1256
+ return result;
1022
1257
  }
1023
- function multiselect(message, options) {
1024
- const multiselectPrompt = new MultiselectPrompt({ ...options, message });
1025
- return multiselectPrompt.multiselect();
1258
+ function isAbortError(error) {
1259
+ return Array.isArray(error) && error.length > 0 && error[0] instanceof Error;
1026
1260
  }
1261
+ var validators = { required };
1262
+ var transformers = { number, integer, url };
1027
1263
  export {
1028
1264
  PromptAgent,
1029
1265
  confirm,
1030
1266
  multiselect,
1031
1267
  question,
1032
- required,
1033
- select
1268
+ select,
1269
+ transformers,
1270
+ validators
1034
1271
  };