@topcli/prompts 2.4.1 → 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.cjs CHANGED
@@ -34,10 +34,12 @@ __export(index_exports, {
34
34
  confirm: () => confirm,
35
35
  multiselect: () => multiselect,
36
36
  question: () => question,
37
- required: () => required,
38
- select: () => select
37
+ select: () => select,
38
+ transformers: () => transformers,
39
+ validators: () => validators
39
40
  });
40
41
  module.exports = __toCommonJS(index_exports);
42
+ var import_node_events2 = require("events");
41
43
 
42
44
  // src/validators.ts
43
45
  function required() {
@@ -53,10 +55,13 @@ function isValid(result) {
53
55
  return result?.isValid !== false;
54
56
  }
55
57
  if (typeof result === "string") {
56
- return result.length > 0;
58
+ return result.length === 0;
57
59
  }
58
60
  return true;
59
61
  }
62
+ function isValidTransformation(result) {
63
+ return typeof result === "object" && result.isValid === true;
64
+ }
60
65
  function resultError(result) {
61
66
  if (typeof result === "object") {
62
67
  return result.error;
@@ -65,7 +70,7 @@ function resultError(result) {
65
70
  }
66
71
 
67
72
  // src/prompt-agent.ts
68
- var kPrivateInstancier = Symbol("instancier");
73
+ var kPrivateInstancier = /* @__PURE__ */ Symbol("instancier");
69
74
  var PromptAgent = class _PromptAgent {
70
75
  /**
71
76
  * The prompts answers queue.
@@ -107,6 +112,55 @@ var PromptAgent = class _PromptAgent {
107
112
  }
108
113
  };
109
114
 
115
+ // src/transformers.ts
116
+ function number() {
117
+ return {
118
+ transform(input) {
119
+ if (input.trim() === "") {
120
+ return { isValid: false, error: "not a number" };
121
+ }
122
+ const parsed = Number(input.replace(",", "."));
123
+ if (Number.isNaN(parsed)) {
124
+ return { isValid: false, error: "not a number" };
125
+ }
126
+ return { isValid: true, transformed: parsed };
127
+ }
128
+ };
129
+ }
130
+ function integer() {
131
+ return {
132
+ transform(input) {
133
+ if (input.trim() === "") {
134
+ return { isValid: false, error: "not an integer" };
135
+ }
136
+ const parsed = Number(input);
137
+ if (Number.isNaN(parsed) || !Number.isInteger(parsed)) {
138
+ return { isValid: false, error: "not an integer" };
139
+ }
140
+ return { isValid: true, transformed: parsed };
141
+ }
142
+ };
143
+ }
144
+ function url() {
145
+ return {
146
+ transform(input) {
147
+ try {
148
+ return { isValid: true, transformed: new URL(input) };
149
+ } catch {
150
+ try {
151
+ const parsed = new URL(`https://${input}`);
152
+ if (!parsed.hostname.includes(".") && parsed.hostname !== "localhost") {
153
+ return { isValid: false, error: "invalid URL" };
154
+ }
155
+ return { isValid: true, transformed: parsed };
156
+ } catch {
157
+ return { isValid: false, error: "invalid URL" };
158
+ }
159
+ }
160
+ }
161
+ };
162
+ }
163
+
110
164
  // src/prompts/abstract.ts
111
165
  var import_node_os = require("os");
112
166
  var import_node_readline = __toESM(require("readline"), 1);
@@ -287,6 +341,7 @@ var SYMBOLS = {
287
341
  Active: (0, import_node_util3.styleText)("cyan", kSymbols.active),
288
342
  Inactive: (0, import_node_util3.styleText)("gray", kSymbols.inactive)
289
343
  };
344
+ var VALIDATION_SPINNER_INTERVAL = 300;
290
345
 
291
346
  // src/prompts/question.ts
292
347
  var QuestionPrompt = class extends AbstractPrompt {
@@ -296,22 +351,29 @@ var QuestionPrompt = class extends AbstractPrompt {
296
351
  answer;
297
352
  answerBuffer;
298
353
  #validators;
354
+ #transformer;
355
+ #transformedAnswer;
299
356
  #secure;
300
357
  #securePlaceholder = null;
301
358
  constructor(options) {
302
359
  const {
303
360
  defaultValue,
304
- validators = [],
361
+ validators: validators2 = [],
362
+ transformer,
305
363
  secure = false,
306
364
  ...baseOptions
307
365
  } = options;
308
366
  super({ ...baseOptions });
367
+ if (validators2.length > 0 && transformer !== void 0) {
368
+ throw new Error("validators and transformer are mutually exclusive");
369
+ }
309
370
  if (defaultValue && typeof defaultValue !== "string") {
310
371
  throw new TypeError("defaultValue must be a string");
311
372
  }
312
373
  this.defaultValue = defaultValue;
313
374
  this.tip = this.defaultValue ? ` (${this.defaultValue})` : "";
314
- this.#validators = validators;
375
+ this.#validators = validators2;
376
+ this.#transformer = transformer;
315
377
  if (typeof secure === "object") {
316
378
  this.#secure = true;
317
379
  this.#securePlaceholder = secure.placeholder;
@@ -321,18 +383,18 @@ var QuestionPrompt = class extends AbstractPrompt {
321
383
  this.questionSuffixError = "";
322
384
  }
323
385
  #question() {
324
- return new Promise((resolve) => {
325
- const questionQuery = this.#getQuestionQuery();
326
- this.history.push(questionQuery);
327
- this.rl.question(questionQuery, (answer) => {
328
- this.history.push(questionQuery + answer);
329
- this.reset();
330
- resolve(answer);
331
- });
332
- if (this.#securePlaceholder !== null) {
333
- this.transformer = (input) => Buffer.from(this.#securePlaceholder.repeat(input.length), "utf-8");
334
- }
386
+ const { resolve, promise } = Promise.withResolvers();
387
+ const questionQuery = this.#getQuestionQuery();
388
+ this.history.push(questionQuery);
389
+ this.rl.question(questionQuery, (answer) => {
390
+ this.history.push(questionQuery + answer);
391
+ this.reset();
392
+ resolve(answer);
335
393
  });
394
+ if (this.#securePlaceholder !== null) {
395
+ this.transformer = (input) => Buffer.from(this.#securePlaceholder.repeat(input.length), "utf-8");
396
+ }
397
+ return promise;
336
398
  }
337
399
  #getQuestionQuery() {
338
400
  return `${(0, import_node_util4.styleText)("bold", `${SYMBOLS.QuestionMark} ${this.message}${this.tip}`)} ${this.questionSuffixError}`;
@@ -351,14 +413,68 @@ var QuestionPrompt = class extends AbstractPrompt {
351
413
  );
352
414
  this.write(`${prefix} ${(0, import_node_util4.styleText)("bold", this.message)} ${SYMBOLS.Pointer} ${stylizedAnswer}${import_node_os2.EOL}`);
353
415
  }
354
- #onQuestionAnswer() {
416
+ #getValidatingQuery(dotCount) {
417
+ const question2 = (0, import_node_util4.styleText)("bold", `${SYMBOLS.QuestionMark} ${this.message}${this.tip}`);
418
+ const hint = (0, import_node_util4.styleText)("yellow", `[validating${".".repeat(dotCount)}]`);
419
+ return `${question2} ${hint}${import_node_os2.EOL}`;
420
+ }
421
+ async #runTransformer() {
422
+ const result = this.#transformer.transform(this.answer);
423
+ if (result instanceof Promise) {
424
+ let dotCount = 1;
425
+ this.write(this.#getValidatingQuery(dotCount));
426
+ const spinnerInterval = setInterval(() => {
427
+ dotCount = dotCount % 3 + 1;
428
+ this.clearLastLine();
429
+ this.write(this.#getValidatingQuery(dotCount));
430
+ }, VALIDATION_SPINNER_INTERVAL);
431
+ try {
432
+ return await result;
433
+ } finally {
434
+ clearInterval(spinnerInterval);
435
+ this.clearLastLine();
436
+ }
437
+ }
438
+ return result;
439
+ }
440
+ async #onQuestionAnswer() {
355
441
  const questionLineCount = Math.ceil(
356
442
  stringLength(this.#getQuestionQuery() + this.answer) / this.stdout.columns
357
443
  );
358
444
  this.stdout.moveCursor(-this.stdout.columns, -questionLineCount);
359
445
  this.stdout.clearScreenDown();
446
+ if (this.#transformer) {
447
+ const result = await this.#runTransformer();
448
+ if (isValidTransformation(result) === false) {
449
+ this.#setQuestionSuffixError(resultError(result));
450
+ this.answerBuffer = this.#question();
451
+ return;
452
+ }
453
+ this.#transformedAnswer = result.transformed;
454
+ this.answerBuffer = void 0;
455
+ this.#writeAnswer();
456
+ return;
457
+ }
360
458
  for (const validator of this.#validators) {
361
- const validationResult = validator.validate(this.answer);
459
+ let validationResult;
460
+ const result = validator.validate(this.answer);
461
+ if (result instanceof Promise) {
462
+ let dotCount = 1;
463
+ this.write(this.#getValidatingQuery(dotCount));
464
+ const spinnerInterval = setInterval(() => {
465
+ dotCount = dotCount % 3 + 1;
466
+ this.clearLastLine();
467
+ this.write(this.#getValidatingQuery(dotCount));
468
+ }, VALIDATION_SPINNER_INTERVAL);
469
+ try {
470
+ validationResult = await result;
471
+ } finally {
472
+ clearInterval(spinnerInterval);
473
+ this.clearLastLine();
474
+ }
475
+ } else {
476
+ validationResult = result;
477
+ }
362
478
  if (isValid(validationResult) === false) {
363
479
  this.#setQuestionSuffixError(resultError(validationResult));
364
480
  this.answerBuffer = this.#question();
@@ -368,34 +484,44 @@ var QuestionPrompt = class extends AbstractPrompt {
368
484
  this.answerBuffer = void 0;
369
485
  this.#writeAnswer();
370
486
  }
371
- async question() {
487
+ async listen() {
372
488
  if (this.skip) {
373
489
  this.destroy();
490
+ if (this.#transformer) {
491
+ const rawValue = this.defaultValue ?? "";
492
+ const result = await this.#transformer.transform(rawValue);
493
+ if (isValidTransformation(result) === false) {
494
+ throw new Error(`Transformer failed for default value "${rawValue}": ${resultError(result)}`);
495
+ }
496
+ return result.transformed;
497
+ }
374
498
  return this.defaultValue ?? "";
375
499
  }
376
- return new Promise(async (resolve, reject) => {
377
- this.answer = this.agent.nextAnswers.shift();
378
- if (this.answer !== void 0) {
379
- this.#writeAnswer();
380
- this.destroy();
381
- resolve(this.answer);
382
- return;
383
- }
384
- this.once("error", (error) => {
385
- reject(error);
386
- });
387
- this.answer = await this.#question();
388
- if (this.answer === "" && this.defaultValue) {
389
- this.answer = this.defaultValue;
390
- }
391
- this.#onQuestionAnswer();
392
- while (this.answerBuffer !== void 0) {
393
- this.answer = await this.answerBuffer;
394
- this.#onQuestionAnswer();
395
- }
500
+ const agentAnswer = this.agent.nextAnswers.shift();
501
+ if (agentAnswer !== void 0) {
502
+ this.answer = agentAnswer;
503
+ this.#writeAnswer();
396
504
  this.destroy();
397
- resolve(this.answer);
398
- });
505
+ if (this.#transformer) {
506
+ const result = await this.#transformer.transform(agentAnswer);
507
+ if (isValidTransformation(result) === false) {
508
+ throw new Error(`(PromptAgent) transformer failed for answer "${agentAnswer}": ${resultError(result)}`);
509
+ }
510
+ return result.transformed;
511
+ }
512
+ return this.answer;
513
+ }
514
+ this.answer = await this.#question();
515
+ if (this.answer === "" && this.defaultValue) {
516
+ this.answer = this.defaultValue;
517
+ }
518
+ await this.#onQuestionAnswer();
519
+ while (this.answerBuffer !== void 0) {
520
+ this.answer = await this.answerBuffer;
521
+ await this.#onQuestionAnswer();
522
+ }
523
+ this.destroy();
524
+ return this.#transformedAnswer ?? this.answer;
399
525
  }
400
526
  };
401
527
 
@@ -438,16 +564,6 @@ var ConfirmPrompt = class extends AbstractPrompt {
438
564
  #render() {
439
565
  this.write(this.#getQuestionQuery());
440
566
  }
441
- #question() {
442
- return new Promise((resolve) => {
443
- const questionQuery = this.#getQuestionQuery();
444
- this.write(questionQuery);
445
- this.#boundKeyPressEvent = this.#onKeypress.bind(this, resolve);
446
- this.stdin.on("keypress", this.#boundKeyPressEvent);
447
- this.#boundExitEvent = this.#onProcessExit.bind(this);
448
- process.once("exit", this.#boundExitEvent);
449
- });
450
- }
451
567
  #onKeypress(resolve, _value, key) {
452
568
  this.stdout.moveCursor(
453
569
  -this.stdout.columns,
@@ -490,35 +606,36 @@ var ConfirmPrompt = class extends AbstractPrompt {
490
606
  this.stdout.clearScreenDown();
491
607
  this.write(`${this.selectedValue ? SYMBOLS.Tick : SYMBOLS.Cross} ${(0, import_node_util5.styleText)("bold", this.message)}${import_node_os3.EOL}`);
492
608
  }
493
- async confirm() {
609
+ async listen() {
494
610
  if (this.skip) {
495
611
  this.destroy();
496
612
  return this.initial;
497
613
  }
498
- return new Promise(async (resolve, reject) => {
499
- const answer = this.agent.nextAnswers.shift();
500
- if (answer !== void 0) {
501
- this.selectedValue = answer;
502
- this.#onQuestionAnswer();
503
- this.destroy();
504
- resolve(answer);
505
- return;
506
- }
507
- this.once("error", (error) => {
508
- reject(error);
509
- });
510
- this.write(SYMBOLS.HideCursor);
511
- try {
512
- await this.#question();
513
- this.#onQuestionAnswer();
514
- resolve(this.selectedValue);
515
- } finally {
516
- this.write(SYMBOLS.ShowCursor);
517
- this.#onProcessExit();
518
- process.off("exit", this.#boundExitEvent);
519
- this.destroy();
520
- }
521
- });
614
+ const answer = this.agent.nextAnswers.shift();
615
+ if (answer !== void 0) {
616
+ this.selectedValue = answer;
617
+ this.#onQuestionAnswer();
618
+ this.destroy();
619
+ return answer;
620
+ }
621
+ this.write(SYMBOLS.HideCursor);
622
+ try {
623
+ const { resolve, promise } = Promise.withResolvers();
624
+ const questionQuery = this.#getQuestionQuery();
625
+ this.write(questionQuery);
626
+ this.#boundKeyPressEvent = this.#onKeypress.bind(this, resolve);
627
+ this.stdin.on("keypress", this.#boundKeyPressEvent);
628
+ this.#boundExitEvent = this.#onProcessExit.bind(this);
629
+ process.once("exit", this.#boundExitEvent);
630
+ await promise;
631
+ this.#onQuestionAnswer();
632
+ } finally {
633
+ this.write(SYMBOLS.ShowCursor);
634
+ this.#onProcessExit();
635
+ process.off("exit", this.#boundExitEvent);
636
+ this.destroy();
637
+ }
638
+ return this.selectedValue;
522
639
  }
523
640
  };
524
641
 
@@ -530,6 +647,7 @@ var SelectPrompt = class extends AbstractPrompt {
530
647
  #boundExitEvent = () => void 0;
531
648
  #boundKeyPressEvent = () => void 0;
532
649
  #validators;
650
+ #isValidating = false;
533
651
  activeIndex = 0;
534
652
  questionMessage;
535
653
  autocompleteValue = "";
@@ -570,7 +688,7 @@ var SelectPrompt = class extends AbstractPrompt {
570
688
  constructor(options) {
571
689
  const {
572
690
  choices,
573
- validators = [],
691
+ validators: validators2 = [],
574
692
  ...baseOptions
575
693
  } = options;
576
694
  super({ ...baseOptions });
@@ -579,7 +697,7 @@ var SelectPrompt = class extends AbstractPrompt {
579
697
  this.destroy();
580
698
  throw new TypeError("Missing required param: choices");
581
699
  }
582
- this.#validators = validators;
700
+ this.#validators = validators2;
583
701
  for (const choice of choices) {
584
702
  if (typeof choice === "string") {
585
703
  continue;
@@ -635,6 +753,48 @@ var SelectPrompt = class extends AbstractPrompt {
635
753
  this.write(str);
636
754
  }
637
755
  }
756
+ async #handleReturn(resolve, render) {
757
+ this.#isValidating = true;
758
+ try {
759
+ const choice = this.filteredChoices[this.activeIndex] || "";
760
+ const label = typeof choice === "string" ? choice : choice.label;
761
+ const value = typeof choice === "string" ? choice : choice.value;
762
+ for (const validator of this.#validators) {
763
+ let validationResult;
764
+ const result = validator.validate(value);
765
+ if (result instanceof Promise) {
766
+ let dotCount = 1;
767
+ render({ validating: `validating${".".repeat(dotCount)}` });
768
+ const spinnerInterval = setInterval(() => {
769
+ dotCount = dotCount % 3 + 1;
770
+ render({ validating: `validating${".".repeat(dotCount)}` });
771
+ }, VALIDATION_SPINNER_INTERVAL);
772
+ try {
773
+ validationResult = await result;
774
+ } finally {
775
+ clearInterval(spinnerInterval);
776
+ }
777
+ } else {
778
+ validationResult = result;
779
+ }
780
+ if (isValid(validationResult) === false) {
781
+ render({ error: resultError(validationResult) });
782
+ return;
783
+ }
784
+ }
785
+ render({ clearRender: true });
786
+ if (!this.options.ignoreValues?.includes(value)) {
787
+ this.#showAnsweredQuestion(label);
788
+ }
789
+ this.write(SYMBOLS.ShowCursor);
790
+ this.destroy();
791
+ this.#onProcessExit();
792
+ process.off("exit", this.#boundExitEvent);
793
+ resolve(value);
794
+ } finally {
795
+ this.#isValidating = false;
796
+ }
797
+ }
638
798
  #showAnsweredQuestion(label) {
639
799
  const symbolPrefix = label === "" ? SYMBOLS.Cross : SYMBOLS.Tick;
640
800
  const prefix = `${symbolPrefix} ${(0, import_node_util6.styleText)("bold", this.message)} ${SYMBOLS.Pointer}`;
@@ -649,6 +809,9 @@ var SelectPrompt = class extends AbstractPrompt {
649
809
  }
650
810
  #onKeypress(...args) {
651
811
  const [resolve, render, , key] = args;
812
+ if (this.#isValidating) {
813
+ return;
814
+ }
652
815
  if (key.name === "up") {
653
816
  this.activeIndex = this.activeIndex === 0 ? this.filteredChoices.length - 1 : this.activeIndex - 1;
654
817
  render();
@@ -656,25 +819,7 @@ var SelectPrompt = class extends AbstractPrompt {
656
819
  this.activeIndex = this.activeIndex === this.filteredChoices.length - 1 ? 0 : this.activeIndex + 1;
657
820
  render();
658
821
  } else if (key.name === "return") {
659
- const choice = this.filteredChoices[this.activeIndex] || "";
660
- const label = typeof choice === "string" ? choice : choice.label;
661
- const value = typeof choice === "string" ? choice : choice.value;
662
- for (const validator of this.#validators) {
663
- const validationResult = validator.validate(value);
664
- if (isValid(validationResult) === false) {
665
- render({ error: resultError(validationResult) });
666
- return;
667
- }
668
- }
669
- render({ clearRender: true });
670
- if (!this.options.ignoreValues?.includes(value)) {
671
- this.#showAnsweredQuestion(label);
672
- }
673
- this.write(SYMBOLS.ShowCursor);
674
- this.destroy();
675
- this.#onProcessExit();
676
- process.off("exit", this.#boundExitEvent);
677
- resolve(value);
822
+ void this.#handleReturn(resolve, render);
678
823
  } else {
679
824
  if (!key.ctrl && this.options.autocomplete) {
680
825
  this.activeIndex = 0;
@@ -687,76 +832,70 @@ var SelectPrompt = class extends AbstractPrompt {
687
832
  render();
688
833
  }
689
834
  }
690
- async select() {
835
+ async listen() {
691
836
  if (this.skip) {
692
837
  this.destroy();
693
- const answer = this.options.choices[0];
694
- return typeof answer === "string" ? answer : answer.value;
838
+ const answer2 = this.options.choices[0];
839
+ return typeof answer2 === "string" ? answer2 : answer2.value;
695
840
  }
696
- return new Promise((resolve, reject) => {
697
- const answer = this.agent.nextAnswers.shift();
698
- if (answer !== void 0) {
699
- this.#showAnsweredQuestion(answer);
700
- this.destroy();
701
- resolve(answer);
702
- return;
703
- }
704
- this.once("error", (error) => {
705
- reject(error);
706
- });
707
- this.write(SYMBOLS.HideCursor);
708
- this.#showQuestion();
709
- const render = (options = {}) => {
710
- const {
711
- initialRender = false,
712
- clearRender = false,
713
- error = null
714
- } = options;
715
- if (!initialRender) {
716
- let linesToClear = this.lastRender.endIndex - this.lastRender.startIndex;
717
- while (linesToClear > 0) {
718
- this.clearLastLine();
719
- linesToClear--;
720
- }
721
- if (this.options.autocomplete) {
722
- let linesToClear2 = Math.ceil(
723
- stringLength(`${SYMBOLS.Pointer} ${this.autocompleteValue}`) / this.stdout.columns
724
- );
725
- while (linesToClear2 > 0) {
726
- this.clearLastLine();
727
- linesToClear2--;
728
- }
729
- }
841
+ const answer = this.agent.nextAnswers.shift();
842
+ if (answer !== void 0) {
843
+ this.#showAnsweredQuestion(answer);
844
+ this.destroy();
845
+ return answer;
846
+ }
847
+ this.transformer = () => null;
848
+ this.write(SYMBOLS.HideCursor);
849
+ this.#showQuestion();
850
+ const render = (options = {}) => {
851
+ const {
852
+ initialRender = false,
853
+ clearRender = false,
854
+ error = null,
855
+ validating = null
856
+ } = options;
857
+ if (!initialRender) {
858
+ let linesToClear = this.lastRender.endIndex - this.lastRender.startIndex;
859
+ while (linesToClear > 0) {
860
+ this.clearLastLine();
861
+ linesToClear--;
730
862
  }
731
- if (clearRender) {
732
- const questionLineCount = Math.ceil(
733
- stringLength(this.questionMessage) / this.stdout.columns
863
+ if (this.options.autocomplete) {
864
+ let linesToClear2 = Math.ceil(
865
+ stringLength(`${SYMBOLS.Pointer} ${this.autocompleteValue}`) / this.stdout.columns
734
866
  );
735
- this.stdout.moveCursor(-this.stdout.columns, -(1 + questionLineCount));
736
- this.stdout.clearScreenDown();
737
- return;
738
- }
739
- if (error) {
740
- const linesToClear = Math.ceil(stringLength(this.questionMessage) / this.stdout.columns) + 1;
741
- this.stdout.moveCursor(0, -linesToClear);
742
- this.stdout.clearScreenDown();
743
- this.#showQuestion(error);
867
+ while (linesToClear2 > 0) {
868
+ this.clearLastLine();
869
+ linesToClear2--;
870
+ }
744
871
  }
745
- this.#showChoices();
746
- };
747
- render({ initialRender: true });
748
- this.#boundKeyPressEvent = this.#onKeypress.bind(this, resolve, render);
749
- this.stdin.on("keypress", this.#boundKeyPressEvent);
750
- this.#boundExitEvent = this.#onProcessExit.bind(this);
751
- process.once("exit", this.#boundExitEvent);
752
- });
872
+ }
873
+ if (clearRender) {
874
+ this.clearLastLine();
875
+ return;
876
+ }
877
+ if (error || validating) {
878
+ this.clearLastLine();
879
+ this.#showQuestion(error, validating);
880
+ }
881
+ this.#showChoices();
882
+ };
883
+ render({ initialRender: true });
884
+ this.#boundExitEvent = this.#onProcessExit.bind(this);
885
+ process.once("exit", this.#boundExitEvent);
886
+ const { resolve, promise } = Promise.withResolvers();
887
+ this.#boundKeyPressEvent = this.#onKeypress.bind(this, resolve, render);
888
+ this.stdin.on("keypress", this.#boundKeyPressEvent);
889
+ return promise;
753
890
  }
754
- #showQuestion(error = null) {
891
+ #showQuestion(error = null, validating = null) {
755
892
  let hint = "";
756
- if (error) {
757
- hint = ` ${hint.length > 0 ? " " : ""}${(0, import_node_util6.styleText)(["red", "bold"], `[${error}]`)}`;
893
+ if (validating) {
894
+ hint = (0, import_node_util6.styleText)("yellow", `[${validating}]`);
895
+ } else if (error) {
896
+ hint += `${hint.length > 0 ? " " : ""}${(0, import_node_util6.styleText)(["red", "bold"], `[${error}]`)}`;
758
897
  }
759
- this.questionMessage = `${SYMBOLS.QuestionMark} ${(0, import_node_util6.styleText)("bold", this.message)}${hint}`;
898
+ this.questionMessage = `${SYMBOLS.QuestionMark} ${(0, import_node_util6.styleText)("bold", this.message)}${hint.length > 0 ? ` ${hint}` : ""}`;
760
899
  this.write(`${this.questionMessage}${import_node_os4.EOL}`);
761
900
  }
762
901
  };
@@ -770,6 +909,7 @@ var MultiselectPrompt = class extends AbstractPrompt {
770
909
  #boundKeyPressEvent = () => void 0;
771
910
  #validators;
772
911
  #showHint;
912
+ #isValidating = false;
773
913
  activeIndex = 0;
774
914
  selectedIndexes = /* @__PURE__ */ new Set();
775
915
  questionMessage;
@@ -812,7 +952,7 @@ var MultiselectPrompt = class extends AbstractPrompt {
812
952
  const {
813
953
  choices,
814
954
  preSelectedChoices = [],
815
- validators = [],
955
+ validators: validators2 = [],
816
956
  showHint = true,
817
957
  ...baseOptions
818
958
  } = options;
@@ -822,7 +962,7 @@ var MultiselectPrompt = class extends AbstractPrompt {
822
962
  this.destroy();
823
963
  throw new TypeError("Missing required param: choices");
824
964
  }
825
- this.#validators = validators;
965
+ this.#validators = validators2;
826
966
  this.#showHint = showHint;
827
967
  for (const choice of choices) {
828
968
  if (typeof choice === "string") {
@@ -893,6 +1033,44 @@ var MultiselectPrompt = class extends AbstractPrompt {
893
1033
  this.write(str);
894
1034
  }
895
1035
  }
1036
+ async #handleReturn(resolve, render) {
1037
+ this.#isValidating = true;
1038
+ try {
1039
+ const { values, labels } = this.#selectedChoices();
1040
+ for (const validator of this.#validators) {
1041
+ let validationResult;
1042
+ const result = validator.validate(values);
1043
+ if (result instanceof Promise) {
1044
+ let dotCount = 1;
1045
+ render({ validating: `validating${".".repeat(dotCount)}` });
1046
+ const spinnerInterval = setInterval(() => {
1047
+ dotCount = dotCount % 3 + 1;
1048
+ render({ validating: `validating${".".repeat(dotCount)}` });
1049
+ }, VALIDATION_SPINNER_INTERVAL);
1050
+ try {
1051
+ validationResult = await result;
1052
+ } finally {
1053
+ clearInterval(spinnerInterval);
1054
+ }
1055
+ } else {
1056
+ validationResult = result;
1057
+ }
1058
+ if (isValid(validationResult) === false) {
1059
+ render({ error: resultError(validationResult) });
1060
+ return;
1061
+ }
1062
+ }
1063
+ render({ clearRender: true });
1064
+ this.#showAnsweredQuestion(labels.join(", "));
1065
+ this.write(SYMBOLS.ShowCursor);
1066
+ this.destroy();
1067
+ this.#onProcessExit();
1068
+ process.off("exit", this.#boundExitEvent);
1069
+ resolve(values);
1070
+ } finally {
1071
+ this.#isValidating = false;
1072
+ }
1073
+ }
896
1074
  #showAnsweredQuestion(choices, isAgentAnswer = false) {
897
1075
  const prefixSymbol = this.selectedIndexes.size === 0 && !isAgentAnswer ? SYMBOLS.Cross : SYMBOLS.Tick;
898
1076
  const prefix = `${prefixSymbol} ${(0, import_node_util7.styleText)("bold", this.message)} ${SYMBOLS.Pointer}`;
@@ -926,6 +1104,9 @@ var MultiselectPrompt = class extends AbstractPrompt {
926
1104
  }
927
1105
  #onKeypress(...args) {
928
1106
  const [resolve, render, , key] = args;
1107
+ if (this.#isValidating) {
1108
+ return;
1109
+ }
929
1110
  if (key.name === "up") {
930
1111
  this.activeIndex = this.activeIndex === 0 ? this.filteredChoices.length - 1 : this.activeIndex - 1;
931
1112
  render();
@@ -942,21 +1123,7 @@ var MultiselectPrompt = class extends AbstractPrompt {
942
1123
  this.selectedIndexes = new Set([...this.selectedIndexes].filter((index) => index !== this.activeIndex));
943
1124
  render();
944
1125
  } else if (key.name === "return") {
945
- const { values, labels } = this.#selectedChoices();
946
- for (const validator of this.#validators) {
947
- const validationResult = validator.validate(values);
948
- if (isValid(validationResult) === false) {
949
- render({ error: resultError(validationResult) });
950
- return;
951
- }
952
- }
953
- render({ clearRender: true });
954
- this.#showAnsweredQuestion(labels.join(", "));
955
- this.write(SYMBOLS.ShowCursor);
956
- this.destroy();
957
- this.#onProcessExit();
958
- process.off("exit", this.#boundExitEvent);
959
- resolve(values);
1126
+ void this.#handleReturn(resolve, render);
960
1127
  } else {
961
1128
  if (!key.ctrl && this.options.autocomplete) {
962
1129
  this.selectedIndexes.clear();
@@ -970,78 +1137,72 @@ var MultiselectPrompt = class extends AbstractPrompt {
970
1137
  render();
971
1138
  }
972
1139
  }
973
- async multiselect() {
1140
+ async listen() {
974
1141
  if (this.skip) {
975
1142
  this.destroy();
976
1143
  const { values } = this.#selectedChoices();
977
1144
  return values;
978
1145
  }
979
- return new Promise((resolve, reject) => {
980
- const answer = this.agent.nextAnswers.shift();
981
- if (answer !== void 0) {
982
- const formatedAnser = Array.isArray(answer) ? answer.join(", ") : answer;
983
- this.#showAnsweredQuestion(formatedAnser, true);
984
- this.destroy();
985
- resolve(Array.isArray(answer) ? answer : [answer]);
986
- return;
987
- }
988
- this.once("error", (error) => {
989
- reject(error);
990
- });
991
- this.write(SYMBOLS.HideCursor);
992
- this.#showQuestion();
993
- const render = (options = {}) => {
994
- const {
995
- initialRender = false,
996
- clearRender = false,
997
- error = null
998
- } = options;
999
- if (!initialRender) {
1000
- let linesToClear = this.lastRender.endIndex - this.lastRender.startIndex;
1001
- while (linesToClear > 0) {
1002
- this.clearLastLine();
1003
- linesToClear--;
1004
- }
1005
- if (this.options.autocomplete) {
1006
- let linesToClear2 = Math.ceil(
1007
- stringLength(`${SYMBOLS.Pointer} ${this.autocompleteValue}`) / this.stdout.columns
1008
- );
1009
- while (linesToClear2 > 0) {
1010
- this.clearLastLine();
1011
- linesToClear2--;
1012
- }
1013
- }
1146
+ const answer = this.agent.nextAnswers.shift();
1147
+ if (answer !== void 0) {
1148
+ const formatedAnser = Array.isArray(answer) ? answer.join(", ") : answer;
1149
+ this.#showAnsweredQuestion(formatedAnser, true);
1150
+ this.destroy();
1151
+ return Array.isArray(answer) ? answer : [answer];
1152
+ }
1153
+ this.transformer = () => null;
1154
+ this.write(SYMBOLS.HideCursor);
1155
+ this.#showQuestion();
1156
+ const render = (options = {}) => {
1157
+ const {
1158
+ initialRender = false,
1159
+ clearRender = false,
1160
+ error = null,
1161
+ validating = null
1162
+ } = options;
1163
+ if (!initialRender) {
1164
+ let linesToClear = this.lastRender.endIndex - this.lastRender.startIndex;
1165
+ while (linesToClear > 0) {
1166
+ this.clearLastLine();
1167
+ linesToClear--;
1014
1168
  }
1015
- if (clearRender) {
1016
- const questionLineCount = Math.ceil(
1017
- stringLength(this.questionMessage) / this.stdout.columns
1169
+ if (this.options.autocomplete) {
1170
+ let linesToClear2 = Math.ceil(
1171
+ stringLength(`${SYMBOLS.Pointer} ${this.autocompleteValue}`) / this.stdout.columns
1018
1172
  );
1019
- this.stdout.moveCursor(-this.stdout.columns, -(1 + questionLineCount));
1020
- this.stdout.clearScreenDown();
1021
- return;
1022
- }
1023
- if (error) {
1024
- const linesToClear = Math.ceil(stringLength(this.questionMessage) / this.stdout.columns) + 1;
1025
- this.stdout.moveCursor(0, -linesToClear);
1026
- this.stdout.clearScreenDown();
1027
- this.#showQuestion(error);
1173
+ while (linesToClear2 > 0) {
1174
+ this.clearLastLine();
1175
+ linesToClear2--;
1176
+ }
1028
1177
  }
1029
- this.#showChoices();
1030
- };
1031
- render({ initialRender: true });
1032
- this.#boundKeyPressEvent = this.#onKeypress.bind(this, resolve, render);
1033
- this.stdin.on("keypress", this.#boundKeyPressEvent);
1034
- this.#boundExitEvent = this.#onProcessExit.bind(this);
1035
- process.once("exit", this.#boundExitEvent);
1036
- });
1178
+ }
1179
+ if (clearRender) {
1180
+ this.clearLastLine();
1181
+ return;
1182
+ }
1183
+ if (error || validating) {
1184
+ this.clearLastLine();
1185
+ this.#showQuestion(error, validating);
1186
+ }
1187
+ this.#showChoices();
1188
+ };
1189
+ render({ initialRender: true });
1190
+ this.#boundExitEvent = this.#onProcessExit.bind(this);
1191
+ process.once("exit", this.#boundExitEvent);
1192
+ const { promise, resolve } = Promise.withResolvers();
1193
+ this.#boundKeyPressEvent = this.#onKeypress.bind(this, resolve, render);
1194
+ this.stdin.on("keypress", this.#boundKeyPressEvent);
1195
+ return promise;
1037
1196
  }
1038
- #showQuestion(error = null) {
1197
+ #showQuestion(error = null, validating = null) {
1039
1198
  let hint = this.#showHint ? (0, import_node_util7.styleText)(
1040
1199
  "gray",
1041
1200
  // eslint-disable-next-line @stylistic/max-len
1042
1201
  `(Press ${(0, import_node_util7.styleText)("bold", "<Ctrl+A>")} to toggle all, ${(0, import_node_util7.styleText)("bold", "<Left/Right>")} to toggle, ${(0, import_node_util7.styleText)("bold", "<Return>")} to submit)`
1043
1202
  ) : "";
1044
- if (error) {
1203
+ if (validating) {
1204
+ hint += `${hint.length > 0 ? " " : ""}${(0, import_node_util7.styleText)("yellow", `[${validating}]`)}`;
1205
+ } else if (error) {
1045
1206
  hint += `${hint.length > 0 ? " " : ""}${(0, import_node_util7.styleText)(["red", "bold"], `[${error}]`)}`;
1046
1207
  }
1047
1208
  this.questionMessage = `${SYMBOLS.QuestionMark} ${(0, import_node_util7.styleText)("bold", this.message)}${hint.length > 0 ? ` ${hint}` : ""}`;
@@ -1050,32 +1211,102 @@ var MultiselectPrompt = class extends AbstractPrompt {
1050
1211
  };
1051
1212
 
1052
1213
  // src/index.ts
1053
- function question(message, options = {}) {
1054
- return new QuestionPrompt(
1214
+ async function question(message, options = {}) {
1215
+ const prompt = new QuestionPrompt(
1055
1216
  { ...options, message }
1056
- ).question();
1217
+ );
1218
+ const onErrorSignal = new AbortController();
1219
+ const onError = (0, import_node_events2.once)(
1220
+ prompt,
1221
+ "error",
1222
+ { signal: onErrorSignal.signal }
1223
+ );
1224
+ const result = await Promise.race([
1225
+ prompt.listen(),
1226
+ onError
1227
+ ]);
1228
+ if (isAbortError(result)) {
1229
+ prompt.destroy();
1230
+ throw result[0];
1231
+ }
1232
+ onErrorSignal.abort();
1233
+ return result;
1057
1234
  }
1058
- function select(message, options) {
1059
- return new SelectPrompt(
1235
+ async function select(message, options) {
1236
+ const prompt = new SelectPrompt(
1060
1237
  { ...options, message }
1061
- ).select();
1238
+ );
1239
+ const onErrorSignal = new AbortController();
1240
+ const onError = (0, import_node_events2.once)(
1241
+ prompt,
1242
+ "error",
1243
+ { signal: onErrorSignal.signal }
1244
+ );
1245
+ const result = await Promise.race([
1246
+ prompt.listen(),
1247
+ onError
1248
+ ]);
1249
+ if (isAbortError(result)) {
1250
+ prompt.destroy();
1251
+ throw result[0];
1252
+ }
1253
+ onErrorSignal.abort();
1254
+ return result;
1062
1255
  }
1063
- function confirm(message, options = {}) {
1064
- return new ConfirmPrompt(
1256
+ async function confirm(message, options = {}) {
1257
+ const prompt = new ConfirmPrompt(
1065
1258
  { ...options, message }
1066
- ).confirm();
1259
+ );
1260
+ const onErrorSignal = new AbortController();
1261
+ const onError = (0, import_node_events2.once)(
1262
+ prompt,
1263
+ "error",
1264
+ { signal: onErrorSignal.signal }
1265
+ );
1266
+ const result = await Promise.race([
1267
+ prompt.listen(),
1268
+ onError
1269
+ ]);
1270
+ if (isAbortError(result)) {
1271
+ prompt.destroy();
1272
+ throw result[0];
1273
+ }
1274
+ onErrorSignal.abort();
1275
+ return result;
1067
1276
  }
1068
- function multiselect(message, options) {
1069
- return new MultiselectPrompt(
1277
+ async function multiselect(message, options) {
1278
+ const prompt = new MultiselectPrompt(
1070
1279
  { ...options, message }
1071
- ).multiselect();
1280
+ );
1281
+ const onErrorSignal = new AbortController();
1282
+ const onError = (0, import_node_events2.once)(
1283
+ prompt,
1284
+ "error",
1285
+ { signal: onErrorSignal.signal }
1286
+ );
1287
+ const result = await Promise.race([
1288
+ prompt.listen(),
1289
+ onError
1290
+ ]);
1291
+ if (isAbortError(result)) {
1292
+ prompt.destroy();
1293
+ throw result[0];
1294
+ }
1295
+ onErrorSignal.abort();
1296
+ return result;
1297
+ }
1298
+ function isAbortError(error) {
1299
+ return Array.isArray(error) && error.length > 0 && error[0] instanceof Error;
1072
1300
  }
1301
+ var validators = { required };
1302
+ var transformers = { number, integer, url };
1073
1303
  // Annotate the CommonJS export names for ESM import in node:
1074
1304
  0 && (module.exports = {
1075
1305
  PromptAgent,
1076
1306
  confirm,
1077
1307
  multiselect,
1078
1308
  question,
1079
- required,
1080
- select
1309
+ select,
1310
+ transformers,
1311
+ validators
1081
1312
  });