@topcli/prompts 1.1.0 → 1.2.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
@@ -27,20 +27,15 @@ $ yarn add @topcli/prompts
27
27
  You can locally run `node ./demo.js`
28
28
 
29
29
  ```js
30
- import { prompt, confirm, select } from '@topcli/prompts';
30
+ import { question, confirm, select } from "@topcli/prompts";
31
31
 
32
- const kTestRunner = ['node', 'tap', 'tape', 'vitest', 'mocha', 'ava'];
32
+ const kTestRunner = ["node", "tap", "tape", "vitest", "mocha", "ava"];
33
33
 
34
- const name = await prompt('Project name ?');
35
- const runner = await select('Choose a test runner', {
36
- choices: kTestRunner,
37
- maxVisible: 5
38
- });
39
- const isCLI = await confirm('Your project is a CLI ?', {
40
- initial: true
41
- });
34
+ const name = await question("Project name ?", { defaultValue: "foo" });
35
+ const runner = await select("Choose a test runner", { choices: kTestRunner, maxVisible: 5 });
36
+ const isCLI = await confirm("Your project is a CLI ?", { initial: true });
42
37
 
43
- console.log(name, runner, isCLI);^
38
+ console.log(name, runner, isCLI);
44
39
  ```
45
40
 
46
41
  ## API
@@ -141,6 +136,7 @@ export interface ConfirmOptions {
141
136
  <td align="center" valign="top" width="14.28%"><a href="https://www.linkedin.com/in/thomas-gentilhomme/"><img src="https://avatars.githubusercontent.com/u/4438263?v=4?s=100" width="100px;" alt="Gentilhomme"/><br /><sub><b>Gentilhomme</b></sub></a><br /><a href="https://github.com/TopCli/prompts/pulls?q=is%3Apr+reviewed-by%3Afraxken" title="Reviewed Pull Requests">👀</a></td>
142
137
  <td align="center" valign="top" width="14.28%"><a href="http://tonygo.dev"><img src="https://avatars0.githubusercontent.com/u/22824417?v=4?s=100" width="100px;" alt="Tony Gorez"/><br /><sub><b>Tony Gorez</b></sub></a><br /><a href="https://github.com/TopCli/prompts/pulls?q=is%3Apr+reviewed-by%3Atony-go" title="Reviewed Pull Requests">👀</a></td>
143
138
  <td align="center" valign="top" width="14.28%"><a href="http://sofiand.github.io/portfolio-client/"><img src="https://avatars.githubusercontent.com/u/39944043?v=4?s=100" width="100px;" alt="Yefis"/><br /><sub><b>Yefis</b></sub></a><br /><a href="https://github.com/TopCli/prompts/commits?author=SofianD" title="Code">💻</a> <a href="https://github.com/TopCli/prompts/commits?author=SofianD" title="Documentation">📖</a></td>
139
+ <td align="center" valign="top" width="14.28%"><a href="http://justie.dev"><img src="https://avatars.githubusercontent.com/u/7118300?v=4?s=100" width="100px;" alt="Ben"/><br /><sub><b>Ben</b></sub></a><br /><a href="https://github.com/TopCli/prompts/commits?author=JUSTIVE" title="Documentation">📖</a> <a href="#maintenance-JUSTIVE" title="Maintenance">🚧</a></td>
144
140
  </tr>
145
141
  </tbody>
146
142
  </table>
package/index.d.ts CHANGED
@@ -1,4 +1,10 @@
1
- export interface PromptOptions {
1
+ export interface QuestionOptions {
2
+ stdin?: NodeJS.ReadStream & {
3
+ fd: 0;
4
+ };
5
+ stdout?: NodeJS.WriteStream & {
6
+ fd: 1;
7
+ };
2
8
  defaultValue?: string;
3
9
  validators?: Validator[];
4
10
  }
@@ -24,7 +30,7 @@ export interface ConfirmOptions {
24
30
  initial?: boolean;
25
31
  }
26
32
 
27
- export function question(message: string, options?: PromptOptions): Promise<string>;
33
+ export function question(message: string, options?: QuestionOptions): Promise<string>;
28
34
  export function select(message: string, options: SelectOptions): Promise<string>;
29
35
  export function confirm(message: string, options?: ConfirmOptions): Promise<boolean>;
30
36
 
package/package.json CHANGED
@@ -1,9 +1,9 @@
1
1
  {
2
2
  "name": "@topcli/prompts",
3
- "version": "1.1.0",
3
+ "version": "1.2.0",
4
4
  "description": "Node.js user input library for command-line interfaces.",
5
5
  "scripts": {
6
- "test": "node --loader=esmock --test test/",
6
+ "test": "node --no-warnings=ExperimentalWarning --loader=esmock --test test/",
7
7
  "coverage": "c8 -r html npm run test",
8
8
  "lint": "eslint .",
9
9
  "lint:fix": "eslint . --fix"
@@ -24,14 +24,15 @@
24
24
  "type": "module",
25
25
  "devDependencies": {
26
26
  "@nodesecure/eslint-config": "^1.7.0",
27
- "c8": "^7.13.0",
28
- "eslint": "^8.38.0",
29
- "esmock": "^2.1.0"
27
+ "@types/node": "^20.4.5",
28
+ "c8": "^8.0.0",
29
+ "eslint": "^8.44.0",
30
+ "esmock": "^2.3.1"
30
31
  },
31
32
  "dependencies": {
32
33
  "is-unicode-supported": "^1.3.0",
33
34
  "kleur": "^4.1.5",
34
- "strip-ansi": "^7.0.1"
35
+ "strip-ansi": "^7.1.0"
35
36
  },
36
37
  "engines": {
37
38
  "node": ">=14"
@@ -8,7 +8,25 @@ import kleur from "kleur";
8
8
  import { AbstractPrompt } from "./abstract-prompt.js";
9
9
  import { SYMBOLS } from "./constants.js";
10
10
 
11
+ // CONSTANTS
12
+ const kToggleKeys = new Set([
13
+ "left",
14
+ "right",
15
+ "tab",
16
+ "q",
17
+ "a",
18
+ "d",
19
+ "h",
20
+ "j",
21
+ "k",
22
+ "l",
23
+ "space"
24
+ ]);
25
+
11
26
  export class ConfirmPrompt extends AbstractPrompt {
27
+ #boundKeyPressEvent;
28
+ #boundExitEvent;
29
+
12
30
  constructor(message, options = {}) {
13
31
  const {
14
32
  stdin = process.stdin,
@@ -17,55 +35,83 @@ export class ConfirmPrompt extends AbstractPrompt {
17
35
  } = options;
18
36
  super(message, stdin, stdout);
19
37
 
20
- const Yes = kleur.bold("Yes");
21
- const No = kleur.bold("No");
22
- this.tip = kleur.gray(initial ? `(${Yes}/no)` : `(yes/${No})`);
23
-
24
38
  this.initial = initial;
39
+ this.selectedValue = initial;
40
+ }
41
+
42
+ #getHint() {
43
+ const Yes = kleur.bold().underline().cyan("Yes");
44
+ const No = kleur.bold().underline().cyan("No");
45
+
46
+ return this.selectedValue ? `${Yes}/No` : `Yes/${No}`;
47
+ }
48
+
49
+ #render() {
50
+ this.write(this.#getQuestionQuery());
25
51
  }
26
52
 
27
53
  #question() {
28
54
  return new Promise((resolve) => {
29
55
  const questionQuery = this.#getQuestionQuery();
30
56
 
31
- this.rl.question(questionQuery,
32
- (answer) => {
33
- this.history.push(questionQuery + answer);
57
+ this.write(questionQuery);
58
+
59
+ this.#boundKeyPressEvent = this.#onKeypress.bind(this, resolve);
60
+ this.stdin.on("keypress", this.#boundKeyPressEvent);
34
61
 
35
- resolve(answer);
36
- }
37
- );
62
+ this.#boundExitEvent = this.#onProcessExit.bind(this);
63
+ process.once("exit", this.#boundExitEvent);
38
64
  });
39
65
  }
40
66
 
67
+ #onKeypress(resolve, value, key) {
68
+ this.stdin.pause();
69
+ this.stdout.moveCursor(-this.#getQuestionQuery().length, 0);
70
+ this.stdout.clearScreenDown(() => this.stdin.resume());
71
+
72
+ if (key.name === "return") {
73
+ resolve(this.selectedValue);
74
+
75
+ return;
76
+ }
77
+
78
+ if (kToggleKeys.has(key.name)) {
79
+ this.selectedValue = !this.selectedValue;
80
+ }
81
+
82
+ this.#render();
83
+ }
84
+
85
+ #onProcessExit() {
86
+ this.stdin.off("keypress", this.#boundKeyPressEvent);
87
+ }
88
+
41
89
  #getQuestionQuery() {
42
90
  const query = kleur.bold(`${SYMBOLS.QuestionMark} ${this.message}`);
43
91
 
44
- return `${query} ${this.tip} `;
92
+ return `${query} ${this.#getHint()}`;
45
93
  }
46
94
 
47
95
  #onQuestionAnswer() {
48
96
  this.clearLastLine();
49
- this.write(`${this.answer ? SYMBOLS.Tick : SYMBOLS.Cross} ${kleur.bold(this.message)}${EOL}`);
50
- }
51
-
52
- #validateResult(result) {
53
- if (typeof result !== "string") {
54
- return false;
55
- }
56
-
57
- return ["y", "yes", "no", "n"].includes(result.toLowerCase());
97
+ this.write(`${this.selectedValue ? SYMBOLS.Tick : SYMBOLS.Cross} ${kleur.bold(this.message)}${EOL}`);
58
98
  }
59
99
 
60
100
  async confirm() {
101
+ this.write(SYMBOLS.HideCursor);
102
+
61
103
  try {
62
- const result = await this.#question();
63
- this.answer = this.#validateResult(result) ? ["y", "yes"].includes(result.toLocaleLowerCase()) : this.initial;
104
+ await this.#question();
64
105
  this.#onQuestionAnswer();
65
106
 
66
- return this.answer;
107
+ return this.selectedValue;
67
108
  }
68
109
  finally {
110
+ this.write(SYMBOLS.ShowCursor);
111
+
112
+ this.#onProcessExit();
113
+ process.off("exit", this.#boundExitEvent);
114
+
69
115
  this.destroy();
70
116
  }
71
117
  }