mepcli 0.1.0 → 0.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/LICENSE +21 -21
- package/README.md +50 -50
- package/dist/core.d.ts +8 -1
- package/dist/core.js +337 -35
- package/dist/types.d.ts +24 -1
- package/example.ts +82 -82
- package/package.json +37 -37
package/LICENSE
CHANGED
|
@@ -1,21 +1,21 @@
|
|
|
1
|
-
MIT License
|
|
2
|
-
|
|
3
|
-
Copyright (c) 2025 CodeTease
|
|
4
|
-
|
|
5
|
-
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
-
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
-
in the Software without restriction, including without limitation the rights
|
|
8
|
-
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
-
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
-
furnished to do so, subject to the following conditions:
|
|
11
|
-
|
|
12
|
-
The above copyright notice and this permission notice shall be included in all
|
|
13
|
-
copies or substantial portions of the Software.
|
|
14
|
-
|
|
15
|
-
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
-
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
-
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
-
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
-
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
-
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
-
SOFTWARE.
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 CodeTease
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -1,51 +1,51 @@
|
|
|
1
|
-
# Mep: Minimalist CLI Prompt
|
|
2
|
-
|
|
3
|
-
**Mep** is a minimalist and zero-dependency library for creating interactive command-line prompts in Node.js. It focuses on simplicity, modern design, and robust input handling, including support for cursor movement and input validation.
|
|
4
|
-
|
|
5
|
-
## Features
|
|
6
|
-
|
|
7
|
-
- **Zero Dependency:** Keeps your project clean and fast.
|
|
8
|
-
- **Full-Featured Prompts:** Includes `text`, `password`, `select`, ch`eckbox, and `confirm`.
|
|
9
|
-
- **Responsive Input:** Supports cursor movement (Left/Right) and character insertion/deletion in `text` and `password` prompts.
|
|
10
|
-
- **Validation:** Built-in support for input validation with custom error messages.
|
|
11
|
-
- **Elegant Look:** Uses ANSI colors for a clean, modern CLI experience.
|
|
12
|
-
|
|
13
|
-
## Installation
|
|
14
|
-
|
|
15
|
-
```bash
|
|
16
|
-
npm install mepcli
|
|
17
|
-
# or
|
|
18
|
-
yarn add mepcli
|
|
19
|
-
```
|
|
20
|
-
|
|
21
|
-
## Usage Example
|
|
22
|
-
|
|
23
|
-
Mep provides a static class facade, `MepCLI`, for all interactions.
|
|
24
|
-
```javascript
|
|
25
|
-
import { MepCLI } from 'mepcli';
|
|
26
|
-
|
|
27
|
-
async function setup() {
|
|
28
|
-
// Text input with validation and cursor support
|
|
29
|
-
const projectName = await MepCLI.text({
|
|
30
|
-
message: "Enter the project name:",
|
|
31
|
-
validate: (v) => v.length > 5 || "Must be longer than 5 chars",
|
|
32
|
-
});
|
|
33
|
-
|
|
34
|
-
// Select menu
|
|
35
|
-
const choice = await MepCLI.select({
|
|
36
|
-
message: "Choose an option:",
|
|
37
|
-
choices: [
|
|
38
|
-
{ title: "Option A", value: 1 },
|
|
39
|
-
{ title: "Option B", value: 2 }
|
|
40
|
-
]
|
|
41
|
-
});
|
|
42
|
-
|
|
43
|
-
console.log(`\nProject: ${projectName}, Selected: ${choice}`);
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
setup();
|
|
47
|
-
```
|
|
48
|
-
|
|
49
|
-
## License
|
|
50
|
-
|
|
1
|
+
# Mep: Minimalist CLI Prompt
|
|
2
|
+
|
|
3
|
+
**Mep** is a minimalist and zero-dependency library for creating interactive command-line prompts in Node.js. It focuses on simplicity, modern design, and robust input handling, including support for cursor movement and input validation.
|
|
4
|
+
|
|
5
|
+
## Features
|
|
6
|
+
|
|
7
|
+
- **Zero Dependency:** Keeps your project clean and fast.
|
|
8
|
+
- **Full-Featured Prompts:** Includes `text`, `password`, `select`, ch`eckbox, and `confirm`.
|
|
9
|
+
- **Responsive Input:** Supports cursor movement (Left/Right) and character insertion/deletion in `text` and `password` prompts.
|
|
10
|
+
- **Validation:** Built-in support for input validation with custom error messages.
|
|
11
|
+
- **Elegant Look:** Uses ANSI colors for a clean, modern CLI experience.
|
|
12
|
+
|
|
13
|
+
## Installation
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
npm install mepcli
|
|
17
|
+
# or
|
|
18
|
+
yarn add mepcli
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
## Usage Example
|
|
22
|
+
|
|
23
|
+
Mep provides a static class facade, `MepCLI`, for all interactions.
|
|
24
|
+
```javascript
|
|
25
|
+
import { MepCLI } from 'mepcli';
|
|
26
|
+
|
|
27
|
+
async function setup() {
|
|
28
|
+
// Text input with validation and cursor support
|
|
29
|
+
const projectName = await MepCLI.text({
|
|
30
|
+
message: "Enter the project name:",
|
|
31
|
+
validate: (v) => v.length > 5 || "Must be longer than 5 chars",
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
// Select menu
|
|
35
|
+
const choice = await MepCLI.select({
|
|
36
|
+
message: "Choose an option:",
|
|
37
|
+
choices: [
|
|
38
|
+
{ title: "Option A", value: 1 },
|
|
39
|
+
{ title: "Option B", value: 2 }
|
|
40
|
+
]
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
console.log(`\nProject: ${projectName}, Selected: ${choice}`);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
setup();
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
## License
|
|
50
|
+
|
|
51
51
|
This project is under the **MIT License**.
|
package/dist/core.d.ts
CHANGED
|
@@ -1,11 +1,18 @@
|
|
|
1
|
-
import { TextOptions, SelectOptions, ConfirmOptions, CheckboxOptions } from './types';
|
|
1
|
+
import { TextOptions, SelectOptions, ConfirmOptions, CheckboxOptions, ThemeConfig, NumberOptions, ToggleOptions } from './types';
|
|
2
2
|
/**
|
|
3
3
|
* Public Facade for MepCLI
|
|
4
4
|
*/
|
|
5
5
|
export declare class MepCLI {
|
|
6
|
+
static theme: ThemeConfig;
|
|
7
|
+
/**
|
|
8
|
+
* Shows a spinner while a promise is pending.
|
|
9
|
+
*/
|
|
10
|
+
static spin<T>(message: string, taskPromise: Promise<T>): Promise<T>;
|
|
6
11
|
static text(options: TextOptions): Promise<string>;
|
|
7
12
|
static select(options: SelectOptions): Promise<any>;
|
|
8
13
|
static checkbox(options: CheckboxOptions): Promise<any[]>;
|
|
9
14
|
static confirm(options: ConfirmOptions): Promise<boolean>;
|
|
10
15
|
static password(options: TextOptions): Promise<string>;
|
|
16
|
+
static number(options: NumberOptions): Promise<number>;
|
|
17
|
+
static toggle(options: ToggleOptions): Promise<boolean>;
|
|
11
18
|
}
|
package/dist/core.js
CHANGED
|
@@ -77,6 +77,7 @@ class TextPrompt extends Prompt {
|
|
|
77
77
|
super(options);
|
|
78
78
|
this.errorMsg = '';
|
|
79
79
|
this.cursor = 0;
|
|
80
|
+
this.hasTyped = false;
|
|
80
81
|
this.value = options.initial || '';
|
|
81
82
|
this.cursor = this.value.length;
|
|
82
83
|
}
|
|
@@ -91,21 +92,21 @@ class TextPrompt extends Prompt {
|
|
|
91
92
|
}
|
|
92
93
|
// 1. Render the Prompt Message
|
|
93
94
|
this.print(ansi_1.ANSI.ERASE_LINE + ansi_1.ANSI.CURSOR_LEFT);
|
|
94
|
-
const icon = this.errorMsg ? `${
|
|
95
|
-
this.print(`${icon} ${ansi_1.ANSI.BOLD}${this.options.message}${ansi_1.ANSI.RESET} `);
|
|
95
|
+
const icon = this.errorMsg ? `${MepCLI.theme.error}✖` : `${MepCLI.theme.success}?`;
|
|
96
|
+
this.print(`${icon} ${ansi_1.ANSI.BOLD}${MepCLI.theme.title}${this.options.message}${ansi_1.ANSI.RESET} `);
|
|
96
97
|
// 2. Render the Value or Placeholder
|
|
97
|
-
if (!this.value && this.options.placeholder && !this.errorMsg) {
|
|
98
|
-
this.print(`${
|
|
98
|
+
if (!this.value && this.options.placeholder && !this.errorMsg && !this.hasTyped) {
|
|
99
|
+
this.print(`${MepCLI.theme.muted}${this.options.placeholder}${ansi_1.ANSI.RESET}`);
|
|
99
100
|
// Move cursor back to start so typing overwrites placeholder visually
|
|
100
101
|
this.print(`\x1b[${this.options.placeholder.length}D`);
|
|
101
102
|
}
|
|
102
103
|
else {
|
|
103
104
|
const displayValue = this.options.isPassword ? '*'.repeat(this.value.length) : this.value;
|
|
104
|
-
this.print(`${
|
|
105
|
+
this.print(`${MepCLI.theme.main}${displayValue}${ansi_1.ANSI.RESET}`);
|
|
105
106
|
}
|
|
106
107
|
// 3. Handle Error Message
|
|
107
108
|
if (this.errorMsg) {
|
|
108
|
-
this.print(`\n${ansi_1.ANSI.ERASE_LINE}${
|
|
109
|
+
this.print(`\n${ansi_1.ANSI.ERASE_LINE}${MepCLI.theme.error}>> ${this.errorMsg}${ansi_1.ANSI.RESET}`);
|
|
109
110
|
this.print(ansi_1.ANSI.UP); // Go back to input line
|
|
110
111
|
// Re-calculate position to end of input
|
|
111
112
|
const promptLen = this.options.message.length + 3; // Icon + 2 spaces
|
|
@@ -140,6 +141,7 @@ class TextPrompt extends Prompt {
|
|
|
140
141
|
}
|
|
141
142
|
// Backspace
|
|
142
143
|
if (char === '\u0008' || char === '\x7f') {
|
|
144
|
+
this.hasTyped = true;
|
|
143
145
|
if (this.cursor > 0) {
|
|
144
146
|
this.value = this.value.slice(0, this.cursor - 1) + this.value.slice(this.cursor);
|
|
145
147
|
this.cursor--;
|
|
@@ -166,6 +168,7 @@ class TextPrompt extends Prompt {
|
|
|
166
168
|
}
|
|
167
169
|
// Delete key
|
|
168
170
|
if (char === '\u001b[3~') {
|
|
171
|
+
this.hasTyped = true;
|
|
169
172
|
if (this.cursor < this.value.length) {
|
|
170
173
|
this.value = this.value.slice(0, this.cursor) + this.value.slice(this.cursor + 1);
|
|
171
174
|
this.errorMsg = '';
|
|
@@ -175,6 +178,7 @@ class TextPrompt extends Prompt {
|
|
|
175
178
|
}
|
|
176
179
|
// Regular Typing
|
|
177
180
|
if (char.length === 1 && !/^[\x00-\x1F]/.test(char)) {
|
|
181
|
+
this.hasTyped = true;
|
|
178
182
|
this.value = this.value.slice(0, this.cursor) + char + this.value.slice(this.cursor);
|
|
179
183
|
this.cursor++;
|
|
180
184
|
this.errorMsg = '';
|
|
@@ -187,40 +191,136 @@ class SelectPrompt extends Prompt {
|
|
|
187
191
|
constructor(options) {
|
|
188
192
|
super(options);
|
|
189
193
|
this.selectedIndex = 0;
|
|
194
|
+
this.searchBuffer = '';
|
|
195
|
+
// Custom render to handle variable height clearing
|
|
196
|
+
this.lastRenderHeight = 0;
|
|
197
|
+
// Find first non-separator index
|
|
198
|
+
this.selectedIndex = this.findNextSelectableIndex(-1, 1);
|
|
190
199
|
}
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
200
|
+
isSeparator(item) {
|
|
201
|
+
return item && item.separator === true;
|
|
202
|
+
}
|
|
203
|
+
findNextSelectableIndex(currentIndex, direction) {
|
|
204
|
+
let nextIndex = currentIndex + direction;
|
|
205
|
+
const choices = this.getFilteredChoices();
|
|
206
|
+
// Loop around logic
|
|
207
|
+
if (nextIndex < 0)
|
|
208
|
+
nextIndex = choices.length - 1;
|
|
209
|
+
if (nextIndex >= choices.length)
|
|
210
|
+
nextIndex = 0;
|
|
211
|
+
if (choices.length === 0)
|
|
212
|
+
return 0;
|
|
213
|
+
// Safety check to prevent infinite loop if all are separators (shouldn't happen in practice)
|
|
214
|
+
let count = 0;
|
|
215
|
+
while (this.isSeparator(choices[nextIndex]) && count < choices.length) {
|
|
216
|
+
nextIndex += direction;
|
|
217
|
+
if (nextIndex < 0)
|
|
218
|
+
nextIndex = choices.length - 1;
|
|
219
|
+
if (nextIndex >= choices.length)
|
|
220
|
+
nextIndex = 0;
|
|
221
|
+
count++;
|
|
196
222
|
}
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
}
|
|
223
|
+
return nextIndex;
|
|
224
|
+
}
|
|
225
|
+
getFilteredChoices() {
|
|
226
|
+
if (!this.searchBuffer)
|
|
227
|
+
return this.options.choices;
|
|
228
|
+
return this.options.choices.filter(c => {
|
|
229
|
+
if (this.isSeparator(c))
|
|
230
|
+
return false; // Hide separators when searching
|
|
231
|
+
return c.title.toLowerCase().includes(this.searchBuffer.toLowerCase());
|
|
207
232
|
});
|
|
208
233
|
}
|
|
234
|
+
renderWrapper(firstRender) {
|
|
235
|
+
if (!firstRender && this.lastRenderHeight > 0) {
|
|
236
|
+
this.print(`\x1b[${this.lastRenderHeight}A`);
|
|
237
|
+
}
|
|
238
|
+
let output = '';
|
|
239
|
+
const choices = this.getFilteredChoices();
|
|
240
|
+
// Header
|
|
241
|
+
const searchStr = this.searchBuffer ? ` ${MepCLI.theme.muted}(Filter: ${this.searchBuffer})${ansi_1.ANSI.RESET}` : '';
|
|
242
|
+
output += `${ansi_1.ANSI.ERASE_LINE}${ansi_1.ANSI.CURSOR_LEFT}${MepCLI.theme.success}?${ansi_1.ANSI.RESET} ${ansi_1.ANSI.BOLD}${MepCLI.theme.title}${this.options.message}${ansi_1.ANSI.RESET}${searchStr}\n`;
|
|
243
|
+
if (choices.length === 0) {
|
|
244
|
+
output += `${ansi_1.ANSI.ERASE_LINE}${ansi_1.ANSI.CURSOR_LEFT} ${MepCLI.theme.muted}No results found${ansi_1.ANSI.RESET}\n`;
|
|
245
|
+
}
|
|
246
|
+
else {
|
|
247
|
+
choices.forEach((choice, index) => {
|
|
248
|
+
output += `${ansi_1.ANSI.ERASE_LINE}${ansi_1.ANSI.CURSOR_LEFT}`;
|
|
249
|
+
if (this.isSeparator(choice)) {
|
|
250
|
+
output += ` ${ansi_1.ANSI.DIM}${choice.text || '────────'}${ansi_1.ANSI.RESET}\n`;
|
|
251
|
+
}
|
|
252
|
+
else {
|
|
253
|
+
if (index === this.selectedIndex) {
|
|
254
|
+
output += `${MepCLI.theme.main}❯ ${choice.title}${ansi_1.ANSI.RESET}\n`;
|
|
255
|
+
}
|
|
256
|
+
else {
|
|
257
|
+
output += ` ${choice.title}\n`;
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
});
|
|
261
|
+
}
|
|
262
|
+
this.print(output);
|
|
263
|
+
// Clear remaining lines if list shrunk
|
|
264
|
+
const currentHeight = choices.length + 1 + (choices.length === 0 ? 1 : 0);
|
|
265
|
+
const linesToClear = this.lastRenderHeight - currentHeight;
|
|
266
|
+
if (linesToClear > 0) {
|
|
267
|
+
for (let i = 0; i < linesToClear; i++) {
|
|
268
|
+
this.print(`${ansi_1.ANSI.ERASE_LINE}\n`);
|
|
269
|
+
}
|
|
270
|
+
this.print(`\x1b[${linesToClear}A`); // Move back up
|
|
271
|
+
}
|
|
272
|
+
this.lastRenderHeight = currentHeight;
|
|
273
|
+
}
|
|
274
|
+
render(firstRender) {
|
|
275
|
+
this.print(ansi_1.ANSI.HIDE_CURSOR);
|
|
276
|
+
this.renderWrapper(firstRender);
|
|
277
|
+
}
|
|
209
278
|
handleInput(char) {
|
|
279
|
+
const choices = this.getFilteredChoices();
|
|
210
280
|
if (char === '\r' || char === '\n') {
|
|
281
|
+
if (choices.length === 0) {
|
|
282
|
+
this.searchBuffer = '';
|
|
283
|
+
this.selectedIndex = this.findNextSelectableIndex(-1, 1);
|
|
284
|
+
this.render(false);
|
|
285
|
+
return;
|
|
286
|
+
}
|
|
287
|
+
if (this.isSeparator(choices[this.selectedIndex]))
|
|
288
|
+
return;
|
|
211
289
|
this.cleanup();
|
|
212
|
-
this.print(`\x1b[${this.options.choices.length - this.selectedIndex}B`);
|
|
213
290
|
this.print(ansi_1.ANSI.SHOW_CURSOR);
|
|
214
291
|
if (this._resolve)
|
|
215
|
-
this._resolve(
|
|
292
|
+
this._resolve(choices[this.selectedIndex].value);
|
|
216
293
|
return;
|
|
217
294
|
}
|
|
218
295
|
if (char === '\u001b[A') { // Up
|
|
219
|
-
|
|
220
|
-
|
|
296
|
+
if (choices.length > 0) {
|
|
297
|
+
this.selectedIndex = this.findNextSelectableIndex(this.selectedIndex, -1);
|
|
298
|
+
this.render(false);
|
|
299
|
+
}
|
|
300
|
+
return;
|
|
221
301
|
}
|
|
222
302
|
if (char === '\u001b[B') { // Down
|
|
223
|
-
|
|
303
|
+
if (choices.length > 0) {
|
|
304
|
+
this.selectedIndex = this.findNextSelectableIndex(this.selectedIndex, 1);
|
|
305
|
+
this.render(false);
|
|
306
|
+
}
|
|
307
|
+
return;
|
|
308
|
+
}
|
|
309
|
+
// Backspace
|
|
310
|
+
if (char === '\u0008' || char === '\x7f') {
|
|
311
|
+
if (this.searchBuffer.length > 0) {
|
|
312
|
+
this.searchBuffer = this.searchBuffer.slice(0, -1);
|
|
313
|
+
this.selectedIndex = 0; // Reset selection
|
|
314
|
+
this.selectedIndex = this.findNextSelectableIndex(-1, 1);
|
|
315
|
+
this.render(false);
|
|
316
|
+
}
|
|
317
|
+
return;
|
|
318
|
+
}
|
|
319
|
+
// Typing
|
|
320
|
+
if (char.length === 1 && !/^[\x00-\x1F]/.test(char)) {
|
|
321
|
+
this.searchBuffer += char;
|
|
322
|
+
this.selectedIndex = 0; // Reset selection
|
|
323
|
+
this.selectedIndex = this.findNextSelectableIndex(-1, 1);
|
|
224
324
|
this.render(false);
|
|
225
325
|
}
|
|
226
326
|
}
|
|
@@ -241,22 +341,22 @@ class CheckboxPrompt extends Prompt {
|
|
|
241
341
|
this.print(`\x1b[${this.options.choices.length + 1 + extraLines}A`);
|
|
242
342
|
}
|
|
243
343
|
this.print(`${ansi_1.ANSI.ERASE_LINE}${ansi_1.ANSI.CURSOR_LEFT}`);
|
|
244
|
-
const icon = this.errorMsg ? `${
|
|
245
|
-
this.print(`${icon} ${ansi_1.ANSI.BOLD}${this.options.message}${ansi_1.ANSI.RESET} ${
|
|
344
|
+
const icon = this.errorMsg ? `${MepCLI.theme.error}✖` : `${MepCLI.theme.success}?`;
|
|
345
|
+
this.print(`${icon} ${ansi_1.ANSI.BOLD}${MepCLI.theme.title}${this.options.message}${ansi_1.ANSI.RESET} ${MepCLI.theme.muted}(Press <space> to select, <enter> to confirm)${ansi_1.ANSI.RESET}\n`);
|
|
246
346
|
this.options.choices.forEach((choice, index) => {
|
|
247
347
|
this.print(`${ansi_1.ANSI.ERASE_LINE}${ansi_1.ANSI.CURSOR_LEFT}`);
|
|
248
|
-
const cursor = index === this.selectedIndex ? `${
|
|
348
|
+
const cursor = index === this.selectedIndex ? `${MepCLI.theme.main}❯${ansi_1.ANSI.RESET}` : ' ';
|
|
249
349
|
const isChecked = this.checkedState[index];
|
|
250
350
|
const checkbox = isChecked
|
|
251
|
-
? `${
|
|
252
|
-
: `${
|
|
351
|
+
? `${MepCLI.theme.success}◉${ansi_1.ANSI.RESET}`
|
|
352
|
+
: `${MepCLI.theme.muted}◯${ansi_1.ANSI.RESET}`;
|
|
253
353
|
const title = index === this.selectedIndex
|
|
254
|
-
? `${
|
|
354
|
+
? `${MepCLI.theme.main}${choice.title}${ansi_1.ANSI.RESET}`
|
|
255
355
|
: choice.title;
|
|
256
356
|
this.print(`${cursor} ${checkbox} ${title}\n`);
|
|
257
357
|
});
|
|
258
358
|
if (this.errorMsg) {
|
|
259
|
-
this.print(`${ansi_1.ANSI.ERASE_LINE}${
|
|
359
|
+
this.print(`${ansi_1.ANSI.ERASE_LINE}${MepCLI.theme.error}>> ${this.errorMsg}${ansi_1.ANSI.RESET}`);
|
|
260
360
|
}
|
|
261
361
|
else if (!firstRender) {
|
|
262
362
|
this.print(`${ansi_1.ANSI.ERASE_LINE}`);
|
|
@@ -323,9 +423,9 @@ class ConfirmPrompt extends Prompt {
|
|
|
323
423
|
this.print(`${ansi_1.ANSI.ERASE_LINE}${ansi_1.ANSI.CURSOR_LEFT}`);
|
|
324
424
|
}
|
|
325
425
|
const hint = this.value ? `${ansi_1.ANSI.BOLD}Yes${ansi_1.ANSI.RESET}/no` : `yes/${ansi_1.ANSI.BOLD}No${ansi_1.ANSI.RESET}`;
|
|
326
|
-
this.print(`${
|
|
426
|
+
this.print(`${MepCLI.theme.success}?${ansi_1.ANSI.RESET} ${ansi_1.ANSI.BOLD}${MepCLI.theme.title}${this.options.message}${ansi_1.ANSI.RESET} ${MepCLI.theme.muted}(${hint})${ansi_1.ANSI.RESET} `);
|
|
327
427
|
const text = this.value ? 'Yes' : 'No';
|
|
328
|
-
this.print(`${
|
|
428
|
+
this.print(`${MepCLI.theme.main}${text}${ansi_1.ANSI.RESET}\x1b[${text.length}D`);
|
|
329
429
|
}
|
|
330
430
|
handleInput(char) {
|
|
331
431
|
const c = char.toLowerCase();
|
|
@@ -343,10 +443,199 @@ class ConfirmPrompt extends Prompt {
|
|
|
343
443
|
}
|
|
344
444
|
}
|
|
345
445
|
}
|
|
446
|
+
// --- Implementation: Toggle Prompt ---
|
|
447
|
+
class TogglePrompt extends Prompt {
|
|
448
|
+
constructor(options) {
|
|
449
|
+
super(options);
|
|
450
|
+
this.value = options.initial ?? false;
|
|
451
|
+
}
|
|
452
|
+
render(firstRender) {
|
|
453
|
+
this.print(ansi_1.ANSI.HIDE_CURSOR);
|
|
454
|
+
if (!firstRender) {
|
|
455
|
+
this.print(`${ansi_1.ANSI.ERASE_LINE}${ansi_1.ANSI.CURSOR_LEFT}`);
|
|
456
|
+
}
|
|
457
|
+
const activeText = this.options.activeText || 'ON';
|
|
458
|
+
const inactiveText = this.options.inactiveText || 'OFF';
|
|
459
|
+
let toggleDisplay = '';
|
|
460
|
+
if (this.value) {
|
|
461
|
+
toggleDisplay = `${MepCLI.theme.main}[${ansi_1.ANSI.BOLD}${activeText}${ansi_1.ANSI.RESET}${MepCLI.theme.main}]${ansi_1.ANSI.RESET} ${MepCLI.theme.muted}${inactiveText}${ansi_1.ANSI.RESET}`;
|
|
462
|
+
}
|
|
463
|
+
else {
|
|
464
|
+
toggleDisplay = `${MepCLI.theme.muted}${activeText}${ansi_1.ANSI.RESET} ${MepCLI.theme.main}[${ansi_1.ANSI.BOLD}${inactiveText}${ansi_1.ANSI.RESET}${MepCLI.theme.main}]${ansi_1.ANSI.RESET}`;
|
|
465
|
+
}
|
|
466
|
+
this.print(`${MepCLI.theme.success}?${ansi_1.ANSI.RESET} ${ansi_1.ANSI.BOLD}${MepCLI.theme.title}${this.options.message}${ansi_1.ANSI.RESET} ${toggleDisplay}`);
|
|
467
|
+
this.print(`\x1b[${toggleDisplay.length}D`); // Move back is not really needed as we hide cursor, but kept for consistency
|
|
468
|
+
}
|
|
469
|
+
handleInput(char) {
|
|
470
|
+
if (char === '\r' || char === '\n') {
|
|
471
|
+
this.submit(this.value);
|
|
472
|
+
return;
|
|
473
|
+
}
|
|
474
|
+
if (char === '\u001b[D' || char === '\u001b[C' || char === 'h' || char === 'l') { // Left/Right
|
|
475
|
+
this.value = !this.value;
|
|
476
|
+
this.render(false);
|
|
477
|
+
}
|
|
478
|
+
if (char === ' ') {
|
|
479
|
+
this.value = !this.value;
|
|
480
|
+
this.render(false);
|
|
481
|
+
}
|
|
482
|
+
}
|
|
483
|
+
}
|
|
484
|
+
// --- Implementation: Number Prompt ---
|
|
485
|
+
class NumberPrompt extends Prompt {
|
|
486
|
+
constructor(options) {
|
|
487
|
+
super(options);
|
|
488
|
+
this.cursor = 0;
|
|
489
|
+
this.errorMsg = '';
|
|
490
|
+
this.value = options.initial ?? 0;
|
|
491
|
+
this.stringValue = this.value.toString();
|
|
492
|
+
this.cursor = this.stringValue.length;
|
|
493
|
+
}
|
|
494
|
+
render(firstRender) {
|
|
495
|
+
this.print(ansi_1.ANSI.SHOW_CURSOR);
|
|
496
|
+
if (!firstRender) {
|
|
497
|
+
this.print(ansi_1.ANSI.ERASE_LINE + ansi_1.ANSI.CURSOR_LEFT);
|
|
498
|
+
if (this.errorMsg) {
|
|
499
|
+
this.print(ansi_1.ANSI.UP + ansi_1.ANSI.ERASE_LINE + ansi_1.ANSI.CURSOR_LEFT);
|
|
500
|
+
}
|
|
501
|
+
}
|
|
502
|
+
// 1. Render the Prompt Message
|
|
503
|
+
this.print(ansi_1.ANSI.ERASE_LINE + ansi_1.ANSI.CURSOR_LEFT);
|
|
504
|
+
const icon = this.errorMsg ? `${MepCLI.theme.error}✖` : `${MepCLI.theme.success}?`;
|
|
505
|
+
this.print(`${icon} ${ansi_1.ANSI.BOLD}${MepCLI.theme.title}${this.options.message}${ansi_1.ANSI.RESET} `);
|
|
506
|
+
// 2. Render the Value
|
|
507
|
+
this.print(`${MepCLI.theme.main}${this.stringValue}${ansi_1.ANSI.RESET}`);
|
|
508
|
+
// 3. Handle Error Message
|
|
509
|
+
if (this.errorMsg) {
|
|
510
|
+
this.print(`\n${ansi_1.ANSI.ERASE_LINE}${MepCLI.theme.error}>> ${this.errorMsg}${ansi_1.ANSI.RESET}`);
|
|
511
|
+
this.print(ansi_1.ANSI.UP);
|
|
512
|
+
const promptLen = this.options.message.length + 3;
|
|
513
|
+
const valLen = this.stringValue.length;
|
|
514
|
+
this.print(`\x1b[1000D\x1b[${promptLen + valLen}C`);
|
|
515
|
+
}
|
|
516
|
+
// 4. Position Cursor
|
|
517
|
+
const diff = this.stringValue.length - this.cursor;
|
|
518
|
+
if (diff > 0) {
|
|
519
|
+
this.print(`\x1b[${diff}D`);
|
|
520
|
+
}
|
|
521
|
+
}
|
|
522
|
+
handleInput(char) {
|
|
523
|
+
// Enter
|
|
524
|
+
if (char === '\r' || char === '\n') {
|
|
525
|
+
const num = parseFloat(this.stringValue);
|
|
526
|
+
if (isNaN(num)) {
|
|
527
|
+
this.errorMsg = 'Please enter a valid number.';
|
|
528
|
+
this.render(false);
|
|
529
|
+
return;
|
|
530
|
+
}
|
|
531
|
+
if (this.options.min !== undefined && num < this.options.min) {
|
|
532
|
+
this.errorMsg = `Minimum value is ${this.options.min}`;
|
|
533
|
+
this.render(false);
|
|
534
|
+
return;
|
|
535
|
+
}
|
|
536
|
+
if (this.options.max !== undefined && num > this.options.max) {
|
|
537
|
+
this.errorMsg = `Maximum value is ${this.options.max}`;
|
|
538
|
+
this.render(false);
|
|
539
|
+
return;
|
|
540
|
+
}
|
|
541
|
+
if (this.errorMsg) {
|
|
542
|
+
this.print(`\n${ansi_1.ANSI.ERASE_LINE}${ansi_1.ANSI.UP}`);
|
|
543
|
+
}
|
|
544
|
+
this.submit(num);
|
|
545
|
+
return;
|
|
546
|
+
}
|
|
547
|
+
// Up Arrow (Increment)
|
|
548
|
+
if (char === '\u001b[A') {
|
|
549
|
+
let num = parseFloat(this.stringValue) || 0;
|
|
550
|
+
num += (this.options.step ?? 1);
|
|
551
|
+
if (this.options.max !== undefined && num > this.options.max)
|
|
552
|
+
num = this.options.max;
|
|
553
|
+
this.stringValue = num.toString();
|
|
554
|
+
this.cursor = this.stringValue.length;
|
|
555
|
+
this.errorMsg = '';
|
|
556
|
+
this.render(false);
|
|
557
|
+
return;
|
|
558
|
+
}
|
|
559
|
+
// Down Arrow (Decrement)
|
|
560
|
+
if (char === '\u001b[B') {
|
|
561
|
+
let num = parseFloat(this.stringValue) || 0;
|
|
562
|
+
num -= (this.options.step ?? 1);
|
|
563
|
+
if (this.options.min !== undefined && num < this.options.min)
|
|
564
|
+
num = this.options.min;
|
|
565
|
+
this.stringValue = num.toString();
|
|
566
|
+
this.cursor = this.stringValue.length;
|
|
567
|
+
this.errorMsg = '';
|
|
568
|
+
this.render(false);
|
|
569
|
+
return;
|
|
570
|
+
}
|
|
571
|
+
// Backspace
|
|
572
|
+
if (char === '\u0008' || char === '\x7f') {
|
|
573
|
+
if (this.cursor > 0) {
|
|
574
|
+
this.stringValue = this.stringValue.slice(0, this.cursor - 1) + this.stringValue.slice(this.cursor);
|
|
575
|
+
this.cursor--;
|
|
576
|
+
this.errorMsg = '';
|
|
577
|
+
this.render(false);
|
|
578
|
+
}
|
|
579
|
+
return;
|
|
580
|
+
}
|
|
581
|
+
// Arrow Left
|
|
582
|
+
if (char === '\u001b[D') {
|
|
583
|
+
if (this.cursor > 0) {
|
|
584
|
+
this.cursor--;
|
|
585
|
+
this.render(false);
|
|
586
|
+
}
|
|
587
|
+
return;
|
|
588
|
+
}
|
|
589
|
+
// Arrow Right
|
|
590
|
+
if (char === '\u001b[C') {
|
|
591
|
+
if (this.cursor < this.stringValue.length) {
|
|
592
|
+
this.cursor++;
|
|
593
|
+
this.render(false);
|
|
594
|
+
}
|
|
595
|
+
return;
|
|
596
|
+
}
|
|
597
|
+
// Numeric Input (and . and -)
|
|
598
|
+
if (/^[0-9.\-]$/.test(char)) {
|
|
599
|
+
if (char === '-' && (this.cursor !== 0 || this.stringValue.includes('-')))
|
|
600
|
+
return;
|
|
601
|
+
if (char === '.' && this.stringValue.includes('.'))
|
|
602
|
+
return;
|
|
603
|
+
this.stringValue = this.stringValue.slice(0, this.cursor) + char + this.stringValue.slice(this.cursor);
|
|
604
|
+
this.cursor++;
|
|
605
|
+
this.errorMsg = '';
|
|
606
|
+
this.render(false);
|
|
607
|
+
}
|
|
608
|
+
}
|
|
609
|
+
}
|
|
346
610
|
/**
|
|
347
611
|
* Public Facade for MepCLI
|
|
348
612
|
*/
|
|
349
613
|
class MepCLI {
|
|
614
|
+
/**
|
|
615
|
+
* Shows a spinner while a promise is pending.
|
|
616
|
+
*/
|
|
617
|
+
static async spin(message, taskPromise) {
|
|
618
|
+
const frames = ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏'];
|
|
619
|
+
let i = 0;
|
|
620
|
+
process.stdout.write(ansi_1.ANSI.HIDE_CURSOR);
|
|
621
|
+
const interval = setInterval(() => {
|
|
622
|
+
process.stdout.write(`${ansi_1.ANSI.ERASE_LINE}${ansi_1.ANSI.CURSOR_LEFT}${MepCLI.theme.main}${frames[i]}${ansi_1.ANSI.RESET} ${message}`);
|
|
623
|
+
i = (i + 1) % frames.length;
|
|
624
|
+
}, 80);
|
|
625
|
+
try {
|
|
626
|
+
const result = await taskPromise;
|
|
627
|
+
clearInterval(interval);
|
|
628
|
+
process.stdout.write(`${ansi_1.ANSI.ERASE_LINE}${ansi_1.ANSI.CURSOR_LEFT}${MepCLI.theme.success}✔${ansi_1.ANSI.RESET} ${message}\n`);
|
|
629
|
+
process.stdout.write(ansi_1.ANSI.SHOW_CURSOR);
|
|
630
|
+
return result;
|
|
631
|
+
}
|
|
632
|
+
catch (error) {
|
|
633
|
+
clearInterval(interval);
|
|
634
|
+
process.stdout.write(`${ansi_1.ANSI.ERASE_LINE}${ansi_1.ANSI.CURSOR_LEFT}${MepCLI.theme.error}✖${ansi_1.ANSI.RESET} ${message}\n`);
|
|
635
|
+
process.stdout.write(ansi_1.ANSI.SHOW_CURSOR);
|
|
636
|
+
throw error;
|
|
637
|
+
}
|
|
638
|
+
}
|
|
350
639
|
static text(options) {
|
|
351
640
|
return new TextPrompt(options).run();
|
|
352
641
|
}
|
|
@@ -362,5 +651,18 @@ class MepCLI {
|
|
|
362
651
|
static password(options) {
|
|
363
652
|
return new TextPrompt({ ...options, isPassword: true }).run();
|
|
364
653
|
}
|
|
654
|
+
static number(options) {
|
|
655
|
+
return new NumberPrompt(options).run();
|
|
656
|
+
}
|
|
657
|
+
static toggle(options) {
|
|
658
|
+
return new TogglePrompt(options).run();
|
|
659
|
+
}
|
|
365
660
|
}
|
|
366
661
|
exports.MepCLI = MepCLI;
|
|
662
|
+
MepCLI.theme = {
|
|
663
|
+
main: ansi_1.ANSI.FG_CYAN,
|
|
664
|
+
success: ansi_1.ANSI.FG_GREEN,
|
|
665
|
+
error: ansi_1.ANSI.FG_RED,
|
|
666
|
+
muted: ansi_1.ANSI.FG_GRAY,
|
|
667
|
+
title: ansi_1.ANSI.RESET
|
|
668
|
+
};
|
package/dist/types.d.ts
CHANGED
|
@@ -1,6 +1,13 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Type definitions for Mep CLI interactions.
|
|
3
3
|
*/
|
|
4
|
+
export interface ThemeConfig {
|
|
5
|
+
main: string;
|
|
6
|
+
success: string;
|
|
7
|
+
error: string;
|
|
8
|
+
muted: string;
|
|
9
|
+
title: string;
|
|
10
|
+
}
|
|
4
11
|
export interface BaseOptions {
|
|
5
12
|
message: string;
|
|
6
13
|
}
|
|
@@ -10,13 +17,17 @@ export interface TextOptions extends BaseOptions {
|
|
|
10
17
|
validate?: (value: string) => string | boolean;
|
|
11
18
|
isPassword?: boolean;
|
|
12
19
|
}
|
|
20
|
+
export interface Separator {
|
|
21
|
+
separator: true;
|
|
22
|
+
text?: string;
|
|
23
|
+
}
|
|
13
24
|
export interface SelectChoice {
|
|
14
25
|
title: string;
|
|
15
26
|
value: any;
|
|
16
27
|
description?: string;
|
|
17
28
|
}
|
|
18
29
|
export interface SelectOptions extends BaseOptions {
|
|
19
|
-
choices: SelectChoice[];
|
|
30
|
+
choices: (SelectChoice | Separator)[];
|
|
20
31
|
}
|
|
21
32
|
export interface CheckboxChoice extends SelectChoice {
|
|
22
33
|
selected?: boolean;
|
|
@@ -29,3 +40,15 @@ export interface CheckboxOptions extends BaseOptions {
|
|
|
29
40
|
export interface ConfirmOptions extends BaseOptions {
|
|
30
41
|
initial?: boolean;
|
|
31
42
|
}
|
|
43
|
+
export interface NumberOptions extends BaseOptions {
|
|
44
|
+
initial?: number;
|
|
45
|
+
min?: number;
|
|
46
|
+
max?: number;
|
|
47
|
+
step?: number;
|
|
48
|
+
placeholder?: string;
|
|
49
|
+
}
|
|
50
|
+
export interface ToggleOptions extends BaseOptions {
|
|
51
|
+
initial?: boolean;
|
|
52
|
+
activeText?: string;
|
|
53
|
+
inactiveText?: string;
|
|
54
|
+
}
|
package/example.ts
CHANGED
|
@@ -1,83 +1,83 @@
|
|
|
1
|
-
import { MepCLI } from './src'; // Or mepcli if installed via NPM
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* Runs a comprehensive set of tests for all MepCLI prompt types.
|
|
5
|
-
*/
|
|
6
|
-
async function runAllTests() {
|
|
7
|
-
console.clear();
|
|
8
|
-
console.log("--- MepCLI Comprehensive Test Suite (Neutralized) ---\n");
|
|
9
|
-
|
|
10
|
-
try {
|
|
11
|
-
// --- 1. Text Prompt Test (with Validation) ---
|
|
12
|
-
const projectName = await MepCLI.text({
|
|
13
|
-
message: "Enter the name for your new project:",
|
|
14
|
-
placeholder: "e.g., minimalist-cli-app",
|
|
15
|
-
initial: "MepProject",
|
|
16
|
-
validate: (value) => {
|
|
17
|
-
if (value.length < 3) {
|
|
18
|
-
return "Project name must be at least 3 characters long.";
|
|
19
|
-
}
|
|
20
|
-
if (value.includes('&')) {
|
|
21
|
-
return "Project name cannot contain '&' symbol.";
|
|
22
|
-
}
|
|
23
|
-
return true;
|
|
24
|
-
}
|
|
25
|
-
});
|
|
26
|
-
console.log(`\n✅ Text Result: Project name set to '${projectName}'`);
|
|
27
|
-
|
|
28
|
-
// --- 2. Password Prompt Test ---
|
|
29
|
-
const apiKey = await MepCLI.password({
|
|
30
|
-
message: "Enter the project's external API key:",
|
|
31
|
-
placeholder: "Input will be hidden..."
|
|
32
|
-
});
|
|
33
|
-
// Note: Do not log the actual key in a real app.
|
|
34
|
-
console.log(`\n✅ Password Result: API key entered (length: ${apiKey.length})`);
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
// --- 3. Select Prompt Test (Single Choice) ---
|
|
38
|
-
const theme = await MepCLI.select({
|
|
39
|
-
message: "Choose your preferred editor color theme:",
|
|
40
|
-
choices: [
|
|
41
|
-
{ title: "Dark Mode (Default)", value: "dark" },
|
|
42
|
-
{ title: "Light Mode (Classic)", value: "light" },
|
|
43
|
-
{ title: "High Contrast (Accessibility)", value: "contrast" },
|
|
44
|
-
{ title: "Monokai Pro", value: "monokai" },
|
|
45
|
-
]
|
|
46
|
-
});
|
|
47
|
-
console.log(`\n✅ Select Result: Chosen theme is: ${theme}`);
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
// --- 4. Checkbox Prompt Test (Multi-Choice with Min/Max) ---
|
|
51
|
-
const buildTools = await MepCLI.checkbox({
|
|
52
|
-
message: "Select your required bundlers/build tools (Min 1, Max 2):",
|
|
53
|
-
min: 1,
|
|
54
|
-
max: 2,
|
|
55
|
-
choices: [
|
|
56
|
-
{ title: "Webpack", value: "webpack" },
|
|
57
|
-
{ title: "Vite", value: "vite", selected: true },
|
|
58
|
-
{ title: "Rollup", value: "rollup" },
|
|
59
|
-
{ title: "esbuild", value: "esbuild" }
|
|
60
|
-
]
|
|
61
|
-
});
|
|
62
|
-
console.log(`\n✅ Checkbox Result: Selected build tools: [${buildTools.join(', ')}]`);
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
// --- 5. Confirm Prompt Test ---
|
|
66
|
-
const proceed = await MepCLI.confirm({
|
|
67
|
-
message: "Do you want to continue with the installation setup?",
|
|
68
|
-
initial: true
|
|
69
|
-
});
|
|
70
|
-
console.log(`\n✅ Confirm Result: Setup decision: ${proceed ? 'Proceed' : 'Cancel'}`);
|
|
71
|
-
|
|
72
|
-
console.log("\n--- All MepCLI tests completed successfully! ---");
|
|
73
|
-
|
|
74
|
-
} catch (e) {
|
|
75
|
-
if (e instanceof Error && e.message === 'User force closed') {
|
|
76
|
-
console.log("\nOperation cancelled by user (Ctrl+C).");
|
|
77
|
-
} else {
|
|
78
|
-
console.error("\nAn error occurred during prompt execution:", e);
|
|
79
|
-
}
|
|
80
|
-
}
|
|
81
|
-
}
|
|
82
|
-
|
|
1
|
+
import { MepCLI } from './src'; // Or mepcli if installed via NPM
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Runs a comprehensive set of tests for all MepCLI prompt types.
|
|
5
|
+
*/
|
|
6
|
+
async function runAllTests() {
|
|
7
|
+
console.clear();
|
|
8
|
+
console.log("--- MepCLI Comprehensive Test Suite (Neutralized) ---\n");
|
|
9
|
+
|
|
10
|
+
try {
|
|
11
|
+
// --- 1. Text Prompt Test (with Validation) ---
|
|
12
|
+
const projectName = await MepCLI.text({
|
|
13
|
+
message: "Enter the name for your new project:",
|
|
14
|
+
placeholder: "e.g., minimalist-cli-app",
|
|
15
|
+
initial: "MepProject",
|
|
16
|
+
validate: (value) => {
|
|
17
|
+
if (value.length < 3) {
|
|
18
|
+
return "Project name must be at least 3 characters long.";
|
|
19
|
+
}
|
|
20
|
+
if (value.includes('&')) {
|
|
21
|
+
return "Project name cannot contain '&' symbol.";
|
|
22
|
+
}
|
|
23
|
+
return true;
|
|
24
|
+
}
|
|
25
|
+
});
|
|
26
|
+
console.log(`\n✅ Text Result: Project name set to '${projectName}'`);
|
|
27
|
+
|
|
28
|
+
// --- 2. Password Prompt Test ---
|
|
29
|
+
const apiKey = await MepCLI.password({
|
|
30
|
+
message: "Enter the project's external API key:",
|
|
31
|
+
placeholder: "Input will be hidden..."
|
|
32
|
+
});
|
|
33
|
+
// Note: Do not log the actual key in a real app.
|
|
34
|
+
console.log(`\n✅ Password Result: API key entered (length: ${apiKey.length})`);
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
// --- 3. Select Prompt Test (Single Choice) ---
|
|
38
|
+
const theme = await MepCLI.select({
|
|
39
|
+
message: "Choose your preferred editor color theme:",
|
|
40
|
+
choices: [
|
|
41
|
+
{ title: "Dark Mode (Default)", value: "dark" },
|
|
42
|
+
{ title: "Light Mode (Classic)", value: "light" },
|
|
43
|
+
{ title: "High Contrast (Accessibility)", value: "contrast" },
|
|
44
|
+
{ title: "Monokai Pro", value: "monokai" },
|
|
45
|
+
]
|
|
46
|
+
});
|
|
47
|
+
console.log(`\n✅ Select Result: Chosen theme is: ${theme}`);
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
// --- 4. Checkbox Prompt Test (Multi-Choice with Min/Max) ---
|
|
51
|
+
const buildTools = await MepCLI.checkbox({
|
|
52
|
+
message: "Select your required bundlers/build tools (Min 1, Max 2):",
|
|
53
|
+
min: 1,
|
|
54
|
+
max: 2,
|
|
55
|
+
choices: [
|
|
56
|
+
{ title: "Webpack", value: "webpack" },
|
|
57
|
+
{ title: "Vite", value: "vite", selected: true },
|
|
58
|
+
{ title: "Rollup", value: "rollup" },
|
|
59
|
+
{ title: "esbuild", value: "esbuild" }
|
|
60
|
+
]
|
|
61
|
+
});
|
|
62
|
+
console.log(`\n✅ Checkbox Result: Selected build tools: [${buildTools.join(', ')}]`);
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
// --- 5. Confirm Prompt Test ---
|
|
66
|
+
const proceed = await MepCLI.confirm({
|
|
67
|
+
message: "Do you want to continue with the installation setup?",
|
|
68
|
+
initial: true
|
|
69
|
+
});
|
|
70
|
+
console.log(`\n✅ Confirm Result: Setup decision: ${proceed ? 'Proceed' : 'Cancel'}`);
|
|
71
|
+
|
|
72
|
+
console.log("\n--- All MepCLI tests completed successfully! ---");
|
|
73
|
+
|
|
74
|
+
} catch (e) {
|
|
75
|
+
if (e instanceof Error && e.message === 'User force closed') {
|
|
76
|
+
console.log("\nOperation cancelled by user (Ctrl+C).");
|
|
77
|
+
} else {
|
|
78
|
+
console.error("\nAn error occurred during prompt execution:", e);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
83
|
runAllTests();
|
package/package.json
CHANGED
|
@@ -1,37 +1,37 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "mepcli",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "Zero-dependency, minimalist interactive CLI prompt for Node.js",
|
|
5
|
-
"repository": {
|
|
6
|
-
"type": "git",
|
|
7
|
-
"url": "git+https://github.com/CodeTease/mep.git"
|
|
8
|
-
},
|
|
9
|
-
"main": "dist/index.js",
|
|
10
|
-
"types": "dist/index.d.ts",
|
|
11
|
-
"files": [
|
|
12
|
-
"dist",
|
|
13
|
-
"README.md",
|
|
14
|
-
"LICENSE",
|
|
15
|
-
"example.ts"
|
|
16
|
-
],
|
|
17
|
-
"scripts": {
|
|
18
|
-
"build": "tsc",
|
|
19
|
-
"prepublishOnly": "npm run build",
|
|
20
|
-
"test": "ts-node example.ts"
|
|
21
|
-
},
|
|
22
|
-
"keywords": [
|
|
23
|
-
"cli",
|
|
24
|
-
"prompt",
|
|
25
|
-
"inquirer",
|
|
26
|
-
"interactive",
|
|
27
|
-
"zero-dependency",
|
|
28
|
-
"codetease"
|
|
29
|
-
],
|
|
30
|
-
"author": "CodeTease",
|
|
31
|
-
"license": "MIT",
|
|
32
|
-
"devDependencies": {
|
|
33
|
-
"@types/node": "^20.
|
|
34
|
-
"ts-node": "^10.9.0",
|
|
35
|
-
"typescript": "^5.0.0"
|
|
36
|
-
}
|
|
37
|
-
}
|
|
1
|
+
{
|
|
2
|
+
"name": "mepcli",
|
|
3
|
+
"version": "0.2.0",
|
|
4
|
+
"description": "Zero-dependency, minimalist interactive CLI prompt for Node.js",
|
|
5
|
+
"repository": {
|
|
6
|
+
"type": "git",
|
|
7
|
+
"url": "git+https://github.com/CodeTease/mep.git"
|
|
8
|
+
},
|
|
9
|
+
"main": "dist/index.js",
|
|
10
|
+
"types": "dist/index.d.ts",
|
|
11
|
+
"files": [
|
|
12
|
+
"dist",
|
|
13
|
+
"README.md",
|
|
14
|
+
"LICENSE",
|
|
15
|
+
"example.ts"
|
|
16
|
+
],
|
|
17
|
+
"scripts": {
|
|
18
|
+
"build": "tsc",
|
|
19
|
+
"prepublishOnly": "npm run build",
|
|
20
|
+
"test": "ts-node example.ts"
|
|
21
|
+
},
|
|
22
|
+
"keywords": [
|
|
23
|
+
"cli",
|
|
24
|
+
"prompt",
|
|
25
|
+
"inquirer",
|
|
26
|
+
"interactive",
|
|
27
|
+
"zero-dependency",
|
|
28
|
+
"codetease"
|
|
29
|
+
],
|
|
30
|
+
"author": "CodeTease",
|
|
31
|
+
"license": "MIT",
|
|
32
|
+
"devDependencies": {
|
|
33
|
+
"@types/node": "^20.19.25",
|
|
34
|
+
"ts-node": "^10.9.0",
|
|
35
|
+
"typescript": "^5.0.0"
|
|
36
|
+
}
|
|
37
|
+
}
|