hedgequantx 2.9.59 → 2.9.60
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/package.json +1 -1
- package/src/utils/prompts.js +88 -321
package/package.json
CHANGED
package/src/utils/prompts.js
CHANGED
|
@@ -1,19 +1,15 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* @fileoverview Centralized prompts utility
|
|
2
|
+
* @fileoverview Centralized prompts utility
|
|
3
3
|
* @module utils/prompts
|
|
4
4
|
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
* prompts (password, list selection).
|
|
5
|
+
* Simple prompts using inquirer for user input.
|
|
6
|
+
* No spinners on prompts - spinners are only for data loading (use ora).
|
|
8
7
|
*/
|
|
9
8
|
|
|
10
9
|
const inquirer = require('inquirer');
|
|
11
10
|
const readline = require('readline');
|
|
12
11
|
const chalk = require('chalk');
|
|
13
12
|
|
|
14
|
-
// Spinner frames for yellow waiting indicator
|
|
15
|
-
const SPINNER_FRAMES = ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏'];
|
|
16
|
-
|
|
17
13
|
/** @type {readline.Interface|null} */
|
|
18
14
|
let rl = null;
|
|
19
15
|
|
|
@@ -48,305 +44,45 @@ const closeReadline = () => {
|
|
|
48
44
|
};
|
|
49
45
|
|
|
50
46
|
/**
|
|
51
|
-
*
|
|
52
|
-
*
|
|
53
|
-
* @param {string} message - Prompt message
|
|
54
|
-
* @returns {Promise<string>}
|
|
55
|
-
*/
|
|
56
|
-
const animatedPrompt = (message) => {
|
|
57
|
-
return new Promise((resolve) => {
|
|
58
|
-
prepareStdin();
|
|
59
|
-
closeReadline();
|
|
60
|
-
|
|
61
|
-
let frameIndex = 0;
|
|
62
|
-
let userInput = '';
|
|
63
|
-
let cursorPos = 0;
|
|
64
|
-
|
|
65
|
-
// Enable raw mode for character-by-character input
|
|
66
|
-
if (process.stdin.isTTY) {
|
|
67
|
-
process.stdin.setRawMode(true);
|
|
68
|
-
}
|
|
69
|
-
process.stdin.resume();
|
|
70
|
-
|
|
71
|
-
const render = () => {
|
|
72
|
-
const spinner = chalk.yellow(SPINNER_FRAMES[frameIndex]);
|
|
73
|
-
const line = `\r${spinner} ${message} ${userInput}`;
|
|
74
|
-
process.stdout.write('\r\x1b[K'); // Clear line
|
|
75
|
-
process.stdout.write(line);
|
|
76
|
-
};
|
|
77
|
-
|
|
78
|
-
// Animate spinner every 80ms
|
|
79
|
-
const spinnerInterval = setInterval(() => {
|
|
80
|
-
frameIndex = (frameIndex + 1) % SPINNER_FRAMES.length;
|
|
81
|
-
render();
|
|
82
|
-
}, 80);
|
|
83
|
-
|
|
84
|
-
render();
|
|
85
|
-
|
|
86
|
-
const onData = (key) => {
|
|
87
|
-
const char = key.toString();
|
|
88
|
-
|
|
89
|
-
// Enter key
|
|
90
|
-
if (char === '\r' || char === '\n') {
|
|
91
|
-
clearInterval(spinnerInterval);
|
|
92
|
-
process.stdin.removeListener('data', onData);
|
|
93
|
-
if (process.stdin.isTTY) {
|
|
94
|
-
process.stdin.setRawMode(false);
|
|
95
|
-
}
|
|
96
|
-
process.stdout.write('\n');
|
|
97
|
-
resolve(userInput);
|
|
98
|
-
return;
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
// Ctrl+C
|
|
102
|
-
if (char === '\x03') {
|
|
103
|
-
clearInterval(spinnerInterval);
|
|
104
|
-
process.stdin.removeListener('data', onData);
|
|
105
|
-
if (process.stdin.isTTY) {
|
|
106
|
-
process.stdin.setRawMode(false);
|
|
107
|
-
}
|
|
108
|
-
process.stdout.write('\n');
|
|
109
|
-
process.exit(0);
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
// Backspace
|
|
113
|
-
if (char === '\x7f' || char === '\b') {
|
|
114
|
-
if (userInput.length > 0) {
|
|
115
|
-
userInput = userInput.slice(0, -1);
|
|
116
|
-
render();
|
|
117
|
-
}
|
|
118
|
-
return;
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
// Regular printable character
|
|
122
|
-
if (char >= ' ' && char <= '~') {
|
|
123
|
-
userInput += char;
|
|
124
|
-
render();
|
|
125
|
-
}
|
|
126
|
-
};
|
|
127
|
-
|
|
128
|
-
process.stdin.on('data', onData);
|
|
129
|
-
});
|
|
130
|
-
};
|
|
131
|
-
|
|
132
|
-
/**
|
|
133
|
-
* Animated Y/N confirm prompt
|
|
134
|
-
* Shows [Y/n] or [y/N] based on default
|
|
135
|
-
* @param {string} message - Prompt message
|
|
136
|
-
* @param {boolean} defaultVal - Default value
|
|
137
|
-
* @returns {Promise<boolean>}
|
|
138
|
-
*/
|
|
139
|
-
const animatedConfirm = (message, defaultVal = true) => {
|
|
140
|
-
return new Promise((resolve) => {
|
|
141
|
-
prepareStdin();
|
|
142
|
-
closeReadline();
|
|
143
|
-
|
|
144
|
-
let frameIndex = 0;
|
|
145
|
-
const hint = defaultVal ? '[Y/n]' : '[y/N]';
|
|
146
|
-
|
|
147
|
-
if (process.stdin.isTTY) {
|
|
148
|
-
process.stdin.setRawMode(true);
|
|
149
|
-
}
|
|
150
|
-
process.stdin.resume();
|
|
151
|
-
|
|
152
|
-
const render = () => {
|
|
153
|
-
const spinner = chalk.yellow(SPINNER_FRAMES[frameIndex]);
|
|
154
|
-
process.stdout.write('\r\x1b[K');
|
|
155
|
-
process.stdout.write(`${spinner} ${message} ${chalk.dim(hint)} `);
|
|
156
|
-
};
|
|
157
|
-
|
|
158
|
-
const spinnerInterval = setInterval(() => {
|
|
159
|
-
frameIndex = (frameIndex + 1) % SPINNER_FRAMES.length;
|
|
160
|
-
render();
|
|
161
|
-
}, 80);
|
|
162
|
-
|
|
163
|
-
render();
|
|
164
|
-
|
|
165
|
-
const onData = (key) => {
|
|
166
|
-
const char = key.toString().toLowerCase();
|
|
167
|
-
|
|
168
|
-
// Enter = use default
|
|
169
|
-
if (char === '\r' || char === '\n') {
|
|
170
|
-
cleanup();
|
|
171
|
-
process.stdout.write(defaultVal ? 'Yes' : 'No');
|
|
172
|
-
process.stdout.write('\n');
|
|
173
|
-
resolve(defaultVal);
|
|
174
|
-
return;
|
|
175
|
-
}
|
|
176
|
-
|
|
177
|
-
// Y = yes
|
|
178
|
-
if (char === 'y') {
|
|
179
|
-
cleanup();
|
|
180
|
-
process.stdout.write('Yes');
|
|
181
|
-
process.stdout.write('\n');
|
|
182
|
-
resolve(true);
|
|
183
|
-
return;
|
|
184
|
-
}
|
|
185
|
-
|
|
186
|
-
// N = no
|
|
187
|
-
if (char === 'n') {
|
|
188
|
-
cleanup();
|
|
189
|
-
process.stdout.write('No');
|
|
190
|
-
process.stdout.write('\n');
|
|
191
|
-
resolve(false);
|
|
192
|
-
return;
|
|
193
|
-
}
|
|
194
|
-
|
|
195
|
-
// Ctrl+C
|
|
196
|
-
if (char === '\x03') {
|
|
197
|
-
cleanup();
|
|
198
|
-
process.stdout.write('\n');
|
|
199
|
-
process.exit(0);
|
|
200
|
-
}
|
|
201
|
-
};
|
|
202
|
-
|
|
203
|
-
const cleanup = () => {
|
|
204
|
-
clearInterval(spinnerInterval);
|
|
205
|
-
process.stdin.removeListener('data', onData);
|
|
206
|
-
if (process.stdin.isTTY) {
|
|
207
|
-
process.stdin.setRawMode(false);
|
|
208
|
-
}
|
|
209
|
-
};
|
|
210
|
-
|
|
211
|
-
process.stdin.on('data', onData);
|
|
212
|
-
});
|
|
213
|
-
};
|
|
214
|
-
|
|
215
|
-
/**
|
|
216
|
-
* Animated list selection with arrow keys
|
|
217
|
-
* @param {string} message - Prompt message
|
|
218
|
-
* @param {Array<{name: string, value: any}>} choices - Options
|
|
219
|
-
* @returns {Promise<any>}
|
|
220
|
-
*/
|
|
221
|
-
const animatedSelect = (message, choices) => {
|
|
222
|
-
return new Promise((resolve) => {
|
|
223
|
-
prepareStdin();
|
|
224
|
-
closeReadline();
|
|
225
|
-
|
|
226
|
-
let frameIndex = 0;
|
|
227
|
-
let selectedIndex = 0;
|
|
228
|
-
const validChoices = choices.filter(c => !c.disabled);
|
|
229
|
-
|
|
230
|
-
if (process.stdin.isTTY) {
|
|
231
|
-
process.stdin.setRawMode(true);
|
|
232
|
-
}
|
|
233
|
-
process.stdin.resume();
|
|
234
|
-
|
|
235
|
-
const render = () => {
|
|
236
|
-
const spinner = chalk.yellow(SPINNER_FRAMES[frameIndex]);
|
|
237
|
-
// Move cursor to start and clear down
|
|
238
|
-
process.stdout.write('\r\x1b[K');
|
|
239
|
-
process.stdout.write(`${spinner} ${message}\n`);
|
|
240
|
-
|
|
241
|
-
validChoices.forEach((choice, i) => {
|
|
242
|
-
process.stdout.write('\x1b[K'); // Clear line
|
|
243
|
-
if (i === selectedIndex) {
|
|
244
|
-
process.stdout.write(`${chalk.cyan('❯')} ${chalk.cyan(choice.name)}\n`);
|
|
245
|
-
} else {
|
|
246
|
-
process.stdout.write(` ${choice.name}\n`);
|
|
247
|
-
}
|
|
248
|
-
});
|
|
249
|
-
|
|
250
|
-
// Move cursor back up
|
|
251
|
-
process.stdout.write(`\x1b[${validChoices.length + 1}A`);
|
|
252
|
-
};
|
|
253
|
-
|
|
254
|
-
const spinnerInterval = setInterval(() => {
|
|
255
|
-
frameIndex = (frameIndex + 1) % SPINNER_FRAMES.length;
|
|
256
|
-
render();
|
|
257
|
-
}, 80);
|
|
258
|
-
|
|
259
|
-
render();
|
|
260
|
-
|
|
261
|
-
let escapeSeq = '';
|
|
262
|
-
|
|
263
|
-
const onData = (key) => {
|
|
264
|
-
const char = key.toString();
|
|
265
|
-
|
|
266
|
-
// Handle escape sequences (arrow keys)
|
|
267
|
-
if (char === '\x1b') {
|
|
268
|
-
escapeSeq = char;
|
|
269
|
-
return;
|
|
270
|
-
}
|
|
271
|
-
|
|
272
|
-
if (escapeSeq === '\x1b' && char === '[') {
|
|
273
|
-
escapeSeq += char;
|
|
274
|
-
return;
|
|
275
|
-
}
|
|
276
|
-
|
|
277
|
-
if (escapeSeq === '\x1b[') {
|
|
278
|
-
escapeSeq = '';
|
|
279
|
-
// Up arrow
|
|
280
|
-
if (char === 'A') {
|
|
281
|
-
selectedIndex = Math.max(0, selectedIndex - 1);
|
|
282
|
-
render();
|
|
283
|
-
return;
|
|
284
|
-
}
|
|
285
|
-
// Down arrow
|
|
286
|
-
if (char === 'B') {
|
|
287
|
-
selectedIndex = Math.min(validChoices.length - 1, selectedIndex + 1);
|
|
288
|
-
render();
|
|
289
|
-
return;
|
|
290
|
-
}
|
|
291
|
-
}
|
|
292
|
-
|
|
293
|
-
// Enter = select
|
|
294
|
-
if (char === '\r' || char === '\n') {
|
|
295
|
-
cleanup();
|
|
296
|
-
// Clear the menu lines
|
|
297
|
-
process.stdout.write('\r\x1b[K');
|
|
298
|
-
for (let i = 0; i < validChoices.length; i++) {
|
|
299
|
-
process.stdout.write('\x1b[B\x1b[K');
|
|
300
|
-
}
|
|
301
|
-
process.stdout.write(`\x1b[${validChoices.length}A`);
|
|
302
|
-
process.stdout.write(`${chalk.yellow('⠋')} ${message} ${chalk.cyan(validChoices[selectedIndex].name)}\n`);
|
|
303
|
-
resolve(validChoices[selectedIndex].value);
|
|
304
|
-
return;
|
|
305
|
-
}
|
|
306
|
-
|
|
307
|
-
// Ctrl+C
|
|
308
|
-
if (char === '\x03') {
|
|
309
|
-
cleanup();
|
|
310
|
-
process.stdout.write('\n');
|
|
311
|
-
process.exit(0);
|
|
312
|
-
}
|
|
313
|
-
};
|
|
314
|
-
|
|
315
|
-
const cleanup = () => {
|
|
316
|
-
clearInterval(spinnerInterval);
|
|
317
|
-
process.stdin.removeListener('data', onData);
|
|
318
|
-
if (process.stdin.isTTY) {
|
|
319
|
-
process.stdin.setRawMode(false);
|
|
320
|
-
}
|
|
321
|
-
};
|
|
322
|
-
|
|
323
|
-
process.stdin.on('data', onData);
|
|
324
|
-
});
|
|
325
|
-
};
|
|
326
|
-
|
|
327
|
-
/**
|
|
328
|
-
* Wait for Enter key with animated spinner
|
|
329
|
-
* @param {string} [message='Press Enter to continue...'] - Message to display
|
|
47
|
+
* Wait for Enter key
|
|
48
|
+
* @param {string} [message='Press Enter to continue...'] - Message
|
|
330
49
|
* @returns {Promise<void>}
|
|
331
50
|
*/
|
|
332
51
|
const waitForEnter = async (message = 'Press Enter to continue...') => {
|
|
333
|
-
|
|
52
|
+
closeReadline();
|
|
53
|
+
prepareStdin();
|
|
54
|
+
|
|
55
|
+
await inquirer.prompt([{
|
|
56
|
+
type: 'input',
|
|
57
|
+
name: 'continue',
|
|
58
|
+
message,
|
|
59
|
+
prefix: '',
|
|
60
|
+
}]);
|
|
334
61
|
};
|
|
335
62
|
|
|
336
63
|
/**
|
|
337
|
-
* Text input
|
|
64
|
+
* Text input
|
|
338
65
|
* @param {string} message - Prompt message
|
|
339
66
|
* @param {string} [defaultVal=''] - Default value
|
|
340
67
|
* @returns {Promise<string>}
|
|
341
68
|
*/
|
|
342
69
|
const textInput = async (message, defaultVal = '') => {
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
70
|
+
closeReadline();
|
|
71
|
+
prepareStdin();
|
|
72
|
+
|
|
73
|
+
const { value } = await inquirer.prompt([{
|
|
74
|
+
type: 'input',
|
|
75
|
+
name: 'value',
|
|
76
|
+
message,
|
|
77
|
+
default: defaultVal,
|
|
78
|
+
prefix: '',
|
|
79
|
+
}]);
|
|
80
|
+
|
|
81
|
+
return value;
|
|
346
82
|
};
|
|
347
83
|
|
|
348
84
|
/**
|
|
349
|
-
* Password input (masked)
|
|
85
|
+
* Password input (masked)
|
|
350
86
|
* @param {string} message - Prompt message
|
|
351
87
|
* @returns {Promise<string>}
|
|
352
88
|
*/
|
|
@@ -357,7 +93,7 @@ const passwordInput = async (message) => {
|
|
|
357
93
|
const { value } = await inquirer.prompt([{
|
|
358
94
|
type: 'password',
|
|
359
95
|
name: 'value',
|
|
360
|
-
message
|
|
96
|
+
message,
|
|
361
97
|
mask: '*',
|
|
362
98
|
prefix: '',
|
|
363
99
|
}]);
|
|
@@ -366,17 +102,33 @@ const passwordInput = async (message) => {
|
|
|
366
102
|
};
|
|
367
103
|
|
|
368
104
|
/**
|
|
369
|
-
* Confirm prompt with
|
|
105
|
+
* Confirm prompt with Yes/No selection
|
|
370
106
|
* @param {string} message - Prompt message
|
|
371
107
|
* @param {boolean} [defaultVal=true] - Default value
|
|
372
108
|
* @returns {Promise<boolean>}
|
|
373
109
|
*/
|
|
374
110
|
const confirmPrompt = async (message, defaultVal = true) => {
|
|
375
|
-
|
|
111
|
+
closeReadline();
|
|
112
|
+
prepareStdin();
|
|
113
|
+
|
|
114
|
+
const choices = defaultVal
|
|
115
|
+
? [{ name: 'Yes', value: true }, { name: 'No', value: false }]
|
|
116
|
+
: [{ name: 'No', value: false }, { name: 'Yes', value: true }];
|
|
117
|
+
|
|
118
|
+
const { value } = await inquirer.prompt([{
|
|
119
|
+
type: 'list',
|
|
120
|
+
name: 'value',
|
|
121
|
+
message,
|
|
122
|
+
choices,
|
|
123
|
+
prefix: '',
|
|
124
|
+
loop: false,
|
|
125
|
+
}]);
|
|
126
|
+
|
|
127
|
+
return value;
|
|
376
128
|
};
|
|
377
129
|
|
|
378
130
|
/**
|
|
379
|
-
* Number input with
|
|
131
|
+
* Number input with validation
|
|
380
132
|
* @param {string} message - Prompt message
|
|
381
133
|
* @param {number} [defaultVal=1] - Default value
|
|
382
134
|
* @param {number} [min=1] - Minimum value
|
|
@@ -384,40 +136,55 @@ const confirmPrompt = async (message, defaultVal = true) => {
|
|
|
384
136
|
* @returns {Promise<number>}
|
|
385
137
|
*/
|
|
386
138
|
const numberInput = async (message, defaultVal = 1, min = 1, max = 1000) => {
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
}
|
|
403
|
-
|
|
404
|
-
|
|
139
|
+
closeReadline();
|
|
140
|
+
prepareStdin();
|
|
141
|
+
|
|
142
|
+
const { value } = await inquirer.prompt([{
|
|
143
|
+
type: 'input',
|
|
144
|
+
name: 'value',
|
|
145
|
+
message: `${message} (${min}-${max})`,
|
|
146
|
+
default: String(defaultVal),
|
|
147
|
+
prefix: '',
|
|
148
|
+
validate: (v) => {
|
|
149
|
+
const n = parseInt(v, 10);
|
|
150
|
+
if (isNaN(n)) return 'Enter a number';
|
|
151
|
+
if (n < min) return `Min: ${min}`;
|
|
152
|
+
if (n > max) return `Max: ${max}`;
|
|
153
|
+
return true;
|
|
154
|
+
},
|
|
155
|
+
}]);
|
|
156
|
+
|
|
157
|
+
return parseInt(value, 10) || defaultVal;
|
|
405
158
|
};
|
|
406
159
|
|
|
407
160
|
/**
|
|
408
|
-
* Select from options with
|
|
161
|
+
* Select from options with arrow keys
|
|
409
162
|
* @param {string} message - Prompt message
|
|
410
163
|
* @param {Array<{label: string, value: any, disabled?: boolean}>} options - Options
|
|
411
164
|
* @returns {Promise<any>}
|
|
412
165
|
*/
|
|
413
166
|
const selectOption = async (message, options) => {
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
167
|
+
closeReadline();
|
|
168
|
+
prepareStdin();
|
|
169
|
+
|
|
170
|
+
const choices = options.map(opt => {
|
|
171
|
+
if (opt.disabled) {
|
|
172
|
+
return new inquirer.Separator(opt.label);
|
|
173
|
+
}
|
|
174
|
+
return { name: opt.label, value: opt.value };
|
|
175
|
+
});
|
|
419
176
|
|
|
420
|
-
|
|
177
|
+
const { value } = await inquirer.prompt([{
|
|
178
|
+
type: 'list',
|
|
179
|
+
name: 'value',
|
|
180
|
+
message,
|
|
181
|
+
choices,
|
|
182
|
+
prefix: '',
|
|
183
|
+
loop: false,
|
|
184
|
+
pageSize: 15,
|
|
185
|
+
}]);
|
|
186
|
+
|
|
187
|
+
return value;
|
|
421
188
|
};
|
|
422
189
|
|
|
423
190
|
module.exports = {
|