@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.cjs CHANGED
@@ -27,31 +27,50 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
27
27
  ));
28
28
  var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
29
29
 
30
- // index.ts
30
+ // src/index.ts
31
31
  var index_exports = {};
32
32
  __export(index_exports, {
33
33
  PromptAgent: () => PromptAgent,
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
- // src/prompts/question.ts
43
- var import_node_os2 = require("os");
44
- var import_node_util4 = require("util");
45
-
46
- // src/prompts/abstract.ts
47
- var import_node_os = require("os");
48
- var import_node_readline = __toESM(require("readline"), 1);
49
- var import_node_stream = require("stream");
50
- var import_node_events = __toESM(require("events"), 1);
51
- var import_node_util = require("util");
44
+ // src/validators.ts
45
+ function required() {
46
+ return {
47
+ validate: (input) => {
48
+ const isValid2 = Array.isArray(input) ? input.length > 0 : Boolean(input);
49
+ return isValid2 ? null : { isValid: isValid2, error: "required" };
50
+ }
51
+ };
52
+ }
53
+ function isValid(result) {
54
+ if (typeof result === "object") {
55
+ return result?.isValid !== false;
56
+ }
57
+ if (typeof result === "string") {
58
+ return result.length === 0;
59
+ }
60
+ return true;
61
+ }
62
+ function isValidTransformation(result) {
63
+ return typeof result === "object" && result.isValid === true;
64
+ }
65
+ function resultError(result) {
66
+ if (typeof result === "object") {
67
+ return result.error;
68
+ }
69
+ return result;
70
+ }
52
71
 
53
72
  // src/prompt-agent.ts
54
- var kPrivateInstancier = Symbol("instancier");
73
+ var kPrivateInstancier = /* @__PURE__ */ Symbol("instancier");
55
74
  var PromptAgent = class _PromptAgent {
56
75
  /**
57
76
  * The prompts answers queue.
@@ -93,6 +112,62 @@ var PromptAgent = class _PromptAgent {
93
112
  }
94
113
  };
95
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
+
164
+ // src/prompts/abstract.ts
165
+ var import_node_os = require("os");
166
+ var import_node_readline = __toESM(require("readline"), 1);
167
+ var import_node_stream = require("stream");
168
+ var import_node_events = __toESM(require("events"), 1);
169
+ var import_node_util = require("util");
170
+
96
171
  // src/errors/abort.ts
97
172
  var AbortError = class extends Error {
98
173
  constructor(message) {
@@ -205,6 +280,10 @@ var AbstractPrompt = class _AbstractPrompt extends import_node_events.default {
205
280
  }
206
281
  };
207
282
 
283
+ // src/prompts/question.ts
284
+ var import_node_os2 = require("os");
285
+ var import_node_util4 = require("util");
286
+
208
287
  // src/utils.ts
209
288
  var import_node_process = __toESM(require("process"), 1);
210
289
  var import_node_util2 = require("util");
@@ -262,31 +341,7 @@ var SYMBOLS = {
262
341
  Active: (0, import_node_util3.styleText)("cyan", kSymbols.active),
263
342
  Inactive: (0, import_node_util3.styleText)("gray", kSymbols.inactive)
264
343
  };
265
-
266
- // src/validators.ts
267
- function required() {
268
- return {
269
- validate: (input) => {
270
- const isValid2 = Array.isArray(input) ? input.length > 0 : Boolean(input);
271
- return isValid2 ? null : { isValid: isValid2, error: "required" };
272
- }
273
- };
274
- }
275
- function isValid(result) {
276
- if (typeof result === "object") {
277
- return result?.isValid !== false;
278
- }
279
- if (typeof result === "string") {
280
- return result.length > 0;
281
- }
282
- return true;
283
- }
284
- function resultError(result) {
285
- if (typeof result === "object") {
286
- return result.error;
287
- }
288
- return result;
289
- }
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}` : ""}`;
@@ -1049,28 +1210,103 @@ var MultiselectPrompt = class extends AbstractPrompt {
1049
1210
  }
1050
1211
  };
1051
1212
 
1052
- // index.ts
1053
- function question(message, options = {}) {
1054
- return new QuestionPrompt({ ...options, message }).question();
1213
+ // src/index.ts
1214
+ async function question(message, options = {}) {
1215
+ const prompt = new QuestionPrompt(
1216
+ { ...options, message }
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;
1055
1234
  }
1056
- function select(message, options) {
1057
- const selectPrompt = new SelectPrompt({ ...options, message });
1058
- return selectPrompt.select();
1235
+ async function select(message, options) {
1236
+ const prompt = new SelectPrompt(
1237
+ { ...options, message }
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;
1059
1255
  }
1060
- function confirm(message, options = {}) {
1061
- const confirmPrompt = new ConfirmPrompt({ ...options, message });
1062
- return confirmPrompt.confirm();
1256
+ async function confirm(message, options = {}) {
1257
+ const prompt = new ConfirmPrompt(
1258
+ { ...options, message }
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;
1276
+ }
1277
+ async function multiselect(message, options) {
1278
+ const prompt = new MultiselectPrompt(
1279
+ { ...options, message }
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;
1063
1297
  }
1064
- function multiselect(message, options) {
1065
- const multiselectPrompt = new MultiselectPrompt({ ...options, message });
1066
- return multiselectPrompt.multiselect();
1298
+ function isAbortError(error) {
1299
+ return Array.isArray(error) && error.length > 0 && error[0] instanceof Error;
1067
1300
  }
1301
+ var validators = { required };
1302
+ var transformers = { number, integer, url };
1068
1303
  // Annotate the CommonJS export names for ESM import in node:
1069
1304
  0 && (module.exports = {
1070
1305
  PromptAgent,
1071
1306
  confirm,
1072
1307
  multiselect,
1073
1308
  question,
1074
- required,
1075
- select
1309
+ select,
1310
+ transformers,
1311
+ validators
1076
1312
  });