@topcli/prompts 2.2.0 → 2.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -55,7 +55,7 @@ Simple prompt, similar to `rl.question()` with an improved UI.
55
55
 
56
56
  Use `options.defaultValue` to set a default value.
57
57
 
58
- Use `options.secure` if you need to hide both input and answer.
58
+ Use `options.secure` if you need to hide both input and answer. You can provide either a **boolean** or an **object** which allows to configure a `placeholder` such as `*`.
59
59
 
60
60
  Use `options.signal` to set an `AbortSignal` (throws a [AbortError](#aborterror)).
61
61
 
@@ -70,8 +70,8 @@ const packageName = await question('Package name', {
70
70
  validators: [
71
71
  {
72
72
  validate: (value) => {
73
- if (!existsSync(join(process.cwd(), value))) {
74
- return `Folder ${value} already exists`
73
+ if (fs.existsSync(path.join(process.cwd(), value))) {
74
+ return `Folder ${value} already exists`;
75
75
  }
76
76
  }
77
77
  }
package/dist/index.cjs CHANGED
@@ -41,35 +41,14 @@ module.exports = __toCommonJS(index_exports);
41
41
 
42
42
  // src/prompts/question.ts
43
43
  var import_node_os2 = require("os");
44
- var import_node_util2 = require("util");
45
- var import_wcwidth = __toESM(require("@topcli/wcwidth"), 1);
44
+ var import_node_util4 = require("util");
46
45
 
47
46
  // src/prompts/abstract.ts
48
47
  var import_node_os = require("os");
49
48
  var import_node_readline = __toESM(require("readline"), 1);
50
49
  var import_node_stream = require("stream");
51
50
  var import_node_events = __toESM(require("events"), 1);
52
-
53
- // src/utils.ts
54
- var import_node_process = __toESM(require("process"), 1);
55
- var kAnsiRegex = ansiRegex();
56
- function ansiRegex({ onlyFirst = false } = {}) {
57
- const pattern = [
58
- // eslint-disable-next-line @stylistic/max-len
59
- "[\\u001B\\u009B][[\\]()#;?]*(?:(?:(?:(?:;[-a-zA-Z\\d\\/#&.:=?%@~_]+)*|[a-zA-Z\\d]+(?:;[-a-zA-Z\\d\\/#&.:=?%@~_]*)*)?\\u0007)",
60
- "(?:(?:\\d{1,4}(?:;\\d{0,4})*)?[\\dA-PR-TZcf-nq-uy=><~]))"
61
- ].join("|");
62
- return new RegExp(pattern, onlyFirst ? void 0 : "g");
63
- }
64
- function stripAnsi(string) {
65
- return string.replace(kAnsiRegex, "");
66
- }
67
- function isUnicodeSupported() {
68
- if (import_node_process.default.platform !== "win32") {
69
- return import_node_process.default.env.TERM !== "linux";
70
- }
71
- return Boolean(import_node_process.default.env.WT_SESSION) || Boolean(import_node_process.default.env.TERMINUS_SUBLIME) || import_node_process.default.env.ConEmuTask === "{cmd::Cmder}" || import_node_process.default.env.TERM_PROGRAM === "Terminus-Sublime" || import_node_process.default.env.TERM_PROGRAM === "vscode" || import_node_process.default.env.TERM === "xterm-256color" || import_node_process.default.env.TERM === "alacritty" || import_node_process.default.env.TERMINAL_EMULATOR === "JetBrains-JediTerm";
72
- }
51
+ var import_node_util = require("util");
73
52
 
74
53
  // src/prompt-agent.ts
75
54
  var kPrivateInstancier = Symbol("instancier");
@@ -123,6 +102,9 @@ var AbortError = class extends Error {
123
102
  };
124
103
 
125
104
  // src/prompts/abstract.ts
105
+ function kNoopTransformer(input) {
106
+ return input;
107
+ }
126
108
  var AbstractPrompt = class _AbstractPrompt extends import_node_events.default {
127
109
  stdin;
128
110
  stdout;
@@ -131,7 +113,7 @@ var AbstractPrompt = class _AbstractPrompt extends import_node_events.default {
131
113
  skip;
132
114
  history;
133
115
  agent;
134
- mute;
116
+ transformer = kNoopTransformer;
135
117
  rl;
136
118
  #signalHandler;
137
119
  constructor(options) {
@@ -162,7 +144,6 @@ var AbstractPrompt = class _AbstractPrompt extends import_node_events.default {
162
144
  this.skip = skip;
163
145
  this.history = [];
164
146
  this.agent = PromptAgent.agent();
165
- this.mute = false;
166
147
  if (this.stdout.isTTY) {
167
148
  this.stdin.setRawMode(true);
168
149
  }
@@ -170,8 +151,13 @@ var AbstractPrompt = class _AbstractPrompt extends import_node_events.default {
170
151
  input,
171
152
  output: new import_node_stream.Writable({
172
153
  write: (chunk, encoding, callback) => {
173
- if (!this.mute && chunk) {
174
- this.stdout.write(chunk, encoding);
154
+ if (chunk) {
155
+ const transformed = this.transformer(
156
+ Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk)
157
+ );
158
+ if (transformed !== null) {
159
+ this.stdout.write(transformed, encoding);
160
+ }
175
161
  }
176
162
  callback();
177
163
  }
@@ -192,8 +178,11 @@ var AbstractPrompt = class _AbstractPrompt extends import_node_events.default {
192
178
  this.signal.addEventListener("abort", this.#signalHandler, { once: true });
193
179
  }
194
180
  }
181
+ reset() {
182
+ this.transformer = kNoopTransformer;
183
+ }
195
184
  write(data) {
196
- const formattedData = stripAnsi(data).replace(import_node_os.EOL, "");
185
+ const formattedData = (0, import_node_util.stripVTControlCharacters)(data).replace(import_node_os.EOL, "");
197
186
  if (formattedData) {
198
187
  this.history.push(formattedData);
199
188
  }
@@ -204,7 +193,7 @@ var AbstractPrompt = class _AbstractPrompt extends import_node_events.default {
204
193
  if (!lastLine) {
205
194
  return;
206
195
  }
207
- const lastLineRows = Math.ceil(stripAnsi(lastLine).length / this.stdout.columns);
196
+ const lastLineRows = Math.ceil((0, import_node_util.stripVTControlCharacters)(lastLine).length / this.stdout.columns);
208
197
  this.stdout.moveCursor(-this.stdout.columns, -lastLineRows);
209
198
  this.stdout.clearScreenDown();
210
199
  }
@@ -216,8 +205,31 @@ var AbstractPrompt = class _AbstractPrompt extends import_node_events.default {
216
205
  }
217
206
  };
218
207
 
208
+ // src/utils.ts
209
+ var import_node_process = __toESM(require("process"), 1);
210
+ var import_node_util2 = require("util");
211
+ var kLenSegmenter = new Intl.Segmenter();
212
+ function isUnicodeSupported() {
213
+ if (import_node_process.default.platform !== "win32") {
214
+ return import_node_process.default.env.TERM !== "linux";
215
+ }
216
+ return Boolean(import_node_process.default.env.WT_SESSION) || Boolean(import_node_process.default.env.TERMINUS_SUBLIME) || import_node_process.default.env.ConEmuTask === "{cmd::Cmder}" || import_node_process.default.env.TERM_PROGRAM === "Terminus-Sublime" || import_node_process.default.env.TERM_PROGRAM === "vscode" || import_node_process.default.env.TERM === "xterm-256color" || import_node_process.default.env.TERM === "alacritty" || import_node_process.default.env.TERMINAL_EMULATOR === "JetBrains-JediTerm";
217
+ }
218
+ function stringLength(string) {
219
+ if (string === "") {
220
+ return 0;
221
+ }
222
+ let length = 0;
223
+ for (const _ of kLenSegmenter.segment(
224
+ (0, import_node_util2.stripVTControlCharacters)(string)
225
+ )) {
226
+ length++;
227
+ }
228
+ return length;
229
+ }
230
+
219
231
  // src/constants.ts
220
- var import_node_util = require("util");
232
+ var import_node_util3 = require("util");
221
233
  var kMainSymbols = {
222
234
  tick: "\u2714",
223
235
  cross: "\u2716",
@@ -237,18 +249,18 @@ var kFallbackSymbols = {
237
249
  inactive: "(-)"
238
250
  };
239
251
  var kSymbols = isUnicodeSupported() || process.env.CI ? kMainSymbols : kFallbackSymbols;
240
- var kPointer = (0, import_node_util.styleText)("gray", kSymbols.pointer);
252
+ var kPointer = (0, import_node_util3.styleText)("gray", kSymbols.pointer);
241
253
  var SYMBOLS = {
242
- QuestionMark: (0, import_node_util.styleText)(["blue", "bold"], "?"),
243
- Tick: (0, import_node_util.styleText)(["green", "bold"], kSymbols.tick),
244
- Cross: (0, import_node_util.styleText)(["red", "bold"], kSymbols.cross),
254
+ QuestionMark: (0, import_node_util3.styleText)(["blue", "bold"], "?"),
255
+ Tick: (0, import_node_util3.styleText)(["green", "bold"], kSymbols.tick),
256
+ Cross: (0, import_node_util3.styleText)(["red", "bold"], kSymbols.cross),
245
257
  Pointer: kPointer,
246
258
  Previous: kSymbols.previous,
247
259
  Next: kSymbols.next,
248
260
  ShowCursor: "\x1B[?25h",
249
261
  HideCursor: "\x1B[?25l",
250
- Active: (0, import_node_util.styleText)("cyan", kSymbols.active),
251
- Inactive: (0, import_node_util.styleText)("gray", kSymbols.inactive)
262
+ Active: (0, import_node_util3.styleText)("cyan", kSymbols.active),
263
+ Inactive: (0, import_node_util3.styleText)("gray", kSymbols.inactive)
252
264
  };
253
265
 
254
266
  // src/validators.ts
@@ -285,6 +297,7 @@ var QuestionPrompt = class extends AbstractPrompt {
285
297
  answerBuffer;
286
298
  #validators;
287
299
  #secure;
300
+ #securePlaceholder = null;
288
301
  constructor(options) {
289
302
  const {
290
303
  defaultValue,
@@ -299,7 +312,12 @@ var QuestionPrompt = class extends AbstractPrompt {
299
312
  this.defaultValue = defaultValue;
300
313
  this.tip = this.defaultValue ? ` (${this.defaultValue})` : "";
301
314
  this.#validators = validators;
302
- this.#secure = Boolean(secure);
315
+ if (typeof secure === "object") {
316
+ this.#secure = true;
317
+ this.#securePlaceholder = secure.placeholder;
318
+ } else {
319
+ this.#secure = Boolean(secure);
320
+ }
303
321
  this.questionSuffixError = "";
304
322
  }
305
323
  #question() {
@@ -308,27 +326,34 @@ var QuestionPrompt = class extends AbstractPrompt {
308
326
  this.history.push(questionQuery);
309
327
  this.rl.question(questionQuery, (answer) => {
310
328
  this.history.push(questionQuery + answer);
311
- this.mute = false;
329
+ this.reset();
312
330
  resolve(answer);
313
331
  });
314
- this.mute = this.#secure;
332
+ if (this.#securePlaceholder !== null) {
333
+ this.transformer = (input) => Buffer.from(this.#securePlaceholder.repeat(input.length), "utf-8");
334
+ }
315
335
  });
316
336
  }
317
337
  #getQuestionQuery() {
318
- return `${(0, import_node_util2.styleText)("bold", `${SYMBOLS.QuestionMark} ${this.message}${this.tip}`)} ${this.questionSuffixError}`;
338
+ return `${(0, import_node_util4.styleText)("bold", `${SYMBOLS.QuestionMark} ${this.message}${this.tip}`)} ${this.questionSuffixError}`;
319
339
  }
320
340
  #setQuestionSuffixError(error) {
321
- const suffix = (0, import_node_util2.styleText)("red", `[${error}] `);
341
+ const suffix = (0, import_node_util4.styleText)("red", `[${error}] `);
322
342
  this.questionSuffixError = suffix;
323
343
  }
324
344
  #writeAnswer() {
325
345
  const prefix = this.answer ? SYMBOLS.Tick : SYMBOLS.Cross;
326
- const answer = (0, import_node_util2.styleText)("yellow", this.#secure ? "CONFIDENTIAL" : this.answer ?? "");
327
- this.write(`${prefix} ${(0, import_node_util2.styleText)("bold", this.message)} ${SYMBOLS.Pointer} ${answer}${import_node_os2.EOL}`);
346
+ const answer = this.answer ?? "";
347
+ const maskedAnswer = this.#securePlaceholder ? this.#securePlaceholder.repeat(answer.length) : answer;
348
+ const stylizedAnswer = (0, import_node_util4.styleText)(
349
+ "yellow",
350
+ this.#secure && this.#securePlaceholder === null ? "CONFIDENTIAL" : maskedAnswer
351
+ );
352
+ this.write(`${prefix} ${(0, import_node_util4.styleText)("bold", this.message)} ${SYMBOLS.Pointer} ${stylizedAnswer}${import_node_os2.EOL}`);
328
353
  }
329
354
  #onQuestionAnswer() {
330
355
  const questionLineCount = Math.ceil(
331
- (0, import_wcwidth.default)(stripAnsi(this.#getQuestionQuery() + this.answer)) / this.stdout.columns
356
+ stringLength(this.#getQuestionQuery() + this.answer) / this.stdout.columns
332
357
  );
333
358
  this.stdout.moveCursor(-this.stdout.columns, -questionLineCount);
334
359
  this.stdout.clearScreenDown();
@@ -376,8 +401,7 @@ var QuestionPrompt = class extends AbstractPrompt {
376
401
 
377
402
  // src/prompts/confirm.ts
378
403
  var import_node_os3 = require("os");
379
- var import_node_util3 = require("util");
380
- var import_wcwidth2 = __toESM(require("@topcli/wcwidth"), 1);
404
+ var import_node_util5 = require("util");
381
405
  var kToggleKeys = /* @__PURE__ */ new Set([
382
406
  "left",
383
407
  "right",
@@ -407,8 +431,8 @@ var ConfirmPrompt = class extends AbstractPrompt {
407
431
  this.selectedValue = initial;
408
432
  }
409
433
  #getHint() {
410
- const Yes = (0, import_node_util3.styleText)(["cyan", "bold", "underline"], "Yes");
411
- const No = (0, import_node_util3.styleText)(["cyan", "bold", "underline"], "No");
434
+ const Yes = (0, import_node_util5.styleText)(["cyan", "bold", "underline"], "Yes");
435
+ const No = (0, import_node_util5.styleText)(["cyan", "bold", "underline"], "No");
412
436
  return this.selectedValue ? `${Yes}/No` : `Yes/${No}`;
413
437
  }
414
438
  #render() {
@@ -427,7 +451,7 @@ var ConfirmPrompt = class extends AbstractPrompt {
427
451
  #onKeypress(resolve, _value, key) {
428
452
  this.stdout.moveCursor(
429
453
  -this.stdout.columns,
430
- -Math.floor((0, import_wcwidth2.default)(stripAnsi(this.#getQuestionQuery())) / this.stdout.columns)
454
+ -Math.floor(stringLength(this.#getQuestionQuery()) / this.stdout.columns)
431
455
  );
432
456
  this.stdout.clearScreenDown();
433
457
  if (key.name === "return") {
@@ -454,17 +478,17 @@ var ConfirmPrompt = class extends AbstractPrompt {
454
478
  this.stdin.off("keypress", this.#boundKeyPressEvent);
455
479
  }
456
480
  #getQuestionQuery() {
457
- const query = (0, import_node_util3.styleText)("bold", `${SYMBOLS.QuestionMark} ${this.message}`);
481
+ const query = (0, import_node_util5.styleText)("bold", `${SYMBOLS.QuestionMark} ${this.message}`);
458
482
  return `${query} ${this.#getHint()}`;
459
483
  }
460
484
  #onQuestionAnswer() {
461
485
  this.clearLastLine();
462
486
  this.stdout.moveCursor(
463
487
  -this.stdout.columns,
464
- -Math.floor((0, import_wcwidth2.default)(stripAnsi(this.#getQuestionQuery())) / this.stdout.columns)
488
+ -Math.floor(stringLength(this.#getQuestionQuery()) / this.stdout.columns)
465
489
  );
466
490
  this.stdout.clearScreenDown();
467
- this.write(`${this.selectedValue ? SYMBOLS.Tick : SYMBOLS.Cross} ${(0, import_node_util3.styleText)("bold", this.message)}${import_node_os3.EOL}`);
491
+ this.write(`${this.selectedValue ? SYMBOLS.Tick : SYMBOLS.Cross} ${(0, import_node_util5.styleText)("bold", this.message)}${import_node_os3.EOL}`);
468
492
  }
469
493
  async confirm() {
470
494
  if (this.skip) {
@@ -500,8 +524,7 @@ var ConfirmPrompt = class extends AbstractPrompt {
500
524
 
501
525
  // src/prompts/select.ts
502
526
  var import_node_os4 = require("os");
503
- var import_node_util4 = require("util");
504
- var import_wcwidth3 = __toESM(require("@topcli/wcwidth"), 1);
527
+ var import_node_util6 = require("util");
505
528
  var kRequiredChoiceProperties = ["label", "value"];
506
529
  var SelectPrompt = class extends AbstractPrompt {
507
530
  #boundExitEvent = () => void 0;
@@ -608,14 +631,14 @@ var SelectPrompt = class extends AbstractPrompt {
608
631
  );
609
632
  const formattedDescription = choice.description ? ` - ${choice.description}` : "";
610
633
  const styles = isChoiceSelected ? ["white", "bold"] : ["gray"];
611
- const str = `${prefix}${(0, import_node_util4.styleText)(styles, `${formattedLabel}${formattedDescription}`)}${import_node_os4.EOL}`;
634
+ const str = `${prefix}${(0, import_node_util6.styleText)(styles, `${formattedLabel}${formattedDescription}`)}${import_node_os4.EOL}`;
612
635
  this.write(str);
613
636
  }
614
637
  }
615
- #showAnsweredQuestion(choice) {
616
- const symbolPrefix = choice === "" ? SYMBOLS.Cross : SYMBOLS.Tick;
617
- const prefix = `${symbolPrefix} ${(0, import_node_util4.styleText)("bold", this.message)} ${SYMBOLS.Pointer}`;
618
- const formattedChoice = (0, import_node_util4.styleText)("yellow", typeof choice === "string" ? choice : choice.label);
638
+ #showAnsweredQuestion(label) {
639
+ const symbolPrefix = label === "" ? SYMBOLS.Cross : SYMBOLS.Tick;
640
+ const prefix = `${symbolPrefix} ${(0, import_node_util6.styleText)("bold", this.message)} ${SYMBOLS.Pointer}`;
641
+ const formattedChoice = (0, import_node_util6.styleText)("yellow", label);
619
642
  this.write(`${prefix} ${formattedChoice}${import_node_os4.EOL}`);
620
643
  }
621
644
  #onProcessExit() {
@@ -697,7 +720,7 @@ var SelectPrompt = class extends AbstractPrompt {
697
720
  }
698
721
  if (this.options.autocomplete) {
699
722
  let linesToClear2 = Math.ceil(
700
- (0, import_wcwidth3.default)(`${SYMBOLS.Pointer} ${this.autocompleteValue}`) / this.stdout.columns
723
+ stringLength(`${SYMBOLS.Pointer} ${this.autocompleteValue}`) / this.stdout.columns
701
724
  );
702
725
  while (linesToClear2 > 0) {
703
726
  this.clearLastLine();
@@ -707,14 +730,14 @@ var SelectPrompt = class extends AbstractPrompt {
707
730
  }
708
731
  if (clearRender) {
709
732
  const questionLineCount = Math.ceil(
710
- (0, import_wcwidth3.default)(stripAnsi(this.questionMessage)) / this.stdout.columns
733
+ stringLength(this.questionMessage) / this.stdout.columns
711
734
  );
712
735
  this.stdout.moveCursor(-this.stdout.columns, -(1 + questionLineCount));
713
736
  this.stdout.clearScreenDown();
714
737
  return;
715
738
  }
716
739
  if (error) {
717
- const linesToClear = Math.ceil((0, import_wcwidth3.default)(this.questionMessage) / this.stdout.columns) + 1;
740
+ const linesToClear = Math.ceil(stringLength(this.questionMessage) / this.stdout.columns) + 1;
718
741
  this.stdout.moveCursor(0, -linesToClear);
719
742
  this.stdout.clearScreenDown();
720
743
  this.#showQuestion(error);
@@ -731,17 +754,16 @@ var SelectPrompt = class extends AbstractPrompt {
731
754
  #showQuestion(error = null) {
732
755
  let hint = "";
733
756
  if (error) {
734
- hint = ` ${hint.length > 0 ? " " : ""}${(0, import_node_util4.styleText)(["red", "bold"], `[${error}]`)}`;
757
+ hint = ` ${hint.length > 0 ? " " : ""}${(0, import_node_util6.styleText)(["red", "bold"], `[${error}]`)}`;
735
758
  }
736
- this.questionMessage = `${SYMBOLS.QuestionMark} ${(0, import_node_util4.styleText)("bold", this.message)}${hint}`;
759
+ this.questionMessage = `${SYMBOLS.QuestionMark} ${(0, import_node_util6.styleText)("bold", this.message)}${hint}`;
737
760
  this.write(`${this.questionMessage}${import_node_os4.EOL}`);
738
761
  }
739
762
  };
740
763
 
741
764
  // src/prompts/multiselect.ts
742
765
  var import_node_os5 = require("os");
743
- var import_node_util5 = require("util");
744
- var import_wcwidth4 = __toESM(require("@topcli/wcwidth"), 1);
766
+ var import_node_util7 = require("util");
745
767
  var kRequiredChoiceProperties2 = ["label", "value"];
746
768
  var MultiselectPrompt = class extends AbstractPrompt {
747
769
  #boundExitEvent = () => void 0;
@@ -867,14 +889,14 @@ var MultiselectPrompt = class extends AbstractPrompt {
867
889
  );
868
890
  const formattedDescription = choice.description ? ` - ${choice.description}` : "";
869
891
  const styles = isChoiceActive ? ["white", "bold"] : ["gray"];
870
- const str = `${prefix} ${(0, import_node_util5.styleText)(styles, `${formattedLabel}${formattedDescription}`)}${import_node_os5.EOL}`;
892
+ const str = `${prefix} ${(0, import_node_util7.styleText)(styles, `${formattedLabel}${formattedDescription}`)}${import_node_os5.EOL}`;
871
893
  this.write(str);
872
894
  }
873
895
  }
874
896
  #showAnsweredQuestion(choices, isAgentAnswer = false) {
875
897
  const prefixSymbol = this.selectedIndexes.size === 0 && !isAgentAnswer ? SYMBOLS.Cross : SYMBOLS.Tick;
876
- const prefix = `${prefixSymbol} ${(0, import_node_util5.styleText)("bold", this.message)} ${SYMBOLS.Pointer}`;
877
- const formattedChoice = (0, import_node_util5.styleText)("yellow", choices);
898
+ const prefix = `${prefixSymbol} ${(0, import_node_util7.styleText)("bold", this.message)} ${SYMBOLS.Pointer}`;
899
+ const formattedChoice = (0, import_node_util7.styleText)("yellow", choices);
878
900
  this.write(`${prefix}${choices ? ` ${formattedChoice}` : ""}${import_node_os5.EOL}`);
879
901
  }
880
902
  #selectedChoices() {
@@ -982,7 +1004,7 @@ var MultiselectPrompt = class extends AbstractPrompt {
982
1004
  }
983
1005
  if (this.options.autocomplete) {
984
1006
  let linesToClear2 = Math.ceil(
985
- (0, import_wcwidth4.default)(`${SYMBOLS.Pointer} ${this.autocompleteValue}`) / this.stdout.columns
1007
+ stringLength(`${SYMBOLS.Pointer} ${this.autocompleteValue}`) / this.stdout.columns
986
1008
  );
987
1009
  while (linesToClear2 > 0) {
988
1010
  this.clearLastLine();
@@ -992,14 +1014,14 @@ var MultiselectPrompt = class extends AbstractPrompt {
992
1014
  }
993
1015
  if (clearRender) {
994
1016
  const questionLineCount = Math.ceil(
995
- (0, import_wcwidth4.default)(stripAnsi(this.questionMessage)) / this.stdout.columns
1017
+ stringLength(this.questionMessage) / this.stdout.columns
996
1018
  );
997
1019
  this.stdout.moveCursor(-this.stdout.columns, -(1 + questionLineCount));
998
1020
  this.stdout.clearScreenDown();
999
1021
  return;
1000
1022
  }
1001
1023
  if (error) {
1002
- const linesToClear = Math.ceil((0, import_wcwidth4.default)(this.questionMessage) / this.stdout.columns) + 1;
1024
+ const linesToClear = Math.ceil(stringLength(this.questionMessage) / this.stdout.columns) + 1;
1003
1025
  this.stdout.moveCursor(0, -linesToClear);
1004
1026
  this.stdout.clearScreenDown();
1005
1027
  this.#showQuestion(error);
@@ -1014,15 +1036,15 @@ var MultiselectPrompt = class extends AbstractPrompt {
1014
1036
  });
1015
1037
  }
1016
1038
  #showQuestion(error = null) {
1017
- let hint = this.#showHint ? (0, import_node_util5.styleText)(
1039
+ let hint = this.#showHint ? (0, import_node_util7.styleText)(
1018
1040
  "gray",
1019
1041
  // eslint-disable-next-line @stylistic/max-len
1020
- `(Press ${(0, import_node_util5.styleText)("bold", "<Ctrl+A>")} to toggle all, ${(0, import_node_util5.styleText)("bold", "<Left/Right>")} to toggle, ${(0, import_node_util5.styleText)("bold", "<Return>")} to submit)`
1042
+ `(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)`
1021
1043
  ) : "";
1022
1044
  if (error) {
1023
- hint += `${hint.length > 0 ? " " : ""}${(0, import_node_util5.styleText)(["red", "bold"], `[${error}]`)}`;
1045
+ hint += `${hint.length > 0 ? " " : ""}${(0, import_node_util7.styleText)(["red", "bold"], `[${error}]`)}`;
1024
1046
  }
1025
- this.questionMessage = `${SYMBOLS.QuestionMark} ${(0, import_node_util5.styleText)("bold", this.message)}${hint.length > 0 ? ` ${hint}` : ""}`;
1047
+ this.questionMessage = `${SYMBOLS.QuestionMark} ${(0, import_node_util7.styleText)("bold", this.message)}${hint.length > 0 ? ` ${hint}` : ""}`;
1026
1048
  this.write(`${this.questionMessage}${import_node_os5.EOL}`);
1027
1049
  }
1028
1050
  };
package/dist/index.d.cts CHANGED
@@ -62,28 +62,30 @@ interface Choice<T = any> {
62
62
  interface QuestionOptions extends AbstractPromptOptions {
63
63
  defaultValue?: string;
64
64
  validators?: PromptValidator[];
65
- secure?: boolean;
65
+ secure?: boolean | {
66
+ placeholder: string;
67
+ };
66
68
  }
67
69
 
68
70
  interface ConfirmOptions extends AbstractPromptOptions {
69
71
  initial?: boolean;
70
72
  }
71
73
 
72
- interface MultiselectOptions<T extends string = string> extends AbstractPromptOptions {
73
- choices: (Choice | T)[];
74
+ interface MultiselectOptions<T extends string> extends AbstractPromptOptions {
75
+ choices: (Choice<T> | T)[];
74
76
  maxVisible?: number;
75
- preSelectedChoices?: (Choice | string)[];
76
- validators?: PromptValidator[];
77
+ preSelectedChoices?: (Choice | T)[];
78
+ validators?: PromptValidator<T[]>[];
77
79
  autocomplete?: boolean;
78
80
  caseSensitive?: boolean;
79
81
  showHint?: boolean;
80
82
  }
81
83
 
82
- interface SelectOptions<T extends string = string> extends AbstractPromptOptions {
83
- choices: (Choice | T)[];
84
+ interface SelectOptions<T extends string> extends AbstractPromptOptions {
85
+ choices: (Choice<T> | T)[];
84
86
  maxVisible?: number;
85
- ignoreValues?: (string | number | boolean)[];
86
- validators?: PromptValidator[];
87
+ ignoreValues?: (T | number | boolean)[];
88
+ validators?: PromptValidator<T>[];
87
89
  autocomplete?: boolean;
88
90
  caseSensitive?: boolean;
89
91
  }
package/dist/index.d.ts CHANGED
@@ -62,28 +62,30 @@ interface Choice<T = any> {
62
62
  interface QuestionOptions extends AbstractPromptOptions {
63
63
  defaultValue?: string;
64
64
  validators?: PromptValidator[];
65
- secure?: boolean;
65
+ secure?: boolean | {
66
+ placeholder: string;
67
+ };
66
68
  }
67
69
 
68
70
  interface ConfirmOptions extends AbstractPromptOptions {
69
71
  initial?: boolean;
70
72
  }
71
73
 
72
- interface MultiselectOptions<T extends string = string> extends AbstractPromptOptions {
73
- choices: (Choice | T)[];
74
+ interface MultiselectOptions<T extends string> extends AbstractPromptOptions {
75
+ choices: (Choice<T> | T)[];
74
76
  maxVisible?: number;
75
- preSelectedChoices?: (Choice | string)[];
76
- validators?: PromptValidator[];
77
+ preSelectedChoices?: (Choice | T)[];
78
+ validators?: PromptValidator<T[]>[];
77
79
  autocomplete?: boolean;
78
80
  caseSensitive?: boolean;
79
81
  showHint?: boolean;
80
82
  }
81
83
 
82
- interface SelectOptions<T extends string = string> extends AbstractPromptOptions {
83
- choices: (Choice | T)[];
84
+ interface SelectOptions<T extends string> extends AbstractPromptOptions {
85
+ choices: (Choice<T> | T)[];
84
86
  maxVisible?: number;
85
- ignoreValues?: (string | number | boolean)[];
86
- validators?: PromptValidator[];
87
+ ignoreValues?: (T | number | boolean)[];
88
+ validators?: PromptValidator<T>[];
87
89
  autocomplete?: boolean;
88
90
  caseSensitive?: boolean;
89
91
  }
package/dist/index.js CHANGED
@@ -1,34 +1,13 @@
1
1
  // src/prompts/question.ts
2
2
  import { EOL as EOL2 } from "node:os";
3
3
  import { styleText as styleText2 } from "node:util";
4
- import wcwidth from "@topcli/wcwidth";
5
4
 
6
5
  // src/prompts/abstract.ts
7
6
  import { EOL } from "node:os";
8
7
  import readline from "node:readline";
9
8
  import { Writable } from "node:stream";
10
9
  import EventEmitter from "node:events";
11
-
12
- // src/utils.ts
13
- import process2 from "node:process";
14
- var kAnsiRegex = ansiRegex();
15
- function ansiRegex({ onlyFirst = false } = {}) {
16
- const pattern = [
17
- // eslint-disable-next-line @stylistic/max-len
18
- "[\\u001B\\u009B][[\\]()#;?]*(?:(?:(?:(?:;[-a-zA-Z\\d\\/#&.:=?%@~_]+)*|[a-zA-Z\\d]+(?:;[-a-zA-Z\\d\\/#&.:=?%@~_]*)*)?\\u0007)",
19
- "(?:(?:\\d{1,4}(?:;\\d{0,4})*)?[\\dA-PR-TZcf-nq-uy=><~]))"
20
- ].join("|");
21
- return new RegExp(pattern, onlyFirst ? void 0 : "g");
22
- }
23
- function stripAnsi(string) {
24
- return string.replace(kAnsiRegex, "");
25
- }
26
- function isUnicodeSupported() {
27
- if (process2.platform !== "win32") {
28
- return process2.env.TERM !== "linux";
29
- }
30
- return Boolean(process2.env.WT_SESSION) || Boolean(process2.env.TERMINUS_SUBLIME) || process2.env.ConEmuTask === "{cmd::Cmder}" || process2.env.TERM_PROGRAM === "Terminus-Sublime" || process2.env.TERM_PROGRAM === "vscode" || process2.env.TERM === "xterm-256color" || process2.env.TERM === "alacritty" || process2.env.TERMINAL_EMULATOR === "JetBrains-JediTerm";
31
- }
10
+ import { stripVTControlCharacters } from "node:util";
32
11
 
33
12
  // src/prompt-agent.ts
34
13
  var kPrivateInstancier = Symbol("instancier");
@@ -82,6 +61,9 @@ var AbortError = class extends Error {
82
61
  };
83
62
 
84
63
  // src/prompts/abstract.ts
64
+ function kNoopTransformer(input) {
65
+ return input;
66
+ }
85
67
  var AbstractPrompt = class _AbstractPrompt extends EventEmitter {
86
68
  stdin;
87
69
  stdout;
@@ -90,7 +72,7 @@ var AbstractPrompt = class _AbstractPrompt extends EventEmitter {
90
72
  skip;
91
73
  history;
92
74
  agent;
93
- mute;
75
+ transformer = kNoopTransformer;
94
76
  rl;
95
77
  #signalHandler;
96
78
  constructor(options) {
@@ -121,7 +103,6 @@ var AbstractPrompt = class _AbstractPrompt extends EventEmitter {
121
103
  this.skip = skip;
122
104
  this.history = [];
123
105
  this.agent = PromptAgent.agent();
124
- this.mute = false;
125
106
  if (this.stdout.isTTY) {
126
107
  this.stdin.setRawMode(true);
127
108
  }
@@ -129,8 +110,13 @@ var AbstractPrompt = class _AbstractPrompt extends EventEmitter {
129
110
  input,
130
111
  output: new Writable({
131
112
  write: (chunk, encoding, callback) => {
132
- if (!this.mute && chunk) {
133
- this.stdout.write(chunk, encoding);
113
+ if (chunk) {
114
+ const transformed = this.transformer(
115
+ Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk)
116
+ );
117
+ if (transformed !== null) {
118
+ this.stdout.write(transformed, encoding);
119
+ }
134
120
  }
135
121
  callback();
136
122
  }
@@ -151,8 +137,11 @@ var AbstractPrompt = class _AbstractPrompt extends EventEmitter {
151
137
  this.signal.addEventListener("abort", this.#signalHandler, { once: true });
152
138
  }
153
139
  }
140
+ reset() {
141
+ this.transformer = kNoopTransformer;
142
+ }
154
143
  write(data) {
155
- const formattedData = stripAnsi(data).replace(EOL, "");
144
+ const formattedData = stripVTControlCharacters(data).replace(EOL, "");
156
145
  if (formattedData) {
157
146
  this.history.push(formattedData);
158
147
  }
@@ -163,7 +152,7 @@ var AbstractPrompt = class _AbstractPrompt extends EventEmitter {
163
152
  if (!lastLine) {
164
153
  return;
165
154
  }
166
- const lastLineRows = Math.ceil(stripAnsi(lastLine).length / this.stdout.columns);
155
+ const lastLineRows = Math.ceil(stripVTControlCharacters(lastLine).length / this.stdout.columns);
167
156
  this.stdout.moveCursor(-this.stdout.columns, -lastLineRows);
168
157
  this.stdout.clearScreenDown();
169
158
  }
@@ -175,6 +164,29 @@ var AbstractPrompt = class _AbstractPrompt extends EventEmitter {
175
164
  }
176
165
  };
177
166
 
167
+ // src/utils.ts
168
+ import process2 from "node:process";
169
+ import { stripVTControlCharacters as stripVTControlCharacters2 } from "node:util";
170
+ var kLenSegmenter = new Intl.Segmenter();
171
+ function isUnicodeSupported() {
172
+ if (process2.platform !== "win32") {
173
+ return process2.env.TERM !== "linux";
174
+ }
175
+ return Boolean(process2.env.WT_SESSION) || Boolean(process2.env.TERMINUS_SUBLIME) || process2.env.ConEmuTask === "{cmd::Cmder}" || process2.env.TERM_PROGRAM === "Terminus-Sublime" || process2.env.TERM_PROGRAM === "vscode" || process2.env.TERM === "xterm-256color" || process2.env.TERM === "alacritty" || process2.env.TERMINAL_EMULATOR === "JetBrains-JediTerm";
176
+ }
177
+ function stringLength(string) {
178
+ if (string === "") {
179
+ return 0;
180
+ }
181
+ let length = 0;
182
+ for (const _ of kLenSegmenter.segment(
183
+ stripVTControlCharacters2(string)
184
+ )) {
185
+ length++;
186
+ }
187
+ return length;
188
+ }
189
+
178
190
  // src/constants.ts
179
191
  import { styleText } from "node:util";
180
192
  var kMainSymbols = {
@@ -244,6 +256,7 @@ var QuestionPrompt = class extends AbstractPrompt {
244
256
  answerBuffer;
245
257
  #validators;
246
258
  #secure;
259
+ #securePlaceholder = null;
247
260
  constructor(options) {
248
261
  const {
249
262
  defaultValue,
@@ -258,7 +271,12 @@ var QuestionPrompt = class extends AbstractPrompt {
258
271
  this.defaultValue = defaultValue;
259
272
  this.tip = this.defaultValue ? ` (${this.defaultValue})` : "";
260
273
  this.#validators = validators;
261
- this.#secure = Boolean(secure);
274
+ if (typeof secure === "object") {
275
+ this.#secure = true;
276
+ this.#securePlaceholder = secure.placeholder;
277
+ } else {
278
+ this.#secure = Boolean(secure);
279
+ }
262
280
  this.questionSuffixError = "";
263
281
  }
264
282
  #question() {
@@ -267,10 +285,12 @@ var QuestionPrompt = class extends AbstractPrompt {
267
285
  this.history.push(questionQuery);
268
286
  this.rl.question(questionQuery, (answer) => {
269
287
  this.history.push(questionQuery + answer);
270
- this.mute = false;
288
+ this.reset();
271
289
  resolve(answer);
272
290
  });
273
- this.mute = this.#secure;
291
+ if (this.#securePlaceholder !== null) {
292
+ this.transformer = (input) => Buffer.from(this.#securePlaceholder.repeat(input.length), "utf-8");
293
+ }
274
294
  });
275
295
  }
276
296
  #getQuestionQuery() {
@@ -282,12 +302,17 @@ var QuestionPrompt = class extends AbstractPrompt {
282
302
  }
283
303
  #writeAnswer() {
284
304
  const prefix = this.answer ? SYMBOLS.Tick : SYMBOLS.Cross;
285
- const answer = styleText2("yellow", this.#secure ? "CONFIDENTIAL" : this.answer ?? "");
286
- this.write(`${prefix} ${styleText2("bold", this.message)} ${SYMBOLS.Pointer} ${answer}${EOL2}`);
305
+ const answer = this.answer ?? "";
306
+ const maskedAnswer = this.#securePlaceholder ? this.#securePlaceholder.repeat(answer.length) : answer;
307
+ const stylizedAnswer = styleText2(
308
+ "yellow",
309
+ this.#secure && this.#securePlaceholder === null ? "CONFIDENTIAL" : maskedAnswer
310
+ );
311
+ this.write(`${prefix} ${styleText2("bold", this.message)} ${SYMBOLS.Pointer} ${stylizedAnswer}${EOL2}`);
287
312
  }
288
313
  #onQuestionAnswer() {
289
314
  const questionLineCount = Math.ceil(
290
- wcwidth(stripAnsi(this.#getQuestionQuery() + this.answer)) / this.stdout.columns
315
+ stringLength(this.#getQuestionQuery() + this.answer) / this.stdout.columns
291
316
  );
292
317
  this.stdout.moveCursor(-this.stdout.columns, -questionLineCount);
293
318
  this.stdout.clearScreenDown();
@@ -336,7 +361,6 @@ var QuestionPrompt = class extends AbstractPrompt {
336
361
  // src/prompts/confirm.ts
337
362
  import { EOL as EOL3 } from "node:os";
338
363
  import { styleText as styleText3 } from "node:util";
339
- import wcwidth2 from "@topcli/wcwidth";
340
364
  var kToggleKeys = /* @__PURE__ */ new Set([
341
365
  "left",
342
366
  "right",
@@ -386,7 +410,7 @@ var ConfirmPrompt = class extends AbstractPrompt {
386
410
  #onKeypress(resolve, _value, key) {
387
411
  this.stdout.moveCursor(
388
412
  -this.stdout.columns,
389
- -Math.floor(wcwidth2(stripAnsi(this.#getQuestionQuery())) / this.stdout.columns)
413
+ -Math.floor(stringLength(this.#getQuestionQuery()) / this.stdout.columns)
390
414
  );
391
415
  this.stdout.clearScreenDown();
392
416
  if (key.name === "return") {
@@ -420,7 +444,7 @@ var ConfirmPrompt = class extends AbstractPrompt {
420
444
  this.clearLastLine();
421
445
  this.stdout.moveCursor(
422
446
  -this.stdout.columns,
423
- -Math.floor(wcwidth2(stripAnsi(this.#getQuestionQuery())) / this.stdout.columns)
447
+ -Math.floor(stringLength(this.#getQuestionQuery()) / this.stdout.columns)
424
448
  );
425
449
  this.stdout.clearScreenDown();
426
450
  this.write(`${this.selectedValue ? SYMBOLS.Tick : SYMBOLS.Cross} ${styleText3("bold", this.message)}${EOL3}`);
@@ -460,7 +484,6 @@ var ConfirmPrompt = class extends AbstractPrompt {
460
484
  // src/prompts/select.ts
461
485
  import { EOL as EOL4 } from "node:os";
462
486
  import { styleText as styleText4 } from "node:util";
463
- import wcwidth3 from "@topcli/wcwidth";
464
487
  var kRequiredChoiceProperties = ["label", "value"];
465
488
  var SelectPrompt = class extends AbstractPrompt {
466
489
  #boundExitEvent = () => void 0;
@@ -571,10 +594,10 @@ var SelectPrompt = class extends AbstractPrompt {
571
594
  this.write(str);
572
595
  }
573
596
  }
574
- #showAnsweredQuestion(choice) {
575
- const symbolPrefix = choice === "" ? SYMBOLS.Cross : SYMBOLS.Tick;
597
+ #showAnsweredQuestion(label) {
598
+ const symbolPrefix = label === "" ? SYMBOLS.Cross : SYMBOLS.Tick;
576
599
  const prefix = `${symbolPrefix} ${styleText4("bold", this.message)} ${SYMBOLS.Pointer}`;
577
- const formattedChoice = styleText4("yellow", typeof choice === "string" ? choice : choice.label);
600
+ const formattedChoice = styleText4("yellow", label);
578
601
  this.write(`${prefix} ${formattedChoice}${EOL4}`);
579
602
  }
580
603
  #onProcessExit() {
@@ -656,7 +679,7 @@ var SelectPrompt = class extends AbstractPrompt {
656
679
  }
657
680
  if (this.options.autocomplete) {
658
681
  let linesToClear2 = Math.ceil(
659
- wcwidth3(`${SYMBOLS.Pointer} ${this.autocompleteValue}`) / this.stdout.columns
682
+ stringLength(`${SYMBOLS.Pointer} ${this.autocompleteValue}`) / this.stdout.columns
660
683
  );
661
684
  while (linesToClear2 > 0) {
662
685
  this.clearLastLine();
@@ -666,14 +689,14 @@ var SelectPrompt = class extends AbstractPrompt {
666
689
  }
667
690
  if (clearRender) {
668
691
  const questionLineCount = Math.ceil(
669
- wcwidth3(stripAnsi(this.questionMessage)) / this.stdout.columns
692
+ stringLength(this.questionMessage) / this.stdout.columns
670
693
  );
671
694
  this.stdout.moveCursor(-this.stdout.columns, -(1 + questionLineCount));
672
695
  this.stdout.clearScreenDown();
673
696
  return;
674
697
  }
675
698
  if (error) {
676
- const linesToClear = Math.ceil(wcwidth3(this.questionMessage) / this.stdout.columns) + 1;
699
+ const linesToClear = Math.ceil(stringLength(this.questionMessage) / this.stdout.columns) + 1;
677
700
  this.stdout.moveCursor(0, -linesToClear);
678
701
  this.stdout.clearScreenDown();
679
702
  this.#showQuestion(error);
@@ -700,7 +723,6 @@ var SelectPrompt = class extends AbstractPrompt {
700
723
  // src/prompts/multiselect.ts
701
724
  import { EOL as EOL5 } from "node:os";
702
725
  import { styleText as styleText5 } from "node:util";
703
- import wcwidth4 from "@topcli/wcwidth";
704
726
  var kRequiredChoiceProperties2 = ["label", "value"];
705
727
  var MultiselectPrompt = class extends AbstractPrompt {
706
728
  #boundExitEvent = () => void 0;
@@ -941,7 +963,7 @@ var MultiselectPrompt = class extends AbstractPrompt {
941
963
  }
942
964
  if (this.options.autocomplete) {
943
965
  let linesToClear2 = Math.ceil(
944
- wcwidth4(`${SYMBOLS.Pointer} ${this.autocompleteValue}`) / this.stdout.columns
966
+ stringLength(`${SYMBOLS.Pointer} ${this.autocompleteValue}`) / this.stdout.columns
945
967
  );
946
968
  while (linesToClear2 > 0) {
947
969
  this.clearLastLine();
@@ -951,14 +973,14 @@ var MultiselectPrompt = class extends AbstractPrompt {
951
973
  }
952
974
  if (clearRender) {
953
975
  const questionLineCount = Math.ceil(
954
- wcwidth4(stripAnsi(this.questionMessage)) / this.stdout.columns
976
+ stringLength(this.questionMessage) / this.stdout.columns
955
977
  );
956
978
  this.stdout.moveCursor(-this.stdout.columns, -(1 + questionLineCount));
957
979
  this.stdout.clearScreenDown();
958
980
  return;
959
981
  }
960
982
  if (error) {
961
- const linesToClear = Math.ceil(wcwidth4(this.questionMessage) / this.stdout.columns) + 1;
983
+ const linesToClear = Math.ceil(stringLength(this.questionMessage) / this.stdout.columns) + 1;
962
984
  this.stdout.moveCursor(0, -linesToClear);
963
985
  this.stdout.clearScreenDown();
964
986
  this.#showQuestion(error);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@topcli/prompts",
3
- "version": "2.2.0",
3
+ "version": "2.4.0",
4
4
  "description": "Node.js user input library for command-line interfaces.",
5
5
  "scripts": {
6
6
  "build": "tsup index.ts --format cjs,esm --dts --clean",
@@ -33,16 +33,13 @@
33
33
  "devDependencies": {
34
34
  "@openally/config.eslint": "^2.0.0",
35
35
  "@openally/config.typescript": "^1.0.3",
36
- "@types/node": "^22.10.5",
36
+ "@types/node": "^24.0.3",
37
37
  "c8": "^10.1.3",
38
38
  "glob": "^11.0.0",
39
39
  "tsup": "^8.3.5",
40
40
  "tsx": "^4.19.2",
41
41
  "typescript": "^5.7.2"
42
42
  },
43
- "dependencies": {
44
- "@topcli/wcwidth": "^1.0.1"
45
- },
46
43
  "engines": {
47
44
  "node": "^20.12.0 || >=21.7.0"
48
45
  },