genomic 4.0.2 → 5.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +154 -1125
- package/cache/cache-manager.d.ts +60 -0
- package/cache/cache-manager.js +228 -0
- package/cache/types.d.ts +22 -0
- package/esm/cache/cache-manager.js +191 -0
- package/esm/git/git-cloner.js +92 -0
- package/esm/index.js +41 -4
- package/esm/licenses.js +120 -0
- package/esm/scaffolder/index.js +2 -0
- package/esm/scaffolder/template-scaffolder.js +310 -0
- package/esm/scaffolder/types.js +1 -0
- package/esm/template/extract.js +162 -0
- package/esm/template/prompt.js +103 -0
- package/esm/template/replace.js +110 -0
- package/esm/template/templatizer.js +73 -0
- package/esm/template/types.js +1 -0
- package/esm/types.js +1 -0
- package/esm/utils/npm-version-check.js +52 -0
- package/esm/utils/types.js +1 -0
- package/git/git-cloner.d.ts +32 -0
- package/git/git-cloner.js +129 -0
- package/git/types.d.ts +15 -0
- package/index.d.ts +29 -4
- package/index.js +43 -4
- package/licenses-templates/APACHE-2.0.txt +18 -0
- package/licenses-templates/BSD-3-CLAUSE.txt +28 -0
- package/licenses-templates/CLOSED.txt +20 -0
- package/licenses-templates/GPL-3.0.txt +18 -0
- package/licenses-templates/ISC.txt +16 -0
- package/licenses-templates/MIT.txt +22 -0
- package/licenses-templates/MPL-2.0.txt +8 -0
- package/licenses-templates/UNLICENSE.txt +22 -0
- package/licenses.d.ts +18 -0
- package/licenses.js +162 -0
- package/package.json +9 -14
- package/scaffolder/index.d.ts +2 -0
- package/{question → scaffolder}/index.js +1 -0
- package/scaffolder/template-scaffolder.d.ts +91 -0
- package/scaffolder/template-scaffolder.js +347 -0
- package/scaffolder/types.d.ts +191 -0
- package/scaffolder/types.js +2 -0
- package/template/extract.d.ts +7 -0
- package/template/extract.js +198 -0
- package/template/prompt.d.ts +19 -0
- package/template/prompt.js +107 -0
- package/template/replace.d.ts +9 -0
- package/template/replace.js +146 -0
- package/template/templatizer.d.ts +33 -0
- package/template/templatizer.js +110 -0
- package/template/types.d.ts +18 -0
- package/template/types.js +2 -0
- package/types.d.ts +99 -0
- package/types.js +2 -0
- package/utils/npm-version-check.d.ts +17 -0
- package/utils/npm-version-check.js +57 -0
- package/utils/types.d.ts +6 -0
- package/utils/types.js +2 -0
- package/commander.d.ts +0 -21
- package/commander.js +0 -57
- package/esm/commander.js +0 -50
- package/esm/keypress.js +0 -95
- package/esm/prompt.js +0 -1024
- package/esm/question/index.js +0 -1
- package/esm/resolvers/date.js +0 -11
- package/esm/resolvers/git.js +0 -26
- package/esm/resolvers/index.js +0 -103
- package/esm/resolvers/npm.js +0 -24
- package/esm/resolvers/workspace.js +0 -141
- package/esm/utils.js +0 -12
- package/keypress.d.ts +0 -45
- package/keypress.js +0 -99
- package/prompt.d.ts +0 -116
- package/prompt.js +0 -1032
- package/question/index.d.ts +0 -1
- package/question/types.d.ts +0 -65
- package/resolvers/date.d.ts +0 -5
- package/resolvers/date.js +0 -14
- package/resolvers/git.d.ts +0 -11
- package/resolvers/git.js +0 -30
- package/resolvers/index.d.ts +0 -63
- package/resolvers/index.js +0 -111
- package/resolvers/npm.d.ts +0 -10
- package/resolvers/npm.js +0 -28
- package/resolvers/types.d.ts +0 -12
- package/resolvers/workspace.d.ts +0 -6
- package/resolvers/workspace.js +0 -144
- package/utils.d.ts +0 -2
- package/utils.js +0 -16
- /package/{question → cache}/types.js +0 -0
- /package/esm/{question → cache}/types.js +0 -0
- /package/esm/{resolvers → git}/types.js +0 -0
- /package/{resolvers → git}/types.js +0 -0
package/prompt.js
DELETED
|
@@ -1,1032 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
-
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
-
};
|
|
5
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
-
exports.Prompter = void 0;
|
|
7
|
-
exports.reorderQuestionsByDeps = reorderQuestionsByDeps;
|
|
8
|
-
const yanse_1 = require("yanse");
|
|
9
|
-
const readline_1 = __importDefault(require("readline"));
|
|
10
|
-
const keypress_1 = require("./keypress");
|
|
11
|
-
const resolvers_1 = require("./resolvers");
|
|
12
|
-
function reorderQuestionsByDeps(questions) {
|
|
13
|
-
const nameToIndex = new Map();
|
|
14
|
-
questions.forEach((q, idx) => nameToIndex.set(q.name, idx));
|
|
15
|
-
const resolved = new Set();
|
|
16
|
-
const result = [];
|
|
17
|
-
function addQuestion(q) {
|
|
18
|
-
// If this question depends on others, ensure those are added first
|
|
19
|
-
if (q.dependsOn && q.dependsOn.length > 0) {
|
|
20
|
-
for (const dep of q.dependsOn) {
|
|
21
|
-
if (!resolved.has(dep)) {
|
|
22
|
-
const depIdx = nameToIndex.get(dep);
|
|
23
|
-
if (depIdx === undefined) {
|
|
24
|
-
throw new Error(`Unknown dependency: ${dep}`);
|
|
25
|
-
}
|
|
26
|
-
addQuestion(questions[depIdx]);
|
|
27
|
-
}
|
|
28
|
-
}
|
|
29
|
-
}
|
|
30
|
-
if (!resolved.has(q.name)) {
|
|
31
|
-
resolved.add(q.name);
|
|
32
|
-
result.push(q);
|
|
33
|
-
}
|
|
34
|
-
}
|
|
35
|
-
for (const q of questions) {
|
|
36
|
-
addQuestion(q);
|
|
37
|
-
}
|
|
38
|
-
return result;
|
|
39
|
-
}
|
|
40
|
-
const validationMessage = (question, ctx) => {
|
|
41
|
-
if (ctx.numTries === 0 || ctx.validation.success) {
|
|
42
|
-
return ''; // No message if first attempt or validation was successful
|
|
43
|
-
}
|
|
44
|
-
if (ctx.validation.reason) {
|
|
45
|
-
return (0, yanse_1.red)(`The field "${question.name}" is invalid: ${ctx.validation.reason}\n`);
|
|
46
|
-
}
|
|
47
|
-
switch (ctx.validation.type) {
|
|
48
|
-
case 'required':
|
|
49
|
-
return (0, yanse_1.red)(`The field "${question.name}" is required. Please provide a value.\n`);
|
|
50
|
-
case 'pattern':
|
|
51
|
-
return (0, yanse_1.red)(`The field "${question.name}" does not match the pattern: ${question.pattern}.\n`);
|
|
52
|
-
default:
|
|
53
|
-
return (0, yanse_1.red)(`The field "${question.name}" is invalid. Please try again.\n`);
|
|
54
|
-
}
|
|
55
|
-
};
|
|
56
|
-
class PromptContext {
|
|
57
|
-
numTries = 0;
|
|
58
|
-
needsInput = true;
|
|
59
|
-
validation = { success: false };
|
|
60
|
-
constructor() { }
|
|
61
|
-
tryAgain(validation) {
|
|
62
|
-
this.numTries++;
|
|
63
|
-
this.needsInput = true;
|
|
64
|
-
this.validation = { ...this.validation, ...validation, success: false };
|
|
65
|
-
}
|
|
66
|
-
nextQuestion() {
|
|
67
|
-
this.numTries = 0;
|
|
68
|
-
this.needsInput = false;
|
|
69
|
-
this.validation = { success: true };
|
|
70
|
-
}
|
|
71
|
-
process(validation) {
|
|
72
|
-
if (typeof validation === 'boolean') {
|
|
73
|
-
if (validation) {
|
|
74
|
-
this.nextQuestion();
|
|
75
|
-
}
|
|
76
|
-
else {
|
|
77
|
-
this.tryAgain({ type: 'validation' });
|
|
78
|
-
}
|
|
79
|
-
}
|
|
80
|
-
else {
|
|
81
|
-
if (validation.success) {
|
|
82
|
-
this.nextQuestion();
|
|
83
|
-
}
|
|
84
|
-
else {
|
|
85
|
-
this.tryAgain(validation);
|
|
86
|
-
}
|
|
87
|
-
}
|
|
88
|
-
return this.validation;
|
|
89
|
-
}
|
|
90
|
-
}
|
|
91
|
-
function generatePromptMessage(question, ctx) {
|
|
92
|
-
const { message, name, type, default: def, options = [], description } = question;
|
|
93
|
-
const lines = [];
|
|
94
|
-
// 1. Main prompt label with --name inline
|
|
95
|
-
let promptLine = yanse_1.whiteBright.bold(message || `${name}?`) + ' ' + (0, yanse_1.dim)(`(--${name})`);
|
|
96
|
-
// 2. Append default inline (only if present)
|
|
97
|
-
switch (type) {
|
|
98
|
-
case 'confirm':
|
|
99
|
-
promptLine += ' (y/n)';
|
|
100
|
-
if (def !== undefined) {
|
|
101
|
-
promptLine += ` ${(0, yanse_1.yellow)(`[${def ? 'y' : 'n'}]`)}`;
|
|
102
|
-
}
|
|
103
|
-
break;
|
|
104
|
-
case 'text':
|
|
105
|
-
case 'number':
|
|
106
|
-
if (def !== undefined) {
|
|
107
|
-
promptLine += ` ${(0, yanse_1.yellow)(`[${def}]`)}`;
|
|
108
|
-
}
|
|
109
|
-
break;
|
|
110
|
-
case 'autocomplete':
|
|
111
|
-
case 'list':
|
|
112
|
-
case 'checkbox':
|
|
113
|
-
if (def !== undefined) {
|
|
114
|
-
const defaults = Array.isArray(def) ? def : [def];
|
|
115
|
-
const rendered = defaults.map(d => (0, yanse_1.yellow)(d)).join((0, yanse_1.gray)(', '));
|
|
116
|
-
promptLine += ` ${(0, yanse_1.yellow)(`[${rendered}]`)}`;
|
|
117
|
-
}
|
|
118
|
-
break;
|
|
119
|
-
}
|
|
120
|
-
lines.push(promptLine);
|
|
121
|
-
// 3. Optional description below title
|
|
122
|
-
if (description) {
|
|
123
|
-
lines.push((0, yanse_1.dim)(description));
|
|
124
|
-
}
|
|
125
|
-
// 4. Validation message if applicable
|
|
126
|
-
const validation = validationMessage(question, ctx);
|
|
127
|
-
if (validation) {
|
|
128
|
-
lines.push(validation); // already styled red
|
|
129
|
-
}
|
|
130
|
-
return lines.join('\n') + '\n';
|
|
131
|
-
}
|
|
132
|
-
class Prompter {
|
|
133
|
-
rl;
|
|
134
|
-
keypress;
|
|
135
|
-
noTty;
|
|
136
|
-
output;
|
|
137
|
-
input;
|
|
138
|
-
useDefaults;
|
|
139
|
-
globalMaxLines;
|
|
140
|
-
mutateArgs;
|
|
141
|
-
resolverRegistry;
|
|
142
|
-
handledKeys = new Set();
|
|
143
|
-
constructor(options) {
|
|
144
|
-
const { noTty = false, input = process.stdin, output = process.stdout, useDefaults = false, globalMaxLines = 10, mutateArgs = true, resolverRegistry = resolvers_1.globalResolverRegistry } = options ?? {};
|
|
145
|
-
this.useDefaults = useDefaults;
|
|
146
|
-
this.noTty = noTty;
|
|
147
|
-
this.output = output;
|
|
148
|
-
this.mutateArgs = mutateArgs;
|
|
149
|
-
this.input = input;
|
|
150
|
-
this.globalMaxLines = globalMaxLines;
|
|
151
|
-
this.resolverRegistry = resolverRegistry;
|
|
152
|
-
if (!noTty) {
|
|
153
|
-
this.rl = readline_1.default.createInterface({
|
|
154
|
-
input,
|
|
155
|
-
output
|
|
156
|
-
});
|
|
157
|
-
this.keypress = new keypress_1.TerminalKeypress(noTty, input);
|
|
158
|
-
}
|
|
159
|
-
else {
|
|
160
|
-
this.rl = null;
|
|
161
|
-
this.keypress = null;
|
|
162
|
-
}
|
|
163
|
-
}
|
|
164
|
-
clearScreen() {
|
|
165
|
-
// same as console.clear()
|
|
166
|
-
this.output.write('\x1Bc'); // This is the escape sequence to clear the terminal screen.
|
|
167
|
-
}
|
|
168
|
-
write(message) {
|
|
169
|
-
this.output.write(message);
|
|
170
|
-
}
|
|
171
|
-
log(message) {
|
|
172
|
-
this.output.write(message + '\n');
|
|
173
|
-
}
|
|
174
|
-
getInput(input) {
|
|
175
|
-
return `${(0, yanse_1.white)('>')} ${input}`;
|
|
176
|
-
}
|
|
177
|
-
getPrompt(question, ctx, input) {
|
|
178
|
-
const promptMessage = generatePromptMessage(question, ctx);
|
|
179
|
-
return promptMessage + this.getInput(input);
|
|
180
|
-
}
|
|
181
|
-
displayPrompt(question, ctx, input) {
|
|
182
|
-
const prompt = this.getPrompt(question, ctx, input);
|
|
183
|
-
this.log(prompt);
|
|
184
|
-
}
|
|
185
|
-
generateManPage(opts) {
|
|
186
|
-
let manPage = `${(0, yanse_1.white)('NAME')}\n\t${(0, yanse_1.white)(opts.commandName)} ${opts.description ?? ''}\n\n`;
|
|
187
|
-
// Constructing the SYNOPSIS section with required and optional arguments
|
|
188
|
-
let requiredArgs = '';
|
|
189
|
-
let optionalArgs = '';
|
|
190
|
-
opts.questions.forEach(question => {
|
|
191
|
-
if (question.required) {
|
|
192
|
-
requiredArgs += ` ${(0, yanse_1.white)('--' + question.name)} <${(0, yanse_1.gray)(question.name)}>`;
|
|
193
|
-
}
|
|
194
|
-
else {
|
|
195
|
-
optionalArgs += ` [${(0, yanse_1.white)('--' + question.name)}${question.default ? `=${(0, yanse_1.gray)(String(question.default))}` : ''}]`;
|
|
196
|
-
}
|
|
197
|
-
});
|
|
198
|
-
manPage += `${(0, yanse_1.white)('SYNOPSIS')}\n\t${(0, yanse_1.white)(opts.commandName)}${(0, yanse_1.gray)(requiredArgs)}${(0, yanse_1.gray)(optionalArgs)}\n\n`;
|
|
199
|
-
manPage += `${(0, yanse_1.white)('DESCRIPTION')}\n\tUse this command to interact with the application. It supports the following options:\n\n`;
|
|
200
|
-
opts.questions.forEach(question => {
|
|
201
|
-
manPage += `${(0, yanse_1.white)(question.name.toUpperCase())}\n`;
|
|
202
|
-
manPage += `\t${(0, yanse_1.white)('Type:')} ${(0, yanse_1.gray)(question.type)}\n`;
|
|
203
|
-
if (question.message) {
|
|
204
|
-
manPage += `\t${(0, yanse_1.white)('Summary:')} ${(0, yanse_1.gray)(question.message)}\n`;
|
|
205
|
-
}
|
|
206
|
-
if (question.description) {
|
|
207
|
-
manPage += `\t${(0, yanse_1.white)('Description:')} ${(0, yanse_1.gray)(question.description)}\n`;
|
|
208
|
-
}
|
|
209
|
-
if ('options' in question) {
|
|
210
|
-
const optionsList = Array.isArray(question.options)
|
|
211
|
-
? question.options.map(opt => typeof opt === 'string' ? (0, yanse_1.gray)(opt) : `${(0, yanse_1.gray)(opt.name)} (${(0, yanse_1.gray)(opt.value)})`).join(', ')
|
|
212
|
-
: '';
|
|
213
|
-
manPage += `\t${(0, yanse_1.white)('Options:')} ${(0, yanse_1.gray)(optionsList)}\n`;
|
|
214
|
-
}
|
|
215
|
-
if (question.default !== undefined) {
|
|
216
|
-
manPage += `\t${(0, yanse_1.white)('Default:')} ${(0, yanse_1.gray)(JSON.stringify(question.default))}\n`;
|
|
217
|
-
}
|
|
218
|
-
if (question.required) {
|
|
219
|
-
manPage += `\t${(0, yanse_1.white)('Required:')} ${(0, yanse_1.gray)('Yes')}\n`;
|
|
220
|
-
}
|
|
221
|
-
else {
|
|
222
|
-
manPage += `\t${(0, yanse_1.white)('Required:')} ${(0, yanse_1.gray)('No')}\n`;
|
|
223
|
-
}
|
|
224
|
-
manPage += '\n';
|
|
225
|
-
});
|
|
226
|
-
manPage += `${(0, yanse_1.white)('EXAMPLES')}\n\tExample usage of \`${(0, yanse_1.white)(opts.commandName)}\`.\n\t$ ${(0, yanse_1.white)(opts.commandName)}${(0, yanse_1.gray)(requiredArgs)}${(0, yanse_1.gray)(optionalArgs)}\n\n`;
|
|
227
|
-
manPage += opts.author ? `${(0, yanse_1.white)('AUTHOR')}\n\t${(0, yanse_1.white)(opts.author)}\n` : '';
|
|
228
|
-
return manPage;
|
|
229
|
-
}
|
|
230
|
-
isValidatableAnswer(answer) {
|
|
231
|
-
return answer !== undefined;
|
|
232
|
-
}
|
|
233
|
-
validateAnswer(question, answer, obj, ctx) {
|
|
234
|
-
const validation = this.validateAnswerPattern(question, answer);
|
|
235
|
-
if (!validation.success) {
|
|
236
|
-
return ctx.process(validation);
|
|
237
|
-
}
|
|
238
|
-
if (question.validate) {
|
|
239
|
-
const customValidation = question.validate(answer, obj);
|
|
240
|
-
return ctx.process(customValidation);
|
|
241
|
-
}
|
|
242
|
-
return ctx.process({
|
|
243
|
-
success: true
|
|
244
|
-
});
|
|
245
|
-
}
|
|
246
|
-
isValid(question, obj, ctx) {
|
|
247
|
-
if (this.isValidatableAnswer(obj[question.name])) {
|
|
248
|
-
obj[question.name] = this.sanitizeAnswer(question, obj[question.name], obj);
|
|
249
|
-
const validationResult = this.validateAnswer(question, obj[question.name], obj, ctx);
|
|
250
|
-
if (!validationResult.success) {
|
|
251
|
-
return false;
|
|
252
|
-
}
|
|
253
|
-
}
|
|
254
|
-
if (question.required && this.isEmptyAnswer(obj[question.name])) {
|
|
255
|
-
ctx.tryAgain({ type: 'required' });
|
|
256
|
-
return false;
|
|
257
|
-
}
|
|
258
|
-
return true;
|
|
259
|
-
}
|
|
260
|
-
validateAnswerPattern(question, answer) {
|
|
261
|
-
if (question.pattern && typeof answer === 'string') {
|
|
262
|
-
const regex = new RegExp(question.pattern);
|
|
263
|
-
const success = regex.test(answer);
|
|
264
|
-
if (success) {
|
|
265
|
-
return {
|
|
266
|
-
success
|
|
267
|
-
};
|
|
268
|
-
}
|
|
269
|
-
else {
|
|
270
|
-
return {
|
|
271
|
-
type: 'pattern',
|
|
272
|
-
success: false,
|
|
273
|
-
reason: question.pattern
|
|
274
|
-
};
|
|
275
|
-
}
|
|
276
|
-
}
|
|
277
|
-
return {
|
|
278
|
-
success: true
|
|
279
|
-
};
|
|
280
|
-
}
|
|
281
|
-
isEmptyAnswer(answer) {
|
|
282
|
-
switch (true) {
|
|
283
|
-
case answer === undefined:
|
|
284
|
-
case answer === null:
|
|
285
|
-
case answer === '':
|
|
286
|
-
case Array.isArray(answer) && answer.length === 0:
|
|
287
|
-
return true;
|
|
288
|
-
}
|
|
289
|
-
return false;
|
|
290
|
-
}
|
|
291
|
-
sanitizeAnswer(question, answer, obj) {
|
|
292
|
-
if (question.sanitize) {
|
|
293
|
-
return question.sanitize(answer, obj);
|
|
294
|
-
}
|
|
295
|
-
return answer;
|
|
296
|
-
}
|
|
297
|
-
exit() {
|
|
298
|
-
this.clearScreen();
|
|
299
|
-
this.close();
|
|
300
|
-
}
|
|
301
|
-
async prompt(argv, questions, options) {
|
|
302
|
-
// use local mutateArgs if defined, otherwise global mutateArgs
|
|
303
|
-
const shouldMutate = options?.mutateArgs !== undefined ? options.mutateArgs : this.mutateArgs;
|
|
304
|
-
// Create a working copy of argv - deep clone the _ array to avoid shared reference
|
|
305
|
-
let obj = shouldMutate ? argv : { ...argv };
|
|
306
|
-
const argvAny = argv;
|
|
307
|
-
if (!shouldMutate && Array.isArray(argvAny._)) {
|
|
308
|
-
obj._ = [...argvAny._];
|
|
309
|
-
}
|
|
310
|
-
// Resolve dynamic defaults before processing questions
|
|
311
|
-
await this.resolveDynamicDefaults(questions);
|
|
312
|
-
// Resolve dynamic options before processing questions
|
|
313
|
-
await this.resolveOptionsFrom(questions);
|
|
314
|
-
// Resolve setFrom values - these bypass prompting entirely
|
|
315
|
-
await this.resolveSetValues(questions, obj);
|
|
316
|
-
// Extract positional arguments from argv._ and assign to questions with _: true
|
|
317
|
-
// This must happen before applyOverrides so positional values flow through override pipeline
|
|
318
|
-
// Returns the number of positional arguments consumed for stripping
|
|
319
|
-
const consumedCount = this.extractPositionalArgs(obj, questions);
|
|
320
|
-
// Strip consumed positionals from obj._ (the working copy)
|
|
321
|
-
if (consumedCount > 0 && Array.isArray(obj._)) {
|
|
322
|
-
obj._ = obj._.slice(consumedCount);
|
|
323
|
-
// If mutating, also update the original argv._
|
|
324
|
-
if (shouldMutate) {
|
|
325
|
-
argvAny._ = obj._;
|
|
326
|
-
}
|
|
327
|
-
}
|
|
328
|
-
// first loop through the question, and set any overrides in case other questions use objs for validation
|
|
329
|
-
this.applyOverrides(obj, obj, questions);
|
|
330
|
-
// Check for required arguments when no terminal is available (non-interactive mode)
|
|
331
|
-
if (this.noTty && this.hasMissingRequiredArgs(questions, argv)) {
|
|
332
|
-
// Apply default values for all questions
|
|
333
|
-
this.applyDefaultValues(questions, obj);
|
|
334
|
-
// Recheck for missing required arguments after applying defaults
|
|
335
|
-
// NOT so sure this would ever happen, but possible if developer did something wrong
|
|
336
|
-
if (!this.hasMissingRequiredArgs(questions, argv)) {
|
|
337
|
-
return obj; // Return the updated object if no required arguments are missing
|
|
338
|
-
}
|
|
339
|
-
// Handle error for missing required arguments
|
|
340
|
-
this.handleMissingArgsError(options);
|
|
341
|
-
throw new Error('Missing required arguments. Please provide all required parameters.');
|
|
342
|
-
}
|
|
343
|
-
const ordered = reorderQuestionsByDeps(questions);
|
|
344
|
-
for (let index = 0; index < ordered.length; index++) {
|
|
345
|
-
const question = ordered[index];
|
|
346
|
-
const ctx = new PromptContext();
|
|
347
|
-
// obj is already either argv itself, or a clone, but let's check if it has the property
|
|
348
|
-
if (question.name in obj) {
|
|
349
|
-
this.handleOverrides(argv, obj, question);
|
|
350
|
-
ctx.nextQuestion();
|
|
351
|
-
continue;
|
|
352
|
-
}
|
|
353
|
-
if (question.when && !question.when(obj)) {
|
|
354
|
-
ctx.nextQuestion();
|
|
355
|
-
continue;
|
|
356
|
-
}
|
|
357
|
-
// Apply default value if applicable
|
|
358
|
-
// this is if useDefault is set, rare! not typical defaults which happen AFTER
|
|
359
|
-
// this is mostly to avoid a prompt for "hidden" options
|
|
360
|
-
if ('default' in question && (this.useDefaults || question.useDefault)) {
|
|
361
|
-
obj[question.name] = question.default;
|
|
362
|
-
continue; // Skip to the next question since the default is applied
|
|
363
|
-
}
|
|
364
|
-
while (ctx.needsInput) {
|
|
365
|
-
obj[question.name] = await this.handleQuestionType(question, ctx);
|
|
366
|
-
if (!this.isValid(question, obj, ctx)) {
|
|
367
|
-
if (this.noTty) {
|
|
368
|
-
// If you're not valid and here with noTty, you're out!
|
|
369
|
-
this.clearScreen(); // clear before leaving, not calling exit() since it may be a bad pattern to continue, devs should try/catch
|
|
370
|
-
throw new Error('Missing required arguments. Please provide all required parameters.');
|
|
371
|
-
}
|
|
372
|
-
continue;
|
|
373
|
-
}
|
|
374
|
-
// If input passes validation and is not empty, or not required, move to the next question
|
|
375
|
-
ctx.nextQuestion();
|
|
376
|
-
}
|
|
377
|
-
}
|
|
378
|
-
return obj;
|
|
379
|
-
}
|
|
380
|
-
handleMissingArgsError(options) {
|
|
381
|
-
this.clearScreen();
|
|
382
|
-
if (options?.usageText) {
|
|
383
|
-
this.log(options.usageText);
|
|
384
|
-
}
|
|
385
|
-
else if (options?.manPageInfo) {
|
|
386
|
-
this.log(this.generateManPage(options.manPageInfo));
|
|
387
|
-
}
|
|
388
|
-
else {
|
|
389
|
-
this.log('Missing required arguments. Please provide all required parameters.');
|
|
390
|
-
}
|
|
391
|
-
}
|
|
392
|
-
hasMissingRequiredArgs(questions, argv) {
|
|
393
|
-
return questions.some(question => question.required && this.isEmptyAnswer(argv[question.name]));
|
|
394
|
-
}
|
|
395
|
-
/**
|
|
396
|
-
* Resolves the default value for a question using the resolver system.
|
|
397
|
-
* Priority: defaultFrom > default > undefined
|
|
398
|
-
*/
|
|
399
|
-
async resolveQuestionDefault(question) {
|
|
400
|
-
// Try to resolve from defaultFrom first
|
|
401
|
-
if ('defaultFrom' in question && question.defaultFrom) {
|
|
402
|
-
const resolved = await this.resolverRegistry.resolve(question.defaultFrom);
|
|
403
|
-
if (resolved !== undefined) {
|
|
404
|
-
return resolved;
|
|
405
|
-
}
|
|
406
|
-
}
|
|
407
|
-
// Fallback to static default
|
|
408
|
-
if ('default' in question) {
|
|
409
|
-
return question.default;
|
|
410
|
-
}
|
|
411
|
-
return undefined;
|
|
412
|
-
}
|
|
413
|
-
/**
|
|
414
|
-
* Resolves dynamic defaults for all questions that have defaultFrom specified.
|
|
415
|
-
* Updates the question.default property with the resolved value.
|
|
416
|
-
*/
|
|
417
|
-
async resolveDynamicDefaults(questions) {
|
|
418
|
-
for (const question of questions) {
|
|
419
|
-
if ('defaultFrom' in question && question.defaultFrom) {
|
|
420
|
-
const resolved = await this.resolveQuestionDefault(question);
|
|
421
|
-
if (resolved !== undefined) {
|
|
422
|
-
// Update question.default with resolved value
|
|
423
|
-
question.default = resolved;
|
|
424
|
-
}
|
|
425
|
-
}
|
|
426
|
-
}
|
|
427
|
-
}
|
|
428
|
-
/**
|
|
429
|
-
* Resolves setFrom values for all questions that have setFrom specified.
|
|
430
|
-
* Sets the value directly in obj, bypassing the prompt entirely.
|
|
431
|
-
*/
|
|
432
|
-
async resolveSetValues(questions, obj) {
|
|
433
|
-
for (const question of questions) {
|
|
434
|
-
if ('setFrom' in question && question.setFrom) {
|
|
435
|
-
// Only set if not already provided in args
|
|
436
|
-
if (!(question.name in obj)) {
|
|
437
|
-
const resolved = await this.resolverRegistry.resolve(question.setFrom);
|
|
438
|
-
if (resolved !== undefined) {
|
|
439
|
-
obj[question.name] = resolved;
|
|
440
|
-
}
|
|
441
|
-
}
|
|
442
|
-
}
|
|
443
|
-
}
|
|
444
|
-
}
|
|
445
|
-
/**
|
|
446
|
-
* Resolves optionsFrom values for all questions that have optionsFrom specified.
|
|
447
|
-
* Updates the question.options property with the resolved array.
|
|
448
|
-
*/
|
|
449
|
-
async resolveOptionsFrom(questions) {
|
|
450
|
-
for (const question of questions) {
|
|
451
|
-
if ('optionsFrom' in question && question.optionsFrom) {
|
|
452
|
-
const resolved = await this.resolverRegistry.resolve(question.optionsFrom);
|
|
453
|
-
if (resolved !== undefined && Array.isArray(resolved)) {
|
|
454
|
-
// Update question.options with resolved array
|
|
455
|
-
question.options = resolved;
|
|
456
|
-
}
|
|
457
|
-
}
|
|
458
|
-
}
|
|
459
|
-
}
|
|
460
|
-
/**
|
|
461
|
-
* Extracts positional arguments from obj._ and assigns them to questions marked with _: true.
|
|
462
|
-
*
|
|
463
|
-
* Rules:
|
|
464
|
-
* 1. Named arguments take precedence - if a question already has a value in obj, skip it
|
|
465
|
-
* 2. Positional questions consume from obj._ left-to-right in declaration order
|
|
466
|
-
* 3. Returns the count of consumed positionals so caller can strip them from obj._
|
|
467
|
-
* 4. Missing positional values leave questions unset (for prompting/validation)
|
|
468
|
-
*
|
|
469
|
-
* This effectively allows "naming positional parameters" - users can pass values
|
|
470
|
-
* without flags and they'll be assigned to the appropriate question names.
|
|
471
|
-
*
|
|
472
|
-
* @returns The number of positional arguments consumed
|
|
473
|
-
*/
|
|
474
|
-
extractPositionalArgs(obj, questions) {
|
|
475
|
-
// Get positional arguments array from obj (minimist convention)
|
|
476
|
-
const positionals = Array.isArray(obj._) ? obj._ : [];
|
|
477
|
-
if (positionals.length === 0) {
|
|
478
|
-
return 0;
|
|
479
|
-
}
|
|
480
|
-
// Track which positional index we're consuming from
|
|
481
|
-
let positionalIndex = 0;
|
|
482
|
-
// Process questions in declaration order to maintain predictable assignment
|
|
483
|
-
for (const question of questions) {
|
|
484
|
-
// Only process questions marked as positional
|
|
485
|
-
if (!question._) {
|
|
486
|
-
continue;
|
|
487
|
-
}
|
|
488
|
-
// Skip if this question already has a named argument value
|
|
489
|
-
// Named arguments always take precedence over positional
|
|
490
|
-
if (question.name in obj && question.name !== '_') {
|
|
491
|
-
continue;
|
|
492
|
-
}
|
|
493
|
-
// Skip if we've exhausted all positional arguments
|
|
494
|
-
if (positionalIndex >= positionals.length) {
|
|
495
|
-
break;
|
|
496
|
-
}
|
|
497
|
-
// Assign the next positional value to this question
|
|
498
|
-
const value = positionals[positionalIndex];
|
|
499
|
-
obj[question.name] = value;
|
|
500
|
-
positionalIndex++;
|
|
501
|
-
}
|
|
502
|
-
return positionalIndex;
|
|
503
|
-
}
|
|
504
|
-
applyDefaultValues(questions, obj) {
|
|
505
|
-
questions.forEach(question => {
|
|
506
|
-
if ('default' in question) {
|
|
507
|
-
obj[question.name] = question.default; // Set default value if specified
|
|
508
|
-
}
|
|
509
|
-
});
|
|
510
|
-
}
|
|
511
|
-
applyOverrides(argv, obj, questions) {
|
|
512
|
-
questions.forEach(question => {
|
|
513
|
-
if (question.name in argv) {
|
|
514
|
-
this.handleOverrides(argv, obj, question);
|
|
515
|
-
}
|
|
516
|
-
});
|
|
517
|
-
}
|
|
518
|
-
handleOverrides(argv, obj, question) {
|
|
519
|
-
if (!Object.prototype.hasOwnProperty.call(argv, question.name)) {
|
|
520
|
-
return;
|
|
521
|
-
}
|
|
522
|
-
if (this.handledKeys.has(question.name)) {
|
|
523
|
-
return; // Already handled, skip further processing
|
|
524
|
-
}
|
|
525
|
-
this.handledKeys.add(question.name);
|
|
526
|
-
switch (question.type) {
|
|
527
|
-
case 'text':
|
|
528
|
-
case 'number':
|
|
529
|
-
case 'confirm':
|
|
530
|
-
// do nothing, already set!
|
|
531
|
-
break;
|
|
532
|
-
case 'checkbox':
|
|
533
|
-
this.handleOverridesForCheckboxOptions(argv, obj, question);
|
|
534
|
-
break;
|
|
535
|
-
case 'autocomplete':
|
|
536
|
-
case 'list':
|
|
537
|
-
// get the value from options :)
|
|
538
|
-
this.handleOverridesWithOptions(argv, obj, question);
|
|
539
|
-
break;
|
|
540
|
-
default:
|
|
541
|
-
return;
|
|
542
|
-
}
|
|
543
|
-
}
|
|
544
|
-
handleOverridesForCheckboxOptions(argv, obj, question) {
|
|
545
|
-
const options = this.sanitizeOptions(question);
|
|
546
|
-
const input = argv[question.name];
|
|
547
|
-
// Normalize to array
|
|
548
|
-
const inputs = Array.isArray(input) ? input.map(String) : [String(input)];
|
|
549
|
-
// Set of matched values
|
|
550
|
-
const inputSet = new Set(inputs);
|
|
551
|
-
// Base list of processed options
|
|
552
|
-
const result = options.map(opt => ({
|
|
553
|
-
...opt,
|
|
554
|
-
selected: inputSet.has(opt.name) || inputSet.has(String(opt.value))
|
|
555
|
-
}));
|
|
556
|
-
// Add extras if allowed
|
|
557
|
-
if (question.allowCustomOptions) {
|
|
558
|
-
const knownValues = new Set(options.map(opt => String(opt.value)));
|
|
559
|
-
const unknowns = inputs.filter(val => !knownValues.has(val));
|
|
560
|
-
for (const val of unknowns) {
|
|
561
|
-
result.push({
|
|
562
|
-
name: val,
|
|
563
|
-
value: val,
|
|
564
|
-
selected: true
|
|
565
|
-
});
|
|
566
|
-
}
|
|
567
|
-
}
|
|
568
|
-
// Assign final result
|
|
569
|
-
obj[question.name] = question.returnFullResults
|
|
570
|
-
? result
|
|
571
|
-
: result.filter(opt => opt.selected);
|
|
572
|
-
}
|
|
573
|
-
handleOverridesWithOptions(argv, obj, question) {
|
|
574
|
-
const input = argv[question.name];
|
|
575
|
-
if (typeof input !== 'string')
|
|
576
|
-
return;
|
|
577
|
-
const options = this.sanitizeOptions(question);
|
|
578
|
-
const found = options.find(opt => opt.name === input || String(opt.value) === input);
|
|
579
|
-
if (found) {
|
|
580
|
-
obj[question.name] = found.value;
|
|
581
|
-
}
|
|
582
|
-
else if (question.allowCustomOptions) {
|
|
583
|
-
obj[question.name] = input; // Store as-is
|
|
584
|
-
}
|
|
585
|
-
}
|
|
586
|
-
async handleQuestionType(question, ctx) {
|
|
587
|
-
this.keypress?.clearHandlers();
|
|
588
|
-
switch (question.type) {
|
|
589
|
-
case 'confirm':
|
|
590
|
-
return this.confirm(question, ctx);
|
|
591
|
-
case 'checkbox':
|
|
592
|
-
return this.checkbox(question, ctx);
|
|
593
|
-
case 'list':
|
|
594
|
-
return this.list(question, ctx);
|
|
595
|
-
case 'autocomplete':
|
|
596
|
-
return this.autocomplete(question, ctx);
|
|
597
|
-
case 'number':
|
|
598
|
-
return this.number(question, ctx);
|
|
599
|
-
case 'text':
|
|
600
|
-
return this.text(question, ctx);
|
|
601
|
-
default:
|
|
602
|
-
return this.text(question, ctx);
|
|
603
|
-
}
|
|
604
|
-
}
|
|
605
|
-
async confirm(question, ctx) {
|
|
606
|
-
if (this.noTty || !this.rl)
|
|
607
|
-
return question.default ?? false; // Return default if non-interactive
|
|
608
|
-
return new Promise((resolve) => {
|
|
609
|
-
this.clearScreen();
|
|
610
|
-
this.rl.question(this.getPrompt(question, ctx, ''), (answer) => {
|
|
611
|
-
const userInput = answer.trim().toLowerCase();
|
|
612
|
-
if (userInput === '') {
|
|
613
|
-
resolve(question.default ?? false); // Use default value if input is empty
|
|
614
|
-
}
|
|
615
|
-
else if (['yes', 'y'].includes(userInput)) {
|
|
616
|
-
resolve(true);
|
|
617
|
-
}
|
|
618
|
-
else {
|
|
619
|
-
resolve(false);
|
|
620
|
-
}
|
|
621
|
-
});
|
|
622
|
-
});
|
|
623
|
-
}
|
|
624
|
-
async text(question, ctx) {
|
|
625
|
-
if (this.noTty || !this.rl) {
|
|
626
|
-
if ('default' in question) {
|
|
627
|
-
return question.default;
|
|
628
|
-
}
|
|
629
|
-
return;
|
|
630
|
-
}
|
|
631
|
-
let input = '';
|
|
632
|
-
return new Promise((resolve) => {
|
|
633
|
-
this.clearScreen();
|
|
634
|
-
this.rl.question(this.getPrompt(question, ctx, input), (answer) => {
|
|
635
|
-
input = answer;
|
|
636
|
-
if (input.trim() !== '') {
|
|
637
|
-
resolve(input); // Return input if not empty
|
|
638
|
-
}
|
|
639
|
-
else if ('default' in question) {
|
|
640
|
-
resolve(question.default); // Use default if input is empty
|
|
641
|
-
}
|
|
642
|
-
else {
|
|
643
|
-
resolve(null); // Return null if empty and not required
|
|
644
|
-
}
|
|
645
|
-
});
|
|
646
|
-
});
|
|
647
|
-
}
|
|
648
|
-
async number(question, ctx) {
|
|
649
|
-
if (this.noTty || !this.rl) {
|
|
650
|
-
if ('default' in question) {
|
|
651
|
-
return question.default;
|
|
652
|
-
}
|
|
653
|
-
return;
|
|
654
|
-
}
|
|
655
|
-
let input = '';
|
|
656
|
-
return new Promise((resolve) => {
|
|
657
|
-
this.clearScreen();
|
|
658
|
-
this.rl.question(this.getPrompt(question, ctx, input), (answer) => {
|
|
659
|
-
input = answer.trim();
|
|
660
|
-
if (input !== '') {
|
|
661
|
-
const num = Number(input);
|
|
662
|
-
if (!isNaN(num)) {
|
|
663
|
-
resolve(num);
|
|
664
|
-
}
|
|
665
|
-
else {
|
|
666
|
-
resolve(null); // Let validation handle bad input
|
|
667
|
-
}
|
|
668
|
-
}
|
|
669
|
-
else if ('default' in question) {
|
|
670
|
-
resolve(question.default); // Use default if input is empty
|
|
671
|
-
}
|
|
672
|
-
else {
|
|
673
|
-
resolve(null); // Empty and no default
|
|
674
|
-
}
|
|
675
|
-
});
|
|
676
|
-
});
|
|
677
|
-
}
|
|
678
|
-
async checkbox(question, ctx) {
|
|
679
|
-
if (this.noTty || !this.rl) {
|
|
680
|
-
const options = this.sanitizeOptions(question);
|
|
681
|
-
const defaults = Array.isArray(question.default)
|
|
682
|
-
? question.default
|
|
683
|
-
: [question.default];
|
|
684
|
-
// If returnFullResults is true, return all options with boolean selection
|
|
685
|
-
if (question.returnFullResults) {
|
|
686
|
-
return options.map(opt => ({
|
|
687
|
-
name: opt.name,
|
|
688
|
-
value: opt.value,
|
|
689
|
-
selected: defaults.includes(opt.name)
|
|
690
|
-
}));
|
|
691
|
-
}
|
|
692
|
-
// Otherwise, return only selected options
|
|
693
|
-
return options
|
|
694
|
-
.filter(opt => defaults.includes(opt.name) || defaults.includes(opt.value))
|
|
695
|
-
.map(opt => ({
|
|
696
|
-
name: opt.name,
|
|
697
|
-
value: opt.value,
|
|
698
|
-
selected: true
|
|
699
|
-
}));
|
|
700
|
-
}
|
|
701
|
-
if (!question.options.length) {
|
|
702
|
-
// no arguments don't make sense
|
|
703
|
-
throw new Error('checkbox requires options');
|
|
704
|
-
}
|
|
705
|
-
this.keypress.resume();
|
|
706
|
-
const options = this.sanitizeOptions(question);
|
|
707
|
-
let input = ''; // Search input
|
|
708
|
-
let filteredOptions = options;
|
|
709
|
-
let selectedIndex = 0;
|
|
710
|
-
let startIndex = 0; // Start index for visible options
|
|
711
|
-
const maxLines = this.getMaxLines(question, options.length); // Use provided max or total options
|
|
712
|
-
// const selections: boolean[] = new Array(options.length).fill(false);
|
|
713
|
-
const selections = options.map(opt => {
|
|
714
|
-
if (!question.default)
|
|
715
|
-
return false;
|
|
716
|
-
const defaults = Array.isArray(question.default)
|
|
717
|
-
? question.default
|
|
718
|
-
: [question.default];
|
|
719
|
-
return defaults.includes(opt.name);
|
|
720
|
-
});
|
|
721
|
-
const updateFilteredOptions = () => {
|
|
722
|
-
filteredOptions = this.filterOptions(options, input);
|
|
723
|
-
};
|
|
724
|
-
const display = () => {
|
|
725
|
-
this.clearScreen();
|
|
726
|
-
this.displayPrompt(question, ctx, input);
|
|
727
|
-
const endIndex = Math.min(startIndex + maxLines, filteredOptions.length);
|
|
728
|
-
for (let i = startIndex; i < endIndex; i++) {
|
|
729
|
-
const option = filteredOptions[i];
|
|
730
|
-
const isSelected = selectedIndex === i;
|
|
731
|
-
const marker = isSelected ? '>' : ' ';
|
|
732
|
-
const index = options.map(o => o.name).indexOf(option.name);
|
|
733
|
-
if (index >= 0) {
|
|
734
|
-
const isChecked = selections[index] ? '◉' : '○'; // Use the original index in options
|
|
735
|
-
const line = `${marker} ${isChecked} ${option.name}`;
|
|
736
|
-
this.log(isSelected ? (0, yanse_1.blue)(line) : line);
|
|
737
|
-
}
|
|
738
|
-
else {
|
|
739
|
-
this.log('No options'); // sometimes user searches and there are no options...
|
|
740
|
-
}
|
|
741
|
-
}
|
|
742
|
-
};
|
|
743
|
-
display();
|
|
744
|
-
// Handling BACKSPACE key
|
|
745
|
-
this.keypress.on(keypress_1.KEY_CODES.BACKSPACE, () => {
|
|
746
|
-
input = input.slice(0, -1);
|
|
747
|
-
updateFilteredOptions();
|
|
748
|
-
display();
|
|
749
|
-
});
|
|
750
|
-
// Register alphanumeric keypresses to accumulate input, excluding space
|
|
751
|
-
'abcdefghijklmnopqrstuvwxyz0123456789'.split('').forEach(char => {
|
|
752
|
-
this.keypress.on(char, () => {
|
|
753
|
-
input += char;
|
|
754
|
-
updateFilteredOptions();
|
|
755
|
-
display();
|
|
756
|
-
});
|
|
757
|
-
});
|
|
758
|
-
this.keypress.on(keypress_1.KEY_CODES.UP_ARROW, () => {
|
|
759
|
-
selectedIndex = selectedIndex > 0 ? selectedIndex - 1 : filteredOptions.length - 1;
|
|
760
|
-
if (selectedIndex < startIndex) {
|
|
761
|
-
startIndex = selectedIndex; // Scroll up
|
|
762
|
-
}
|
|
763
|
-
else if (selectedIndex === filteredOptions.length - 1) {
|
|
764
|
-
startIndex = Math.max(0, filteredOptions.length - maxLines); // Jump to the bottom of the list
|
|
765
|
-
}
|
|
766
|
-
display();
|
|
767
|
-
});
|
|
768
|
-
this.keypress.on(keypress_1.KEY_CODES.DOWN_ARROW, () => {
|
|
769
|
-
selectedIndex = (selectedIndex + 1) % filteredOptions.length;
|
|
770
|
-
if (selectedIndex >= startIndex + maxLines) {
|
|
771
|
-
startIndex = selectedIndex - maxLines + 1; // Scroll down
|
|
772
|
-
}
|
|
773
|
-
else if (selectedIndex === 0) {
|
|
774
|
-
startIndex = 0; // Jump to the top of the list
|
|
775
|
-
}
|
|
776
|
-
display();
|
|
777
|
-
});
|
|
778
|
-
this.keypress.on(keypress_1.KEY_CODES.SPACE, () => {
|
|
779
|
-
// Map filtered index back to the original index in options
|
|
780
|
-
selections[options.indexOf(filteredOptions[selectedIndex])] = !selections[options.indexOf(filteredOptions[selectedIndex])];
|
|
781
|
-
display();
|
|
782
|
-
});
|
|
783
|
-
return new Promise(resolve => {
|
|
784
|
-
this.keypress.on(keypress_1.KEY_CODES.ENTER, () => {
|
|
785
|
-
this.keypress.pause();
|
|
786
|
-
const result = [];
|
|
787
|
-
if (question.returnFullResults) {
|
|
788
|
-
// Return all options with their selected status
|
|
789
|
-
options.forEach((option, index) => {
|
|
790
|
-
result.push({
|
|
791
|
-
name: option.name,
|
|
792
|
-
value: option.value,
|
|
793
|
-
selected: selections[index]
|
|
794
|
-
});
|
|
795
|
-
});
|
|
796
|
-
}
|
|
797
|
-
else {
|
|
798
|
-
// Return only options that are selected
|
|
799
|
-
options.forEach((option, index) => {
|
|
800
|
-
if (selections[index]) {
|
|
801
|
-
result.push({
|
|
802
|
-
name: option.name,
|
|
803
|
-
value: option.value,
|
|
804
|
-
selected: selections[index]
|
|
805
|
-
});
|
|
806
|
-
}
|
|
807
|
-
});
|
|
808
|
-
}
|
|
809
|
-
resolve(result);
|
|
810
|
-
});
|
|
811
|
-
});
|
|
812
|
-
}
|
|
813
|
-
async autocomplete(question, ctx) {
|
|
814
|
-
if (this.noTty || !this.rl) {
|
|
815
|
-
if ('default' in question) {
|
|
816
|
-
return question.default;
|
|
817
|
-
}
|
|
818
|
-
return;
|
|
819
|
-
}
|
|
820
|
-
if (!question.options.length) {
|
|
821
|
-
throw new Error('autocomplete requires options');
|
|
822
|
-
}
|
|
823
|
-
this.keypress.resume();
|
|
824
|
-
const options = this.sanitizeOptions(question);
|
|
825
|
-
let input = '';
|
|
826
|
-
let filteredOptions = options;
|
|
827
|
-
let selectedIndex = 0;
|
|
828
|
-
let startIndex = 0; // Start index for visible options
|
|
829
|
-
const maxLines = this.getMaxLines(question, options.length); // Use provided max or total options
|
|
830
|
-
const display = () => {
|
|
831
|
-
this.clearScreen();
|
|
832
|
-
this.displayPrompt(question, ctx, input);
|
|
833
|
-
// Determine the range of options to display
|
|
834
|
-
const endIndex = Math.min(startIndex + maxLines, filteredOptions.length);
|
|
835
|
-
for (let i = startIndex; i < endIndex; i++) {
|
|
836
|
-
const option = filteredOptions[i];
|
|
837
|
-
if (!option) {
|
|
838
|
-
this.log('No options'); // sometimes user searches and there are no options...
|
|
839
|
-
}
|
|
840
|
-
else if (i === selectedIndex) {
|
|
841
|
-
this.log((0, yanse_1.blue)('> ' + option.name)); // Highlight the selected option with yanse
|
|
842
|
-
}
|
|
843
|
-
else {
|
|
844
|
-
this.log(' ' + option.name);
|
|
845
|
-
}
|
|
846
|
-
}
|
|
847
|
-
};
|
|
848
|
-
const updateFilteredOptions = () => {
|
|
849
|
-
filteredOptions = this.filterOptions(options, input);
|
|
850
|
-
// Adjust startIndex to keep the selectedIndex in the visible range
|
|
851
|
-
if (selectedIndex < startIndex) {
|
|
852
|
-
startIndex = selectedIndex;
|
|
853
|
-
}
|
|
854
|
-
else if (selectedIndex >= startIndex + maxLines) {
|
|
855
|
-
startIndex = selectedIndex - maxLines + 1;
|
|
856
|
-
}
|
|
857
|
-
if (selectedIndex >= filteredOptions.length) {
|
|
858
|
-
selectedIndex = Math.max(filteredOptions.length - 1, 0);
|
|
859
|
-
}
|
|
860
|
-
};
|
|
861
|
-
display();
|
|
862
|
-
// Handling BACKSPACE key
|
|
863
|
-
this.keypress.on(keypress_1.KEY_CODES.BACKSPACE, () => {
|
|
864
|
-
input = input.slice(0, -1);
|
|
865
|
-
updateFilteredOptions();
|
|
866
|
-
display();
|
|
867
|
-
});
|
|
868
|
-
// Register alphanumeric and space keypresses to accumulate input
|
|
869
|
-
'abcdefghijklmnopqrstuvwxyz0123456789 '.split('').forEach(char => {
|
|
870
|
-
this.keypress.on(char, () => {
|
|
871
|
-
input += char;
|
|
872
|
-
updateFilteredOptions();
|
|
873
|
-
display();
|
|
874
|
-
});
|
|
875
|
-
});
|
|
876
|
-
// Navigation
|
|
877
|
-
this.keypress.on(keypress_1.KEY_CODES.UP_ARROW, () => {
|
|
878
|
-
selectedIndex = selectedIndex - 1 >= 0 ? selectedIndex - 1 : filteredOptions.length - 1;
|
|
879
|
-
if (selectedIndex < startIndex) {
|
|
880
|
-
startIndex = selectedIndex; // Scroll up
|
|
881
|
-
}
|
|
882
|
-
else if (selectedIndex === filteredOptions.length - 1) {
|
|
883
|
-
startIndex = Math.max(0, filteredOptions.length - maxLines); // Jump to the bottom of the list
|
|
884
|
-
}
|
|
885
|
-
display();
|
|
886
|
-
});
|
|
887
|
-
this.keypress.on(keypress_1.KEY_CODES.DOWN_ARROW, () => {
|
|
888
|
-
selectedIndex = (selectedIndex + 1) % filteredOptions.length;
|
|
889
|
-
if (selectedIndex >= startIndex + maxLines) {
|
|
890
|
-
startIndex = selectedIndex - maxLines + 1; // Scroll down
|
|
891
|
-
}
|
|
892
|
-
else if (selectedIndex === 0) {
|
|
893
|
-
startIndex = 0; // Jump to the top of the list
|
|
894
|
-
}
|
|
895
|
-
display();
|
|
896
|
-
});
|
|
897
|
-
return new Promise(resolve => {
|
|
898
|
-
this.keypress.on(keypress_1.KEY_CODES.ENTER, () => {
|
|
899
|
-
this.keypress.pause();
|
|
900
|
-
resolve(filteredOptions[selectedIndex]?.value || input);
|
|
901
|
-
});
|
|
902
|
-
});
|
|
903
|
-
}
|
|
904
|
-
async list(question, ctx) {
|
|
905
|
-
if (this.noTty || !this.rl) {
|
|
906
|
-
if ('default' in question) {
|
|
907
|
-
return question.default;
|
|
908
|
-
}
|
|
909
|
-
return;
|
|
910
|
-
}
|
|
911
|
-
if (!question.options.length) {
|
|
912
|
-
throw new Error('list requires options');
|
|
913
|
-
}
|
|
914
|
-
this.keypress.resume();
|
|
915
|
-
const options = this.sanitizeOptions(question);
|
|
916
|
-
let input = '';
|
|
917
|
-
let selectedIndex = 0;
|
|
918
|
-
let startIndex = 0; // Start index for visible options
|
|
919
|
-
const maxLines = this.getMaxLines(question, options.length); // Use provided max or total options
|
|
920
|
-
const display = () => {
|
|
921
|
-
this.clearScreen();
|
|
922
|
-
this.displayPrompt(question, ctx, input);
|
|
923
|
-
// Determine the range of options to display
|
|
924
|
-
const endIndex = Math.min(startIndex + maxLines, options.length);
|
|
925
|
-
for (let i = startIndex; i < endIndex; i++) {
|
|
926
|
-
const option = options[i];
|
|
927
|
-
if (!option) {
|
|
928
|
-
this.log('No options'); // sometimes user searches and there are no options...
|
|
929
|
-
}
|
|
930
|
-
else if (i === selectedIndex) {
|
|
931
|
-
this.log((0, yanse_1.blue)('> ' + option.name)); // Highlight the selected option with yanse
|
|
932
|
-
}
|
|
933
|
-
else {
|
|
934
|
-
this.log(' ' + option.name);
|
|
935
|
-
}
|
|
936
|
-
}
|
|
937
|
-
};
|
|
938
|
-
display();
|
|
939
|
-
// Navigation
|
|
940
|
-
this.keypress.on(keypress_1.KEY_CODES.UP_ARROW, () => {
|
|
941
|
-
selectedIndex = selectedIndex - 1 >= 0 ? selectedIndex - 1 : options.length - 1;
|
|
942
|
-
if (selectedIndex < startIndex) {
|
|
943
|
-
startIndex = selectedIndex; // Scroll up
|
|
944
|
-
}
|
|
945
|
-
else if (selectedIndex === options.length - 1) {
|
|
946
|
-
startIndex = Math.max(0, options.length - maxLines); // Jump to the bottom of the list
|
|
947
|
-
}
|
|
948
|
-
display();
|
|
949
|
-
});
|
|
950
|
-
this.keypress.on(keypress_1.KEY_CODES.DOWN_ARROW, () => {
|
|
951
|
-
selectedIndex = (selectedIndex + 1) % options.length;
|
|
952
|
-
if (selectedIndex >= startIndex + maxLines) {
|
|
953
|
-
startIndex = selectedIndex - maxLines + 1; // Scroll down
|
|
954
|
-
}
|
|
955
|
-
else if (selectedIndex === 0) {
|
|
956
|
-
startIndex = 0; // Jump to the top of the list
|
|
957
|
-
}
|
|
958
|
-
display();
|
|
959
|
-
});
|
|
960
|
-
return new Promise(resolve => {
|
|
961
|
-
this.keypress.on(keypress_1.KEY_CODES.ENTER, () => {
|
|
962
|
-
this.keypress.pause();
|
|
963
|
-
resolve(options[selectedIndex]?.value || input);
|
|
964
|
-
});
|
|
965
|
-
});
|
|
966
|
-
}
|
|
967
|
-
getOptionValue(option) {
|
|
968
|
-
if (typeof option === 'string') {
|
|
969
|
-
return { name: option, value: option };
|
|
970
|
-
}
|
|
971
|
-
else if (typeof option === 'object' && option && 'name' in option) {
|
|
972
|
-
return { name: option.name, value: option.value };
|
|
973
|
-
}
|
|
974
|
-
else {
|
|
975
|
-
return undefined;
|
|
976
|
-
}
|
|
977
|
-
}
|
|
978
|
-
sanitizeOptions(question) {
|
|
979
|
-
const options = (question.options ?? []).map(option => this.getOptionValue(option));
|
|
980
|
-
return options.filter(Boolean);
|
|
981
|
-
}
|
|
982
|
-
filterOptions(options, input) {
|
|
983
|
-
input = input.toLowerCase(); // Normalize input for case-insensitive comparison
|
|
984
|
-
// Fuzzy matching: Check if all characters of the input can be found in the option name in order
|
|
985
|
-
const fuzzyMatch = (option, input) => {
|
|
986
|
-
if (!input || !input.trim().length)
|
|
987
|
-
return true;
|
|
988
|
-
const length = input.length;
|
|
989
|
-
let position = 0; // Position in the input string
|
|
990
|
-
// Iterate over each character in the option name
|
|
991
|
-
for (let i = 0; i < option.length; i++) {
|
|
992
|
-
if (option[i] === input[position]) {
|
|
993
|
-
position++; // Move to the next character in the input
|
|
994
|
-
if (position === length) { // Check if we've matched all characters
|
|
995
|
-
return true;
|
|
996
|
-
}
|
|
997
|
-
}
|
|
998
|
-
}
|
|
999
|
-
return false;
|
|
1000
|
-
};
|
|
1001
|
-
return options
|
|
1002
|
-
.filter(option => fuzzyMatch(option.name.toLowerCase(), input))
|
|
1003
|
-
.sort((a, b) => {
|
|
1004
|
-
if (a.name < b.name) {
|
|
1005
|
-
return -1;
|
|
1006
|
-
}
|
|
1007
|
-
if (a.name > b.name) {
|
|
1008
|
-
return 1;
|
|
1009
|
-
}
|
|
1010
|
-
return 0;
|
|
1011
|
-
});
|
|
1012
|
-
}
|
|
1013
|
-
getMaxLines(question, defaultLength) {
|
|
1014
|
-
if (question.maxDisplayLines) {
|
|
1015
|
-
return question.maxDisplayLines;
|
|
1016
|
-
}
|
|
1017
|
-
// if (!this.noTty && (this.output as any).isTTY) {
|
|
1018
|
-
// const rows = Math.round(((this.output as any).rows ?? 0) / 7);
|
|
1019
|
-
// return Math.max(rows, defaultLength);
|
|
1020
|
-
// }
|
|
1021
|
-
return Math.min(this.globalMaxLines, defaultLength);
|
|
1022
|
-
}
|
|
1023
|
-
// Method to cleanly close the readline interface
|
|
1024
|
-
// NOTE: use exit() to close!
|
|
1025
|
-
close() {
|
|
1026
|
-
if (this.rl) {
|
|
1027
|
-
this.rl.close();
|
|
1028
|
-
this.keypress.destroy();
|
|
1029
|
-
}
|
|
1030
|
-
}
|
|
1031
|
-
}
|
|
1032
|
-
exports.Prompter = Prompter;
|