bob-core 2.0.0-beta.21 → 2.0.0-beta.24
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/dist/index.js +1349 -0
- package/dist/index.umd.cjs +1396 -0
- package/dist/package-CnNxf727.js +47 -0
- package/package.json +6 -12
- package/dist/cjs/package-Bxb7QZU4.cjs +0 -1
- package/dist/cjs/package.json +0 -3
- package/dist/cjs/src/index.js +0 -17
- package/dist/esm/package-DZqOZEXL.js +0 -33
- package/dist/esm/src/Cli.d.ts +0 -33
- package/dist/esm/src/Command.d.ts +0 -58
- package/dist/esm/src/CommandIO.d.ts +0 -58
- package/dist/esm/src/CommandParser.d.ts +0 -104
- package/dist/esm/src/CommandRegistry.d.ts +0 -31
- package/dist/esm/src/CommandSignatureParser.d.ts +0 -40
- package/dist/esm/src/CommandWithSignature.d.ts +0 -28
- package/dist/esm/src/ExceptionHandler.d.ts +0 -7
- package/dist/esm/src/Logger.d.ts +0 -16
- package/dist/esm/src/StringSimilarity.d.ts +0 -26
- package/dist/esm/src/commands/HelpCommand.d.ts +0 -12
- package/dist/esm/src/contracts/CommandOption.d.ts +0 -6
- package/dist/esm/src/contracts/LoggerContract.d.ts +0 -13
- package/dist/esm/src/contracts/index.d.ts +0 -2
- package/dist/esm/src/errors/BadCommandOption.d.ts +0 -12
- package/dist/esm/src/errors/BadCommandParameter.d.ts +0 -12
- package/dist/esm/src/errors/BobError.d.ts +0 -4
- package/dist/esm/src/errors/CommandNotFoundError.d.ts +0 -7
- package/dist/esm/src/errors/InvalidOption.d.ts +0 -9
- package/dist/esm/src/errors/MissingRequiredArgumentValue.d.ts +0 -7
- package/dist/esm/src/errors/MissingRequiredOptionValue.d.ts +0 -7
- package/dist/esm/src/errors/index.d.ts +0 -7
- package/dist/esm/src/index.d.ts +0 -14
- package/dist/esm/src/index.js +0 -1030
- package/dist/esm/src/lib/optionHelpers.d.ts +0 -5
- package/dist/esm/src/lib/string.d.ts +0 -1
- package/dist/esm/src/lib/types.d.ts +0 -31
- package/dist/esm/src/lib/valueConverter.d.ts +0 -10
- package/dist/esm/src/options/HelpOption.d.ts +0 -11
- package/dist/esm/src/options/index.d.ts +0 -1
- /package/dist/{cjs/src/Cli.d.ts → Cli.d.ts} +0 -0
- /package/dist/{cjs/src/Command.d.ts → Command.d.ts} +0 -0
- /package/dist/{cjs/src/CommandIO.d.ts → CommandIO.d.ts} +0 -0
- /package/dist/{cjs/src/CommandParser.d.ts → CommandParser.d.ts} +0 -0
- /package/dist/{cjs/src/CommandRegistry.d.ts → CommandRegistry.d.ts} +0 -0
- /package/dist/{cjs/src/CommandSignatureParser.d.ts → CommandSignatureParser.d.ts} +0 -0
- /package/dist/{cjs/src/CommandWithSignature.d.ts → CommandWithSignature.d.ts} +0 -0
- /package/dist/{cjs/src/ExceptionHandler.d.ts → ExceptionHandler.d.ts} +0 -0
- /package/dist/{cjs/src/Logger.d.ts → Logger.d.ts} +0 -0
- /package/dist/{cjs/src/StringSimilarity.d.ts → StringSimilarity.d.ts} +0 -0
- /package/dist/{cjs/src/commands → commands}/HelpCommand.d.ts +0 -0
- /package/dist/{cjs/src/contracts → contracts}/CommandOption.d.ts +0 -0
- /package/dist/{cjs/src/contracts → contracts}/LoggerContract.d.ts +0 -0
- /package/dist/{cjs/src/contracts → contracts}/index.d.ts +0 -0
- /package/dist/{cjs/src/errors → errors}/BadCommandOption.d.ts +0 -0
- /package/dist/{cjs/src/errors → errors}/BadCommandParameter.d.ts +0 -0
- /package/dist/{cjs/src/errors → errors}/BobError.d.ts +0 -0
- /package/dist/{cjs/src/errors → errors}/CommandNotFoundError.d.ts +0 -0
- /package/dist/{cjs/src/errors → errors}/InvalidOption.d.ts +0 -0
- /package/dist/{cjs/src/errors → errors}/MissingRequiredArgumentValue.d.ts +0 -0
- /package/dist/{cjs/src/errors → errors}/MissingRequiredOptionValue.d.ts +0 -0
- /package/dist/{cjs/src/errors → errors}/index.d.ts +0 -0
- /package/dist/{cjs/src/index.d.ts → index.d.ts} +0 -0
- /package/dist/{cjs/src/lib → lib}/optionHelpers.d.ts +0 -0
- /package/dist/{cjs/src/lib → lib}/string.d.ts +0 -0
- /package/dist/{cjs/src/lib → lib}/types.d.ts +0 -0
- /package/dist/{cjs/src/lib → lib}/valueConverter.d.ts +0 -0
- /package/dist/{cjs/src/options → options}/HelpOption.d.ts +0 -0
- /package/dist/{cjs/src/options → options}/index.d.ts +0 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,1349 @@
|
|
|
1
|
+
import prompts from "prompts";
|
|
2
|
+
import chalk from "chalk";
|
|
3
|
+
import minimist from "minimist";
|
|
4
|
+
import fs from "node:fs";
|
|
5
|
+
import path from "node:path";
|
|
6
|
+
class CommandIO {
|
|
7
|
+
logger;
|
|
8
|
+
constructor(opts) {
|
|
9
|
+
this.logger = opts.logger;
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* Logger methods
|
|
13
|
+
*/
|
|
14
|
+
log(...args) {
|
|
15
|
+
this.logger.log(...args);
|
|
16
|
+
}
|
|
17
|
+
info(...args) {
|
|
18
|
+
this.logger.info(...args);
|
|
19
|
+
}
|
|
20
|
+
warn(...args) {
|
|
21
|
+
this.logger.warn(...args);
|
|
22
|
+
}
|
|
23
|
+
error(...args) {
|
|
24
|
+
this.logger.error(...args);
|
|
25
|
+
}
|
|
26
|
+
debug(...args) {
|
|
27
|
+
this.logger.debug(...args);
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Prompt utils
|
|
31
|
+
*/
|
|
32
|
+
async askForConfirmation(message = "Do you want to continue?", defaultValue) {
|
|
33
|
+
return (await prompts({
|
|
34
|
+
type: "confirm",
|
|
35
|
+
name: "value",
|
|
36
|
+
message,
|
|
37
|
+
initial: defaultValue ?? false
|
|
38
|
+
})).value;
|
|
39
|
+
}
|
|
40
|
+
async askForInput(message, defaultValue, opts) {
|
|
41
|
+
return (await prompts({
|
|
42
|
+
type: "text",
|
|
43
|
+
name: "value",
|
|
44
|
+
message,
|
|
45
|
+
initial: defaultValue,
|
|
46
|
+
...opts
|
|
47
|
+
}))?.value ?? null;
|
|
48
|
+
}
|
|
49
|
+
async askForDate(message, defaultValue, opts) {
|
|
50
|
+
return (await prompts({
|
|
51
|
+
type: "date",
|
|
52
|
+
name: "value",
|
|
53
|
+
message,
|
|
54
|
+
initial: defaultValue,
|
|
55
|
+
...opts
|
|
56
|
+
}))?.value ?? null;
|
|
57
|
+
}
|
|
58
|
+
async askForList(message, defaultValue, opts) {
|
|
59
|
+
return (await prompts({
|
|
60
|
+
type: "list",
|
|
61
|
+
name: "value",
|
|
62
|
+
message,
|
|
63
|
+
initial: defaultValue,
|
|
64
|
+
...opts
|
|
65
|
+
}))?.value ?? null;
|
|
66
|
+
}
|
|
67
|
+
async askForToggle(message, defaultValue, opts) {
|
|
68
|
+
return (await prompts({
|
|
69
|
+
type: "toggle",
|
|
70
|
+
name: "value",
|
|
71
|
+
message,
|
|
72
|
+
initial: defaultValue,
|
|
73
|
+
...opts
|
|
74
|
+
}))?.value ?? null;
|
|
75
|
+
}
|
|
76
|
+
async askForSelect(message, options, opts) {
|
|
77
|
+
if (options.length === 0) {
|
|
78
|
+
throw new Error("No options provided");
|
|
79
|
+
}
|
|
80
|
+
const choices = [];
|
|
81
|
+
for (const option of options) {
|
|
82
|
+
if (typeof option === "string") {
|
|
83
|
+
choices.push({ title: option, value: option });
|
|
84
|
+
} else {
|
|
85
|
+
choices.push(option);
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
const result = await prompts({
|
|
89
|
+
type: "select",
|
|
90
|
+
name: "value",
|
|
91
|
+
message,
|
|
92
|
+
choices,
|
|
93
|
+
...opts
|
|
94
|
+
});
|
|
95
|
+
return result?.value ?? null;
|
|
96
|
+
}
|
|
97
|
+
newLoader(text = "", chars = ["⠙", "⠘", "⠰", "⠴", "⠤", "⠦", "⠆", "⠃", "⠋", "⠉"], delay = 100) {
|
|
98
|
+
let loaderText = text;
|
|
99
|
+
let previousText = null;
|
|
100
|
+
let x = 0;
|
|
101
|
+
const interval = setInterval(function() {
|
|
102
|
+
if (previousText) {
|
|
103
|
+
process.stdout.write(new TextEncoder().encode("\r" + " ".repeat(previousText.length + 5) + "\r"));
|
|
104
|
+
previousText = null;
|
|
105
|
+
}
|
|
106
|
+
process.stdout.write(new TextEncoder().encode("\r" + chars[x++] + " " + loaderText));
|
|
107
|
+
x = x % chars.length;
|
|
108
|
+
}, delay);
|
|
109
|
+
const stop = () => {
|
|
110
|
+
clearInterval(interval);
|
|
111
|
+
process.stdout.write(new TextEncoder().encode("\r" + " ".repeat(loaderText.length + 5) + "\r"));
|
|
112
|
+
};
|
|
113
|
+
return {
|
|
114
|
+
[Symbol.dispose]: stop,
|
|
115
|
+
[Symbol.asyncDispose]: stop,
|
|
116
|
+
updateText: (newText) => {
|
|
117
|
+
previousText = loaderText;
|
|
118
|
+
loaderText = newText;
|
|
119
|
+
},
|
|
120
|
+
stop
|
|
121
|
+
};
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
class BobError extends Error {
|
|
125
|
+
}
|
|
126
|
+
function getOptionPrimitiveDefaultValue(type) {
|
|
127
|
+
if (type === "string") return null;
|
|
128
|
+
if (type === "number") return null;
|
|
129
|
+
if (type === "boolean") return false;
|
|
130
|
+
if (Array.isArray(type) && type.length === 1) {
|
|
131
|
+
if (type[0] === "string") return [];
|
|
132
|
+
if (type[0] === "number") return [];
|
|
133
|
+
}
|
|
134
|
+
throw new Error("Invalid option type: " + type);
|
|
135
|
+
}
|
|
136
|
+
function getOptionDefaultValue(option) {
|
|
137
|
+
if (typeof option === "string" || Array.isArray(option)) {
|
|
138
|
+
return getOptionPrimitiveDefaultValue(option);
|
|
139
|
+
}
|
|
140
|
+
if (option.default !== void 0) {
|
|
141
|
+
return option.default;
|
|
142
|
+
}
|
|
143
|
+
return getOptionPrimitiveDefaultValue(option.type);
|
|
144
|
+
}
|
|
145
|
+
function getOptionDetails(option) {
|
|
146
|
+
if (typeof option === "string" || Array.isArray(option)) {
|
|
147
|
+
return {
|
|
148
|
+
alias: [],
|
|
149
|
+
default: getOptionDefaultValue(option),
|
|
150
|
+
description: "",
|
|
151
|
+
required: false,
|
|
152
|
+
secret: false,
|
|
153
|
+
type: option,
|
|
154
|
+
variadic: false
|
|
155
|
+
};
|
|
156
|
+
}
|
|
157
|
+
return {
|
|
158
|
+
alias: option.alias ? Array.isArray(option.alias) ? option.alias : [option.alias] : [],
|
|
159
|
+
default: option.default ?? getOptionDefaultValue(option.type),
|
|
160
|
+
description: option.description ?? "",
|
|
161
|
+
required: option.required ?? false,
|
|
162
|
+
secret: option.secret ?? false,
|
|
163
|
+
type: option.type,
|
|
164
|
+
variadic: option.variadic ?? false
|
|
165
|
+
};
|
|
166
|
+
}
|
|
167
|
+
class InvalidOption extends BobError {
|
|
168
|
+
constructor(option, optionsSchema = {}) {
|
|
169
|
+
super(`Invalid option ${option} in not recognized`);
|
|
170
|
+
this.option = option;
|
|
171
|
+
this.optionsSchema = optionsSchema;
|
|
172
|
+
}
|
|
173
|
+
pretty(logger) {
|
|
174
|
+
const options = Object.entries(this.optionsSchema);
|
|
175
|
+
if (options.length > 0) {
|
|
176
|
+
logger.log(`
|
|
177
|
+
${chalk.yellow("Available options")}:`);
|
|
178
|
+
for (const [name, definition] of options) {
|
|
179
|
+
const details = getOptionDetails(definition);
|
|
180
|
+
const alias = details.alias ? typeof details.alias === "string" ? [details.alias] : details.alias : [];
|
|
181
|
+
const typeDisplay = Array.isArray(details.type) ? `[${details.type[0]}]` : details.type;
|
|
182
|
+
const nameWithAlias = `--${name}${alias.length > 0 ? alias.map((a) => `, -${a}`).join("") : ""}`;
|
|
183
|
+
const spaces = " ".repeat(30 - nameWithAlias.length);
|
|
184
|
+
logger.log(` ${chalk.green(nameWithAlias)} ${spaces} ${details.description || "\b"} ${chalk.white(`(${typeDisplay})`)}`);
|
|
185
|
+
}
|
|
186
|
+
logger.log("");
|
|
187
|
+
}
|
|
188
|
+
logger.log(`${chalk.white.bgRed(" ERROR ")} Option ${chalk.bold.yellow(this.option)} is not recognized.`);
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
class MissingRequiredArgumentValue extends BobError {
|
|
192
|
+
constructor(argument) {
|
|
193
|
+
super(`Argument "${argument}" is required.`);
|
|
194
|
+
this.argument = argument;
|
|
195
|
+
}
|
|
196
|
+
pretty(logger) {
|
|
197
|
+
logger.log(`${chalk.white.bgRed(" ERROR ")} Argument ${chalk.bold.yellow(this.argument)} is required.`);
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
class MissingRequiredOptionValue extends BobError {
|
|
201
|
+
constructor(option) {
|
|
202
|
+
super(`Argument "${option}" is required.`);
|
|
203
|
+
this.option = option;
|
|
204
|
+
}
|
|
205
|
+
pretty(logger) {
|
|
206
|
+
logger.log(`${chalk.white.bgRed(" ERROR ")} Option ${chalk.bold.yellow(this.option)} is required.`);
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
class BadCommandParameter extends BobError {
|
|
210
|
+
constructor(param) {
|
|
211
|
+
let message = `Argument "${param.param}" value is invalid.`;
|
|
212
|
+
if (param.reason) {
|
|
213
|
+
message += ` Reason: ${param.reason}`;
|
|
214
|
+
} else {
|
|
215
|
+
message += ` Value: "${param.value}"`;
|
|
216
|
+
}
|
|
217
|
+
super(message);
|
|
218
|
+
this.param = param;
|
|
219
|
+
}
|
|
220
|
+
pretty(logger) {
|
|
221
|
+
logger.log(` ${chalk.white.bgRed(" ERROR ")} Argument ${chalk.bold.yellow(this.param.param)} value is invalid. `);
|
|
222
|
+
if (this.param.value || this.param.reason) {
|
|
223
|
+
logger.log("");
|
|
224
|
+
}
|
|
225
|
+
if (this.param.value) {
|
|
226
|
+
logger.log(` ${chalk.blue("Value")}: ${this.param.value}`);
|
|
227
|
+
}
|
|
228
|
+
if (this.param.reason) {
|
|
229
|
+
logger.log(` ${chalk.yellow("Reason")}: ${this.param.reason}`);
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
class BadCommandOption extends BobError {
|
|
234
|
+
constructor(param) {
|
|
235
|
+
let message = `Option "${param.option}" value is invalid.`;
|
|
236
|
+
if (param.reason) {
|
|
237
|
+
message += ` Reason: ${param.reason}`;
|
|
238
|
+
} else {
|
|
239
|
+
message += ` Value: "${param.value}"`;
|
|
240
|
+
}
|
|
241
|
+
super(message);
|
|
242
|
+
this.param = param;
|
|
243
|
+
}
|
|
244
|
+
pretty(logger) {
|
|
245
|
+
logger.log(` ${chalk.white.bgRed(" ERROR ")} Option ${chalk.bold.yellow(this.param.option)} value is invalid. `);
|
|
246
|
+
if (this.param.value || this.param.reason) {
|
|
247
|
+
logger.log("");
|
|
248
|
+
}
|
|
249
|
+
if (this.param.value) {
|
|
250
|
+
logger.log(` ${chalk.blue("Value")}: ${this.param.value}`);
|
|
251
|
+
}
|
|
252
|
+
if (this.param.reason) {
|
|
253
|
+
logger.log(` ${chalk.yellow("Reason")}: ${this.param.reason}`);
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
class CommandNotFoundError extends BobError {
|
|
258
|
+
constructor(command) {
|
|
259
|
+
super(`Command "${command}" not found.`);
|
|
260
|
+
this.command = command;
|
|
261
|
+
}
|
|
262
|
+
pretty(logger) {
|
|
263
|
+
logger.log(`${chalk.bgRed(" ERROR ")} Command ${chalk.yellow(this.command)} not found.`);
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
function convertValue(value, type, name, defaultValue) {
|
|
267
|
+
if (value === null || value === void 0) {
|
|
268
|
+
return defaultValue ?? null;
|
|
269
|
+
}
|
|
270
|
+
if (type === "string") {
|
|
271
|
+
return String(value);
|
|
272
|
+
}
|
|
273
|
+
if (type === "number") {
|
|
274
|
+
const num = Number(value);
|
|
275
|
+
if (isNaN(num)) {
|
|
276
|
+
throw new BadCommandOption({
|
|
277
|
+
option: name,
|
|
278
|
+
reason: `Expected a number, got "${value}"`
|
|
279
|
+
});
|
|
280
|
+
}
|
|
281
|
+
return num;
|
|
282
|
+
}
|
|
283
|
+
if (type === "boolean") {
|
|
284
|
+
if (typeof value === "boolean") return value;
|
|
285
|
+
if (value === "true" || value === "1") return true;
|
|
286
|
+
if (value === "false" || value === "0") return false;
|
|
287
|
+
return Boolean(value);
|
|
288
|
+
}
|
|
289
|
+
if (Array.isArray(type)) {
|
|
290
|
+
const elementType = type[0];
|
|
291
|
+
const arrayValue = Array.isArray(value) ? value : [value];
|
|
292
|
+
if (elementType === "string") {
|
|
293
|
+
return arrayValue.map((v) => String(v));
|
|
294
|
+
}
|
|
295
|
+
if (elementType === "number") {
|
|
296
|
+
return arrayValue.map((v) => {
|
|
297
|
+
const num = Number(v);
|
|
298
|
+
if (isNaN(num)) {
|
|
299
|
+
throw new BadCommandOption({
|
|
300
|
+
option: name,
|
|
301
|
+
reason: `Expected array of numbers, got "${v}" in array`
|
|
302
|
+
});
|
|
303
|
+
}
|
|
304
|
+
return num;
|
|
305
|
+
});
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
return value;
|
|
309
|
+
}
|
|
310
|
+
class CommandParser {
|
|
311
|
+
options;
|
|
312
|
+
parsedOptions = null;
|
|
313
|
+
arguments;
|
|
314
|
+
parsedArguments = null;
|
|
315
|
+
io;
|
|
316
|
+
shouldPromptForMissingOptions = true;
|
|
317
|
+
constructor(opts) {
|
|
318
|
+
this.options = opts.options;
|
|
319
|
+
this.arguments = opts.arguments;
|
|
320
|
+
this.io = opts.io;
|
|
321
|
+
}
|
|
322
|
+
// === PUBLIC METHODS ===
|
|
323
|
+
/**
|
|
324
|
+
* Parses raw command-line arguments into structured options and arguments
|
|
325
|
+
* @param args - Raw command line arguments (typically from process.argv.slice(2))
|
|
326
|
+
* @returns Object containing parsed options and arguments
|
|
327
|
+
* @throws {InvalidOption} If an naan option is provided
|
|
328
|
+
* @throws {BadCommandOption} If a value cannot be converted to the expected type
|
|
329
|
+
*/
|
|
330
|
+
init(args) {
|
|
331
|
+
const { _: positionalArgs, ...optionValues } = minimist(args);
|
|
332
|
+
this.validateUnknownOptions(optionValues);
|
|
333
|
+
this.parsedOptions = this.handleOptions(optionValues);
|
|
334
|
+
this.parsedArguments = this.handleArguments(positionalArgs);
|
|
335
|
+
return {
|
|
336
|
+
options: this.parsedOptions,
|
|
337
|
+
arguments: this.parsedArguments
|
|
338
|
+
};
|
|
339
|
+
}
|
|
340
|
+
/**
|
|
341
|
+
* Validates the parsed options and arguments
|
|
342
|
+
* @throws {Error} If validation fails
|
|
343
|
+
*/
|
|
344
|
+
async validate() {
|
|
345
|
+
for (const key in this.options) {
|
|
346
|
+
const optionDetails = getOptionDetails(this.options[key]);
|
|
347
|
+
if (optionDetails.required && (this.parsedOptions?.[key] === void 0 || this.parsedOptions?.[key] === null)) {
|
|
348
|
+
throw new MissingRequiredOptionValue(key);
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
for (const key in this.arguments) {
|
|
352
|
+
const argDetails = getOptionDetails(this.arguments[key]);
|
|
353
|
+
const value = this.parsedArguments?.[key];
|
|
354
|
+
if (argDetails.required && (value === void 0 || value === null)) {
|
|
355
|
+
if (this.shouldPromptForMissingOptions) {
|
|
356
|
+
const newValue = await this.promptForArgument(key, argDetails);
|
|
357
|
+
if (newValue && this.parsedArguments) {
|
|
358
|
+
this.parsedArguments[key] = convertValue(newValue, argDetails.type, key);
|
|
359
|
+
continue;
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
throw new MissingRequiredArgumentValue(key);
|
|
363
|
+
}
|
|
364
|
+
if (argDetails.required && argDetails.variadic && Array.isArray(value) && value.length === 0) {
|
|
365
|
+
if (this.shouldPromptForMissingOptions) {
|
|
366
|
+
const newValue = await this.promptForArgument(key, argDetails);
|
|
367
|
+
if (newValue && this.parsedArguments) {
|
|
368
|
+
this.parsedArguments[key] = convertValue(newValue, argDetails.type, key);
|
|
369
|
+
continue;
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
throw new MissingRequiredArgumentValue(key);
|
|
373
|
+
}
|
|
374
|
+
}
|
|
375
|
+
}
|
|
376
|
+
/**
|
|
377
|
+
* Retrieves a parsed option value by name
|
|
378
|
+
* @param name - The option name
|
|
379
|
+
* @param defaultValue - Optional default value if option is not set
|
|
380
|
+
* @returns The typed option value
|
|
381
|
+
* @throws {Error} If init() has not been called yet
|
|
382
|
+
*/
|
|
383
|
+
option(name, defaultValue) {
|
|
384
|
+
if (!this.parsedOptions) {
|
|
385
|
+
throw new Error("Options have not been parsed yet. Call init() first.");
|
|
386
|
+
}
|
|
387
|
+
if (this.isEmptyValue(this.parsedOptions[name]) && defaultValue !== void 0) {
|
|
388
|
+
return defaultValue;
|
|
389
|
+
}
|
|
390
|
+
return this.parsedOptions[name];
|
|
391
|
+
}
|
|
392
|
+
setOption(name, value) {
|
|
393
|
+
if (!this.parsedOptions) {
|
|
394
|
+
throw new Error("Options have not been parsed yet. Call init() first.");
|
|
395
|
+
}
|
|
396
|
+
if (!(name in this.options)) {
|
|
397
|
+
throw new InvalidOption(name, this.options);
|
|
398
|
+
}
|
|
399
|
+
this.parsedOptions[name] = value;
|
|
400
|
+
}
|
|
401
|
+
/**
|
|
402
|
+
* Retrieves a parsed argument value by name
|
|
403
|
+
* @param name - The argument name
|
|
404
|
+
* @param defaultValue - Optional default value if argument is not set
|
|
405
|
+
* @returns The typed argument value
|
|
406
|
+
* @throws {Error} If init() has not been called yet
|
|
407
|
+
*/
|
|
408
|
+
argument(name, defaultValue) {
|
|
409
|
+
if (!this.parsedArguments) {
|
|
410
|
+
throw new Error("Arguments have not been parsed yet. Call init() first.");
|
|
411
|
+
}
|
|
412
|
+
if (this.isEmptyValue(this.parsedArguments[name]) && defaultValue !== void 0) {
|
|
413
|
+
return defaultValue;
|
|
414
|
+
}
|
|
415
|
+
return this.parsedArguments[name];
|
|
416
|
+
}
|
|
417
|
+
setArgument(name, value) {
|
|
418
|
+
if (!this.parsedArguments) {
|
|
419
|
+
throw new Error("Arguments have not been parsed yet. Call init() first.");
|
|
420
|
+
}
|
|
421
|
+
if (!(name in this.arguments)) {
|
|
422
|
+
throw new InvalidOption(name, this.arguments);
|
|
423
|
+
}
|
|
424
|
+
this.parsedArguments[name] = value;
|
|
425
|
+
}
|
|
426
|
+
// === PRIVATE HELPERS ===
|
|
427
|
+
/**
|
|
428
|
+
* Checks if a value should be considered "empty" for default value purposes
|
|
429
|
+
* @param value - The value to check
|
|
430
|
+
* @returns true if the value is null, undefined, or an empty array
|
|
431
|
+
*/
|
|
432
|
+
isEmptyValue(value) {
|
|
433
|
+
return value === null || value === void 0 || Array.isArray(value) && value.length === 0;
|
|
434
|
+
}
|
|
435
|
+
/**
|
|
436
|
+
* Validates that all provided options are recognized
|
|
437
|
+
* @throws {InvalidOption} If an unknown option is found
|
|
438
|
+
*/
|
|
439
|
+
validateUnknownOptions(optionValues) {
|
|
440
|
+
const validOptionNames = /* @__PURE__ */ new Set();
|
|
441
|
+
for (const key in this.options) {
|
|
442
|
+
validOptionNames.add(key);
|
|
443
|
+
const optionDetails = getOptionDetails(this.options[key]);
|
|
444
|
+
for (const alias of optionDetails.alias) {
|
|
445
|
+
validOptionNames.add(alias);
|
|
446
|
+
}
|
|
447
|
+
}
|
|
448
|
+
for (const optionName in optionValues) {
|
|
449
|
+
if (!validOptionNames.has(optionName)) {
|
|
450
|
+
throw new InvalidOption(optionName, this.options);
|
|
451
|
+
}
|
|
452
|
+
}
|
|
453
|
+
}
|
|
454
|
+
/**
|
|
455
|
+
* Processes named options from minimist output
|
|
456
|
+
*/
|
|
457
|
+
handleOptions(optionValues) {
|
|
458
|
+
const parsedOptions = {};
|
|
459
|
+
for (const key in this.options) {
|
|
460
|
+
const optionDetails = getOptionDetails(this.options[key]);
|
|
461
|
+
parsedOptions[key] = this.resolveOptionValue(key, optionDetails, optionValues);
|
|
462
|
+
}
|
|
463
|
+
return parsedOptions;
|
|
464
|
+
}
|
|
465
|
+
/**
|
|
466
|
+
* Processes positional arguments from minimist output
|
|
467
|
+
*/
|
|
468
|
+
handleArguments(positionalArgs) {
|
|
469
|
+
const parsedArgs = {};
|
|
470
|
+
const remainingArgs = [...positionalArgs];
|
|
471
|
+
for (const key in this.arguments) {
|
|
472
|
+
const argDefinition = getOptionDetails(this.arguments[key]);
|
|
473
|
+
if (argDefinition.variadic) {
|
|
474
|
+
parsedArgs[key] = this.handleVariadicArgument(key, argDefinition, remainingArgs);
|
|
475
|
+
continue;
|
|
476
|
+
}
|
|
477
|
+
parsedArgs[key] = this.resolveArgumentValue(key, argDefinition, remainingArgs.shift());
|
|
478
|
+
}
|
|
479
|
+
return parsedArgs;
|
|
480
|
+
}
|
|
481
|
+
/**
|
|
482
|
+
* Handles variadic arguments that consume all remaining positional values
|
|
483
|
+
*/
|
|
484
|
+
handleVariadicArgument(key, definition, remainingArgs) {
|
|
485
|
+
return remainingArgs.length ? convertValue(remainingArgs, definition.type, key, definition.default) : definition.default;
|
|
486
|
+
}
|
|
487
|
+
/**
|
|
488
|
+
* Resolves a single positional argument value with defaults and type conversion
|
|
489
|
+
* Note: Does not validate required arguments - validation happens in subclass validate() methods
|
|
490
|
+
*/
|
|
491
|
+
resolveArgumentValue(key, definition, argValue) {
|
|
492
|
+
if (argValue === void 0) {
|
|
493
|
+
return definition.default;
|
|
494
|
+
}
|
|
495
|
+
return convertValue(argValue, definition.type, key, definition.default);
|
|
496
|
+
}
|
|
497
|
+
/**
|
|
498
|
+
* Resolves an option value from the parsed option values object
|
|
499
|
+
* Handles alias resolution, defaults, and type conversion
|
|
500
|
+
*/
|
|
501
|
+
resolveOptionValue(key, definition, optionValues) {
|
|
502
|
+
let rawValue = void 0;
|
|
503
|
+
const allNames = [key, ...definition.alias];
|
|
504
|
+
for (const name of allNames) {
|
|
505
|
+
if (name in optionValues) {
|
|
506
|
+
rawValue = optionValues[name];
|
|
507
|
+
break;
|
|
508
|
+
}
|
|
509
|
+
}
|
|
510
|
+
if (rawValue === void 0) {
|
|
511
|
+
if (definition.required) {
|
|
512
|
+
throw new BadCommandOption({
|
|
513
|
+
option: key,
|
|
514
|
+
reason: `Required option is missing`
|
|
515
|
+
});
|
|
516
|
+
}
|
|
517
|
+
return definition.default;
|
|
518
|
+
}
|
|
519
|
+
return convertValue(rawValue, definition.type, key, definition.default);
|
|
520
|
+
}
|
|
521
|
+
optionDefinitions() {
|
|
522
|
+
const defs = {};
|
|
523
|
+
for (const key in this.options) {
|
|
524
|
+
defs[key] = getOptionDetails(this.options[key]);
|
|
525
|
+
}
|
|
526
|
+
return defs;
|
|
527
|
+
}
|
|
528
|
+
argumentDefinitions() {
|
|
529
|
+
const defs = {};
|
|
530
|
+
for (const key in this.arguments) {
|
|
531
|
+
defs[key] = getOptionDetails(this.arguments[key]);
|
|
532
|
+
}
|
|
533
|
+
return defs;
|
|
534
|
+
}
|
|
535
|
+
availableOptions() {
|
|
536
|
+
return Object.keys(this.options);
|
|
537
|
+
}
|
|
538
|
+
availableArguments() {
|
|
539
|
+
return Object.keys(this.arguments);
|
|
540
|
+
}
|
|
541
|
+
/**
|
|
542
|
+
* Disables prompting for missing argument values
|
|
543
|
+
* Useful for non-interactive environments
|
|
544
|
+
*/
|
|
545
|
+
disablePrompting() {
|
|
546
|
+
this.shouldPromptForMissingOptions = false;
|
|
547
|
+
return this;
|
|
548
|
+
}
|
|
549
|
+
/**
|
|
550
|
+
* Prompts the user to provide a missing argument value via CommandIO
|
|
551
|
+
* Used by validate() when shouldPromptForMissingArgs is enabled
|
|
552
|
+
* @param argumentName - The name of the missing argument
|
|
553
|
+
* @param argDef - The argument's definition for type and description
|
|
554
|
+
* @returns The user-provided value, or null if none given
|
|
555
|
+
*/
|
|
556
|
+
async promptForArgument(argumentName, argDef) {
|
|
557
|
+
if (!Array.isArray(argDef.type) && !["string", "number", "secret"].includes(argDef.type)) {
|
|
558
|
+
return null;
|
|
559
|
+
}
|
|
560
|
+
let promptText = `${chalk.yellow.bold(argumentName)} is required`;
|
|
561
|
+
if (argDef.description) {
|
|
562
|
+
promptText += `: ${chalk.gray(`(${argDef.description})`)}`;
|
|
563
|
+
}
|
|
564
|
+
promptText += ` ${chalk.green(`(${argDef.type}${argDef.variadic == true ? "[]" : ""})`)}
|
|
565
|
+
`;
|
|
566
|
+
if (Array.isArray(argDef.type)) {
|
|
567
|
+
promptText += "Please provide one or more values, separated by commas:\n";
|
|
568
|
+
return await this.io.askForList(promptText, void 0, {
|
|
569
|
+
separator: ",",
|
|
570
|
+
validate: (value) => {
|
|
571
|
+
if (!value.length) {
|
|
572
|
+
return "Please enter at least one value";
|
|
573
|
+
}
|
|
574
|
+
if (argDef.type[0] === "number") {
|
|
575
|
+
for (const val of value.split(",")) {
|
|
576
|
+
if (isNaN(Number(val))) {
|
|
577
|
+
return `Please enter only valid numbers`;
|
|
578
|
+
}
|
|
579
|
+
}
|
|
580
|
+
}
|
|
581
|
+
return true;
|
|
582
|
+
}
|
|
583
|
+
});
|
|
584
|
+
}
|
|
585
|
+
return await this.io.askForInput(promptText, void 0, {
|
|
586
|
+
type: argDef.type === "number" ? "number" : argDef.secret ? "password" : "text",
|
|
587
|
+
validate: (value) => {
|
|
588
|
+
if (value === null || value === void 0 || typeof value === "string" && !value.length) {
|
|
589
|
+
return "This value is required";
|
|
590
|
+
}
|
|
591
|
+
if (argDef.type === "number") {
|
|
592
|
+
const num = Number(value);
|
|
593
|
+
if (isNaN(num)) {
|
|
594
|
+
return "Please enter a valid number";
|
|
595
|
+
}
|
|
596
|
+
} else if (argDef.type === "string") {
|
|
597
|
+
if (typeof value !== "string" || !value.length) {
|
|
598
|
+
return "Please enter a valid text";
|
|
599
|
+
}
|
|
600
|
+
}
|
|
601
|
+
return true;
|
|
602
|
+
}
|
|
603
|
+
});
|
|
604
|
+
}
|
|
605
|
+
}
|
|
606
|
+
function generateSpace(nb) {
|
|
607
|
+
return new Array(nb + 5).join(" ");
|
|
608
|
+
}
|
|
609
|
+
class HelpOption {
|
|
610
|
+
type = "boolean";
|
|
611
|
+
option = "help";
|
|
612
|
+
alias = ["h"];
|
|
613
|
+
default = false;
|
|
614
|
+
description = `Display help for the given command. When no command is given display help for the ${chalk.green("list")} command`;
|
|
615
|
+
async handler() {
|
|
616
|
+
const argumentDefinitions = this.parser.argumentDefinitions();
|
|
617
|
+
const optionDefinitions = this.parser.optionDefinitions();
|
|
618
|
+
const availableArguments = Object.entries(argumentDefinitions);
|
|
619
|
+
const availableOptions = Object.entries(optionDefinitions);
|
|
620
|
+
const optionsWithAlias = availableOptions.map(([name, signature]) => {
|
|
621
|
+
const aliases = Array.isArray(signature.alias) ? signature.alias : signature.alias ? [signature.alias] : [];
|
|
622
|
+
return {
|
|
623
|
+
name,
|
|
624
|
+
...signature,
|
|
625
|
+
optionWithAlias: `--${name}${aliases.map((a) => `, -${a}`).join("")}`
|
|
626
|
+
};
|
|
627
|
+
});
|
|
628
|
+
const requiredArguments = availableArguments.filter(([, signature]) => signature.required);
|
|
629
|
+
this.io.log(chalk.yellow("Description:"));
|
|
630
|
+
this.io.log(` ${this.description}
|
|
631
|
+
`);
|
|
632
|
+
this.io.log(chalk.yellow("Usage:"));
|
|
633
|
+
this.io.log(` ${this.command} ${requiredArguments.length > 0 ? requiredArguments.map(([name]) => `<${name}>`).join(" ") : "\b"} [options]`);
|
|
634
|
+
const maxOptionLength = Math.max(...optionsWithAlias.map((opt) => opt.optionWithAlias.length), 0);
|
|
635
|
+
const maxArgumentLength = Math.max(...availableArguments.map(([name]) => name.length), 0);
|
|
636
|
+
const maxLength = maxArgumentLength > maxOptionLength ? maxArgumentLength : maxOptionLength;
|
|
637
|
+
if (availableArguments.length > 0) {
|
|
638
|
+
this.io.log(`
|
|
639
|
+
${chalk.yellow("Arguments")}:`);
|
|
640
|
+
for (const [name, signature] of availableArguments) {
|
|
641
|
+
const spaces = generateSpace(maxLength - name.length);
|
|
642
|
+
let message = ` ${chalk.green(name)} ${spaces} ${signature.description ?? "\b"}`;
|
|
643
|
+
if (signature.default !== void 0 && !signature.required) {
|
|
644
|
+
const typeDisplay = Array.isArray(signature.type) ? `[${signature.type[0]}]` : signature.type;
|
|
645
|
+
const defaultValue = typeDisplay === "array" || Array.isArray(signature.type) ? JSON.stringify(signature.default) : signature.default;
|
|
646
|
+
message += ` ${chalk.yellow(`[default: ${defaultValue}]`)}`;
|
|
647
|
+
}
|
|
648
|
+
if (signature.variadic) {
|
|
649
|
+
message += ` ${chalk.white("(variadic)")}`;
|
|
650
|
+
}
|
|
651
|
+
this.io.log(message);
|
|
652
|
+
}
|
|
653
|
+
}
|
|
654
|
+
if (availableOptions.length > 0) {
|
|
655
|
+
this.io.log(`
|
|
656
|
+
${chalk.yellow("Options")}:`);
|
|
657
|
+
for (const signature of optionsWithAlias) {
|
|
658
|
+
const spaces = generateSpace(maxLength - signature.optionWithAlias.length);
|
|
659
|
+
let message = `${chalk.green(signature.optionWithAlias)} ${spaces} ${signature.description ?? "\b"}`;
|
|
660
|
+
if (signature.type) {
|
|
661
|
+
const typeDisplay = Array.isArray(signature.type) ? `[${signature.type[0]}]` : signature.type;
|
|
662
|
+
message += ` ${chalk.white(`(${typeDisplay})`)}`;
|
|
663
|
+
}
|
|
664
|
+
if (signature.default !== void 0 && !signature.required) {
|
|
665
|
+
const typeDisplay = Array.isArray(signature.type) ? `[${signature.type[0]}]` : signature.type;
|
|
666
|
+
const defaultValue = typeDisplay === "array" || Array.isArray(signature.type) ? JSON.stringify(signature.default) : signature.default;
|
|
667
|
+
message += ` ${chalk.yellow(`[default: ${defaultValue}]`)}`;
|
|
668
|
+
}
|
|
669
|
+
this.io.log(message);
|
|
670
|
+
}
|
|
671
|
+
}
|
|
672
|
+
if (this.commandsExamples.length > 0) {
|
|
673
|
+
this.io.log(`
|
|
674
|
+
${chalk.yellow("Examples")}:`);
|
|
675
|
+
let binaryName = process.argv[0].split("/").pop();
|
|
676
|
+
if (binaryName === "node") {
|
|
677
|
+
binaryName += " " + process.argv[1].split("/").pop();
|
|
678
|
+
}
|
|
679
|
+
for (const [index, example] of this.commandsExamples.entries()) {
|
|
680
|
+
if (index > 0) {
|
|
681
|
+
this.io.log("");
|
|
682
|
+
}
|
|
683
|
+
this.io.log(` ${example.description}
|
|
684
|
+
`);
|
|
685
|
+
this.io.log(` ${chalk.green(`${binaryName} ${example.command}`)}`);
|
|
686
|
+
}
|
|
687
|
+
}
|
|
688
|
+
return -1;
|
|
689
|
+
}
|
|
690
|
+
}
|
|
691
|
+
class Command {
|
|
692
|
+
_command;
|
|
693
|
+
description;
|
|
694
|
+
group;
|
|
695
|
+
commandsExamples = [];
|
|
696
|
+
get command() {
|
|
697
|
+
return this._command;
|
|
698
|
+
}
|
|
699
|
+
ctx;
|
|
700
|
+
io;
|
|
701
|
+
parser;
|
|
702
|
+
disablePromptingFlag = false;
|
|
703
|
+
_preHandler;
|
|
704
|
+
_handler;
|
|
705
|
+
tmp;
|
|
706
|
+
defaultOptions() {
|
|
707
|
+
return [new HelpOption()];
|
|
708
|
+
}
|
|
709
|
+
newCommandParser(opts) {
|
|
710
|
+
return new CommandParser({
|
|
711
|
+
io: opts.io,
|
|
712
|
+
options: opts.options,
|
|
713
|
+
arguments: opts.arguments
|
|
714
|
+
});
|
|
715
|
+
}
|
|
716
|
+
newCommandIO(opts) {
|
|
717
|
+
return new CommandIO(opts);
|
|
718
|
+
}
|
|
719
|
+
constructor(command, opts) {
|
|
720
|
+
this._command = command;
|
|
721
|
+
this.description = opts?.description ?? "";
|
|
722
|
+
this.group = opts?.group;
|
|
723
|
+
this.tmp = {
|
|
724
|
+
options: opts?.options ?? {},
|
|
725
|
+
arguments: opts?.arguments ?? {}
|
|
726
|
+
};
|
|
727
|
+
const defaultOptions = this.defaultOptions();
|
|
728
|
+
if (defaultOptions.length > 0) {
|
|
729
|
+
for (const option of defaultOptions) {
|
|
730
|
+
this.tmp.options[option.option] = option;
|
|
731
|
+
}
|
|
732
|
+
}
|
|
733
|
+
}
|
|
734
|
+
disablePrompting() {
|
|
735
|
+
this.disablePromptingFlag = true;
|
|
736
|
+
return this;
|
|
737
|
+
}
|
|
738
|
+
preHandler(handler) {
|
|
739
|
+
this._preHandler = handler;
|
|
740
|
+
return this;
|
|
741
|
+
}
|
|
742
|
+
handler(handler) {
|
|
743
|
+
this._handler = handler;
|
|
744
|
+
return this;
|
|
745
|
+
}
|
|
746
|
+
options(opts) {
|
|
747
|
+
this.tmp = {
|
|
748
|
+
options: {
|
|
749
|
+
...this.tmp?.options ?? {},
|
|
750
|
+
...opts
|
|
751
|
+
},
|
|
752
|
+
arguments: this.tmp?.arguments ?? {}
|
|
753
|
+
};
|
|
754
|
+
return this;
|
|
755
|
+
}
|
|
756
|
+
arguments(args) {
|
|
757
|
+
this.tmp = {
|
|
758
|
+
options: this.tmp?.options ?? {},
|
|
759
|
+
arguments: {
|
|
760
|
+
...this.tmp?.arguments ?? {},
|
|
761
|
+
...args
|
|
762
|
+
}
|
|
763
|
+
};
|
|
764
|
+
return this;
|
|
765
|
+
}
|
|
766
|
+
async run(opts) {
|
|
767
|
+
if (!this._handler && !this.handle) {
|
|
768
|
+
throw new Error(`No handler defined for command ${this.command || "(unknown)"}`);
|
|
769
|
+
}
|
|
770
|
+
let handlerOptions;
|
|
771
|
+
this.ctx = opts.ctx;
|
|
772
|
+
this.io = this.newCommandIO({
|
|
773
|
+
logger: opts.logger
|
|
774
|
+
});
|
|
775
|
+
if (opts && "args" in opts) {
|
|
776
|
+
const options = this.tmp?.options ?? {};
|
|
777
|
+
for (const option of this.defaultOptions()) {
|
|
778
|
+
if (!(option.option in options)) {
|
|
779
|
+
options[option.option] = option;
|
|
780
|
+
}
|
|
781
|
+
}
|
|
782
|
+
this.parser = this.newCommandParser({
|
|
783
|
+
io: this.io,
|
|
784
|
+
options,
|
|
785
|
+
arguments: this.tmp?.arguments ?? {}
|
|
786
|
+
});
|
|
787
|
+
handlerOptions = this.parser.init(opts.args);
|
|
788
|
+
for (const option of this.defaultOptions()) {
|
|
789
|
+
if (handlerOptions.options[option.option] === true) {
|
|
790
|
+
const code = await option.handler.call(this);
|
|
791
|
+
if (code && code !== 0) {
|
|
792
|
+
return code;
|
|
793
|
+
}
|
|
794
|
+
}
|
|
795
|
+
}
|
|
796
|
+
if (this.disablePromptingFlag) {
|
|
797
|
+
this.parser.disablePrompting();
|
|
798
|
+
}
|
|
799
|
+
await this.parser.validate();
|
|
800
|
+
} else {
|
|
801
|
+
handlerOptions = {
|
|
802
|
+
options: opts.options,
|
|
803
|
+
arguments: opts.arguments
|
|
804
|
+
};
|
|
805
|
+
}
|
|
806
|
+
if (!this._preHandler && this.preHandle) {
|
|
807
|
+
this._preHandler = this.preHandle.bind(this);
|
|
808
|
+
}
|
|
809
|
+
if (this._preHandler) {
|
|
810
|
+
const preHandlerResult = await this._preHandler(opts.ctx, handlerOptions);
|
|
811
|
+
if (preHandlerResult && preHandlerResult !== 0) {
|
|
812
|
+
return preHandlerResult;
|
|
813
|
+
}
|
|
814
|
+
}
|
|
815
|
+
if (!this._handler && this.handle) {
|
|
816
|
+
this._handler = this.handle.bind(this);
|
|
817
|
+
} else if (!this._handler) {
|
|
818
|
+
throw new Error(`No handler defined for command ${this.command || "(unknown)"}`);
|
|
819
|
+
}
|
|
820
|
+
const res = await this._handler(opts.ctx, handlerOptions);
|
|
821
|
+
return res ?? 0;
|
|
822
|
+
}
|
|
823
|
+
}
|
|
824
|
+
class CommandSignatureParser extends CommandParser {
|
|
825
|
+
command;
|
|
826
|
+
constructor(opts) {
|
|
827
|
+
const parseResult = CommandSignatureParser.parseSignature(opts.signature, opts.helperDefinitions, opts.defaultOptions);
|
|
828
|
+
super({
|
|
829
|
+
io: opts.io,
|
|
830
|
+
options: parseResult.options,
|
|
831
|
+
arguments: parseResult.arguments
|
|
832
|
+
});
|
|
833
|
+
this.command = parseResult.command;
|
|
834
|
+
}
|
|
835
|
+
/**
|
|
836
|
+
* Parses command signature string into command name and parameter schemas
|
|
837
|
+
* Example: "migrate {name} {--force}" -> { command: "migrate", arguments: {name: ...}, options: {force: ...} }
|
|
838
|
+
*/
|
|
839
|
+
static parseSignature(signature, helperDefinitions, defaultCommandOptions) {
|
|
840
|
+
const [command, ...signatureParams] = signature.split(/\{(.*?)\}/g).map((param) => param.trim()).filter(Boolean);
|
|
841
|
+
const optionsSchema = {};
|
|
842
|
+
const argumentsSchema = {};
|
|
843
|
+
for (const paramSignature of signatureParams) {
|
|
844
|
+
const { name, isOption, definition } = CommandSignatureParser.parseParamSignature(paramSignature, helperDefinitions);
|
|
845
|
+
if (isOption) {
|
|
846
|
+
optionsSchema[name] = definition;
|
|
847
|
+
} else {
|
|
848
|
+
argumentsSchema[name] = definition;
|
|
849
|
+
}
|
|
850
|
+
}
|
|
851
|
+
for (const option of defaultCommandOptions) {
|
|
852
|
+
optionsSchema[option.option] = {
|
|
853
|
+
type: option.type,
|
|
854
|
+
required: option.required,
|
|
855
|
+
alias: option.alias,
|
|
856
|
+
variadic: option.variadic ?? false,
|
|
857
|
+
description: option.description,
|
|
858
|
+
default: option.default ?? null
|
|
859
|
+
};
|
|
860
|
+
}
|
|
861
|
+
return {
|
|
862
|
+
command,
|
|
863
|
+
options: optionsSchema,
|
|
864
|
+
arguments: argumentsSchema
|
|
865
|
+
};
|
|
866
|
+
}
|
|
867
|
+
/**
|
|
868
|
+
* Parses a single parameter signature like "{name}" or "{--force}" or "{files*}"
|
|
869
|
+
* Extracts name, type, default value, aliases, description, etc.
|
|
870
|
+
*
|
|
871
|
+
* Signature syntax:
|
|
872
|
+
* - {arg} -> required string argument
|
|
873
|
+
* - {arg?} -> optional argument
|
|
874
|
+
* - {arg=default} -> argument with default value
|
|
875
|
+
* - {arg*} -> variadic argument (array)
|
|
876
|
+
* - {arg:desc} -> argument with description
|
|
877
|
+
* - {--opt} -> boolean option
|
|
878
|
+
* - {--opt=} -> string option
|
|
879
|
+
* - {--opt|o} -> option with alias
|
|
880
|
+
*/
|
|
881
|
+
static parseParamSignature(paramSignature, helperDefinitions) {
|
|
882
|
+
let name = paramSignature;
|
|
883
|
+
let isOption = false;
|
|
884
|
+
const definition = {
|
|
885
|
+
required: true,
|
|
886
|
+
type: "string",
|
|
887
|
+
description: void 0,
|
|
888
|
+
default: null,
|
|
889
|
+
variadic: false
|
|
890
|
+
};
|
|
891
|
+
if (name.includes(":")) {
|
|
892
|
+
const [paramName, description] = name.split(":");
|
|
893
|
+
name = paramName.trim();
|
|
894
|
+
definition.description = description.trim();
|
|
895
|
+
}
|
|
896
|
+
if (name.includes("=")) {
|
|
897
|
+
const [paramName, defaultValue] = name.split("=");
|
|
898
|
+
name = paramName.trim();
|
|
899
|
+
definition.default = defaultValue.trim();
|
|
900
|
+
definition.required = false;
|
|
901
|
+
if (typeof definition.default === "string" && !definition.default.length) {
|
|
902
|
+
definition.default = null;
|
|
903
|
+
} else if (definition.default === "true") {
|
|
904
|
+
definition.default = true;
|
|
905
|
+
definition.type = "boolean";
|
|
906
|
+
} else if (definition.default === "false") {
|
|
907
|
+
definition.default = false;
|
|
908
|
+
definition.type = "boolean";
|
|
909
|
+
}
|
|
910
|
+
} else if (name.startsWith("--")) {
|
|
911
|
+
definition.required = false;
|
|
912
|
+
definition.default = false;
|
|
913
|
+
definition.type = "boolean";
|
|
914
|
+
}
|
|
915
|
+
if (name.includes("|")) {
|
|
916
|
+
const [paramName, ...aliases] = name.split("|");
|
|
917
|
+
name = paramName.trim();
|
|
918
|
+
definition.alias = aliases.map((a) => a.trim());
|
|
919
|
+
}
|
|
920
|
+
if (name.startsWith("--")) {
|
|
921
|
+
isOption = true;
|
|
922
|
+
name = name.slice(2);
|
|
923
|
+
}
|
|
924
|
+
if (definition.default === "*") {
|
|
925
|
+
definition.default = [];
|
|
926
|
+
definition.type = ["string"];
|
|
927
|
+
}
|
|
928
|
+
if (name.endsWith("?")) {
|
|
929
|
+
definition.required = false;
|
|
930
|
+
name = name.slice(0, -1);
|
|
931
|
+
}
|
|
932
|
+
if (name.endsWith("*")) {
|
|
933
|
+
definition.type = ["string"];
|
|
934
|
+
definition.variadic = true;
|
|
935
|
+
definition.default = [];
|
|
936
|
+
name = name.slice(0, -1);
|
|
937
|
+
}
|
|
938
|
+
definition.description = definition.description ?? helperDefinitions[name] ?? helperDefinitions[`--${name}`];
|
|
939
|
+
return { name, isOption, definition };
|
|
940
|
+
}
|
|
941
|
+
}
|
|
942
|
+
class CommandWithSignature extends Command {
|
|
943
|
+
helperDefinitions = {};
|
|
944
|
+
get command() {
|
|
945
|
+
if (this.parser) {
|
|
946
|
+
return this.parser.command;
|
|
947
|
+
}
|
|
948
|
+
return this.signature.split(" ")[0];
|
|
949
|
+
}
|
|
950
|
+
newCommandParser(opts) {
|
|
951
|
+
return new CommandSignatureParser({
|
|
952
|
+
io: opts.io,
|
|
953
|
+
signature: this.signature,
|
|
954
|
+
helperDefinitions: this.helperDefinitions,
|
|
955
|
+
defaultOptions: this.defaultOptions()
|
|
956
|
+
});
|
|
957
|
+
}
|
|
958
|
+
constructor() {
|
|
959
|
+
super("");
|
|
960
|
+
}
|
|
961
|
+
option(key, defaultValue = null) {
|
|
962
|
+
return this.parser.option(key, defaultValue);
|
|
963
|
+
}
|
|
964
|
+
argument(key, defaultValue = null) {
|
|
965
|
+
return this.parser.argument(key, defaultValue);
|
|
966
|
+
}
|
|
967
|
+
// Prompt utils
|
|
968
|
+
async askForConfirmation(...opts) {
|
|
969
|
+
return this.io.askForConfirmation(...opts);
|
|
970
|
+
}
|
|
971
|
+
async askForInput(...opts) {
|
|
972
|
+
return this.io.askForInput(...opts);
|
|
973
|
+
}
|
|
974
|
+
async askForSelect(...opts) {
|
|
975
|
+
return this.io.askForSelect(...opts);
|
|
976
|
+
}
|
|
977
|
+
newLoader(...opts) {
|
|
978
|
+
return this.io.newLoader(...opts);
|
|
979
|
+
}
|
|
980
|
+
}
|
|
981
|
+
class Logger {
|
|
982
|
+
level;
|
|
983
|
+
constructor(opts = {}) {
|
|
984
|
+
this.level = opts.level ?? "info";
|
|
985
|
+
}
|
|
986
|
+
shouldLog(level) {
|
|
987
|
+
const levels = ["debug", "info", "warn", "error"];
|
|
988
|
+
const currentLevelIndex = levels.indexOf(this.level);
|
|
989
|
+
const messageLevelIndex = levels.indexOf(level);
|
|
990
|
+
return messageLevelIndex >= currentLevelIndex;
|
|
991
|
+
}
|
|
992
|
+
setLevel(level) {
|
|
993
|
+
this.level = level;
|
|
994
|
+
}
|
|
995
|
+
getLevel() {
|
|
996
|
+
return this.level;
|
|
997
|
+
}
|
|
998
|
+
log(...args) {
|
|
999
|
+
console.log(...args);
|
|
1000
|
+
}
|
|
1001
|
+
info(...args) {
|
|
1002
|
+
if (this.shouldLog("info")) {
|
|
1003
|
+
console.log(...args);
|
|
1004
|
+
}
|
|
1005
|
+
}
|
|
1006
|
+
warn(...args) {
|
|
1007
|
+
if (this.shouldLog("warn")) {
|
|
1008
|
+
console.warn(...args);
|
|
1009
|
+
}
|
|
1010
|
+
}
|
|
1011
|
+
error(...args) {
|
|
1012
|
+
if (this.shouldLog("error")) {
|
|
1013
|
+
console.error(...args);
|
|
1014
|
+
}
|
|
1015
|
+
}
|
|
1016
|
+
debug(...args) {
|
|
1017
|
+
if (this.shouldLog("debug")) {
|
|
1018
|
+
console.log(...args);
|
|
1019
|
+
}
|
|
1020
|
+
}
|
|
1021
|
+
}
|
|
1022
|
+
class StringSimilarity {
|
|
1023
|
+
/**
|
|
1024
|
+
* Generate bigrams (character pairs) from a string
|
|
1025
|
+
*/
|
|
1026
|
+
getBigrams(str) {
|
|
1027
|
+
const bigrams = [];
|
|
1028
|
+
const normalized = str.toLowerCase();
|
|
1029
|
+
for (let i = 0; i < normalized.length - 1; i++) {
|
|
1030
|
+
bigrams.push(normalized.slice(i, i + 2));
|
|
1031
|
+
}
|
|
1032
|
+
return bigrams;
|
|
1033
|
+
}
|
|
1034
|
+
/**
|
|
1035
|
+
* Calculate Dice's Coefficient similarity between two strings (0-1 scale)
|
|
1036
|
+
*/
|
|
1037
|
+
calculateSimilarity(str1, str2) {
|
|
1038
|
+
if (str1 === str2) return 1;
|
|
1039
|
+
if (str1.length < 2 || str2.length < 2) return 0;
|
|
1040
|
+
const bigrams1 = this.getBigrams(str1);
|
|
1041
|
+
const bigrams2 = this.getBigrams(str2);
|
|
1042
|
+
const bigrams2Set = new Set(bigrams2);
|
|
1043
|
+
let matches = 0;
|
|
1044
|
+
for (const bigram of bigrams1) {
|
|
1045
|
+
if (bigrams2Set.has(bigram)) {
|
|
1046
|
+
matches++;
|
|
1047
|
+
bigrams2Set.delete(bigram);
|
|
1048
|
+
}
|
|
1049
|
+
}
|
|
1050
|
+
return 2 * matches / (bigrams1.length + bigrams2.length);
|
|
1051
|
+
}
|
|
1052
|
+
/**
|
|
1053
|
+
* Find best matching string and ratings for all candidates
|
|
1054
|
+
*/
|
|
1055
|
+
findBestMatch(target, candidates) {
|
|
1056
|
+
const ratings = candidates.map((candidate) => ({
|
|
1057
|
+
target: candidate,
|
|
1058
|
+
rating: this.calculateSimilarity(target, candidate)
|
|
1059
|
+
}));
|
|
1060
|
+
let bestMatchIndex = 0;
|
|
1061
|
+
let bestRating = ratings[0]?.rating ?? 0;
|
|
1062
|
+
for (let i = 1; i < ratings.length; i++) {
|
|
1063
|
+
if (ratings[i].rating > bestRating) {
|
|
1064
|
+
bestRating = ratings[i].rating;
|
|
1065
|
+
bestMatchIndex = i;
|
|
1066
|
+
}
|
|
1067
|
+
}
|
|
1068
|
+
return {
|
|
1069
|
+
ratings,
|
|
1070
|
+
bestMatch: ratings[bestMatchIndex],
|
|
1071
|
+
bestMatchIndex
|
|
1072
|
+
};
|
|
1073
|
+
}
|
|
1074
|
+
}
|
|
1075
|
+
class CommandRegistry {
|
|
1076
|
+
commands = {};
|
|
1077
|
+
io;
|
|
1078
|
+
logger;
|
|
1079
|
+
stringSimilarity;
|
|
1080
|
+
newCommandIO(opts) {
|
|
1081
|
+
return new CommandIO(opts);
|
|
1082
|
+
}
|
|
1083
|
+
constructor(opts) {
|
|
1084
|
+
this.logger = opts?.logger ?? new Logger();
|
|
1085
|
+
this.io = this.newCommandIO({
|
|
1086
|
+
logger: this.logger
|
|
1087
|
+
});
|
|
1088
|
+
this.stringSimilarity = opts?.stringSimilarity ?? new StringSimilarity();
|
|
1089
|
+
}
|
|
1090
|
+
getAvailableCommands() {
|
|
1091
|
+
return Object.keys(this.commands);
|
|
1092
|
+
}
|
|
1093
|
+
getCommands() {
|
|
1094
|
+
return Object.values(this.commands);
|
|
1095
|
+
}
|
|
1096
|
+
importFile = async (filePath) => {
|
|
1097
|
+
return (await import(filePath)).default;
|
|
1098
|
+
};
|
|
1099
|
+
commandResolver = async (path2) => {
|
|
1100
|
+
let defaultImport = await this.importFile(path2);
|
|
1101
|
+
if (!defaultImport) {
|
|
1102
|
+
return null;
|
|
1103
|
+
}
|
|
1104
|
+
if (defaultImport && typeof defaultImport === "object" && "default" in defaultImport) {
|
|
1105
|
+
defaultImport = defaultImport.default;
|
|
1106
|
+
}
|
|
1107
|
+
if (typeof defaultImport === "function") {
|
|
1108
|
+
return new defaultImport();
|
|
1109
|
+
} else if (defaultImport instanceof Command) {
|
|
1110
|
+
return defaultImport;
|
|
1111
|
+
}
|
|
1112
|
+
return null;
|
|
1113
|
+
};
|
|
1114
|
+
withCommandResolver(resolver) {
|
|
1115
|
+
this.commandResolver = resolver;
|
|
1116
|
+
return this;
|
|
1117
|
+
}
|
|
1118
|
+
withFileImporter(importer) {
|
|
1119
|
+
this.importFile = importer;
|
|
1120
|
+
return this;
|
|
1121
|
+
}
|
|
1122
|
+
registerCommand(command, force = false) {
|
|
1123
|
+
const commandName = command.command;
|
|
1124
|
+
if (!commandName) {
|
|
1125
|
+
throw new Error("Command signature is invalid, it must have a command name.");
|
|
1126
|
+
}
|
|
1127
|
+
if (!force && this.commands[commandName]) {
|
|
1128
|
+
throw new Error(`Command ${commandName} already registered.`);
|
|
1129
|
+
}
|
|
1130
|
+
this.commands[commandName] = command;
|
|
1131
|
+
}
|
|
1132
|
+
async loadCommandsPath(commandsPath) {
|
|
1133
|
+
const commandsStream = this.listCommandsFiles(commandsPath);
|
|
1134
|
+
for await (const file of commandsStream) {
|
|
1135
|
+
try {
|
|
1136
|
+
const command = await this.commandResolver(file);
|
|
1137
|
+
if (command instanceof Command) {
|
|
1138
|
+
this.registerCommand(command);
|
|
1139
|
+
}
|
|
1140
|
+
} catch (e) {
|
|
1141
|
+
throw new Error(`Command ${file} failed to load. ${e}`, {
|
|
1142
|
+
cause: e
|
|
1143
|
+
});
|
|
1144
|
+
}
|
|
1145
|
+
}
|
|
1146
|
+
}
|
|
1147
|
+
async runCommand(ctx, command, ...args) {
|
|
1148
|
+
const commandToRun = typeof command === "string" ? this.commands[command] : command;
|
|
1149
|
+
const commandSignature = typeof command === "string" ? command : commandToRun.command;
|
|
1150
|
+
if (!commandToRun) {
|
|
1151
|
+
const suggestedCommand = await this.suggestCommand(commandSignature);
|
|
1152
|
+
if (suggestedCommand) {
|
|
1153
|
+
return await this.runCommand(ctx, suggestedCommand, ...args);
|
|
1154
|
+
}
|
|
1155
|
+
return 1;
|
|
1156
|
+
}
|
|
1157
|
+
return await commandToRun.run({
|
|
1158
|
+
ctx,
|
|
1159
|
+
logger: this.logger,
|
|
1160
|
+
args
|
|
1161
|
+
}) ?? 0;
|
|
1162
|
+
}
|
|
1163
|
+
async suggestCommand(command) {
|
|
1164
|
+
const availableCommands = this.getAvailableCommands();
|
|
1165
|
+
const { bestMatch, bestMatchIndex, ratings } = this.stringSimilarity.findBestMatch(command, availableCommands);
|
|
1166
|
+
const similarCommands = ratings.filter((r) => r.rating > 0.3).map((r) => r.target);
|
|
1167
|
+
if (bestMatch.rating > 0 && similarCommands.length <= 1 || bestMatch.rating > 0.7 && similarCommands.length > 1) {
|
|
1168
|
+
const commandToAsk = availableCommands[bestMatchIndex];
|
|
1169
|
+
const runCommand = await this.askRunSimilarCommand(command, commandToAsk);
|
|
1170
|
+
if (runCommand) {
|
|
1171
|
+
return commandToAsk;
|
|
1172
|
+
} else {
|
|
1173
|
+
return null;
|
|
1174
|
+
}
|
|
1175
|
+
}
|
|
1176
|
+
if (similarCommands.length) {
|
|
1177
|
+
this.io.error(`${chalk.bgRed(" ERROR ")} Command ${chalk.yellow(command)} not found.
|
|
1178
|
+
`);
|
|
1179
|
+
const commandToRun = await this.io.askForSelect(chalk.green("Did you mean to run one of these commands instead?"), similarCommands);
|
|
1180
|
+
if (commandToRun) {
|
|
1181
|
+
return commandToRun;
|
|
1182
|
+
}
|
|
1183
|
+
}
|
|
1184
|
+
throw new CommandNotFoundError(command);
|
|
1185
|
+
}
|
|
1186
|
+
async askRunSimilarCommand(command, commandToAsk) {
|
|
1187
|
+
this.io.error(`${chalk.bgRed(" ERROR ")} Command ${chalk.yellow(command)} not found.
|
|
1188
|
+
`);
|
|
1189
|
+
return this.io.askForConfirmation(`${chalk.green(`Do you want to run ${chalk.yellow(commandToAsk)} instead?`)} `);
|
|
1190
|
+
}
|
|
1191
|
+
async *listCommandsFiles(basePath) {
|
|
1192
|
+
console.log(fs);
|
|
1193
|
+
const dirEntry = fs.readdirSync(basePath, { withFileTypes: true });
|
|
1194
|
+
console.log(dirEntry);
|
|
1195
|
+
for (const dirent of dirEntry) {
|
|
1196
|
+
const direntPath = path.resolve(basePath, dirent.name);
|
|
1197
|
+
if (dirent.isDirectory()) {
|
|
1198
|
+
yield* this.listCommandsFiles(path.resolve(basePath, dirent.name));
|
|
1199
|
+
} else {
|
|
1200
|
+
if (!direntPath.endsWith(`.ts`) && !direntPath.endsWith(`.js`) && !direntPath.endsWith(`.mjs`) && !direntPath.endsWith(`.cjs`)) {
|
|
1201
|
+
continue;
|
|
1202
|
+
}
|
|
1203
|
+
yield direntPath;
|
|
1204
|
+
}
|
|
1205
|
+
}
|
|
1206
|
+
}
|
|
1207
|
+
}
|
|
1208
|
+
class ExceptionHandler {
|
|
1209
|
+
logger;
|
|
1210
|
+
constructor(logger) {
|
|
1211
|
+
this.logger = logger;
|
|
1212
|
+
}
|
|
1213
|
+
handle(err) {
|
|
1214
|
+
if (err instanceof BobError) {
|
|
1215
|
+
err.pretty(this.logger);
|
|
1216
|
+
return -1;
|
|
1217
|
+
}
|
|
1218
|
+
throw err;
|
|
1219
|
+
}
|
|
1220
|
+
}
|
|
1221
|
+
class HelpCommand extends Command {
|
|
1222
|
+
constructor(opts) {
|
|
1223
|
+
super("help", {
|
|
1224
|
+
description: chalk.bold("Show help information about the CLI and its commands")
|
|
1225
|
+
});
|
|
1226
|
+
this.opts = opts;
|
|
1227
|
+
}
|
|
1228
|
+
async handle() {
|
|
1229
|
+
const commands = this.opts.commandRegistry.getCommands();
|
|
1230
|
+
const cliName = this.opts.cliName ?? "Bob CLI";
|
|
1231
|
+
const version = this.opts.cliVersion ?? "0.0.0";
|
|
1232
|
+
const coreVersion = (await import("./package-CnNxf727.js"))?.default?.version ?? "0.0.0";
|
|
1233
|
+
this.io.log(`${cliName} ${chalk.green(version)} (core: ${chalk.yellow(coreVersion)})
|
|
1234
|
+
|
|
1235
|
+
${chalk.yellow("Usage")}:
|
|
1236
|
+
command [options] [arguments]
|
|
1237
|
+
|
|
1238
|
+
${chalk.yellow("Available commands")}:
|
|
1239
|
+
`);
|
|
1240
|
+
const maxCommandLength = Math.max(...commands.map((command) => command.command.length)) ?? 0;
|
|
1241
|
+
const commandByGroups = {};
|
|
1242
|
+
for (const command of commands) {
|
|
1243
|
+
const commandGroup = command.group ?? command.command.split(":")[0];
|
|
1244
|
+
if (!commandByGroups[commandGroup]) {
|
|
1245
|
+
commandByGroups[commandGroup] = [];
|
|
1246
|
+
}
|
|
1247
|
+
commandByGroups[commandGroup].push(command);
|
|
1248
|
+
}
|
|
1249
|
+
const sortedCommandsByGroups = Object.entries(commandByGroups).sort(([groupA], [groupB]) => groupA.toLowerCase().localeCompare(groupB.toLowerCase())).sort(([, commandsA], [, commandsB]) => commandsA.length - commandsB.length);
|
|
1250
|
+
for (const [group, groupCommands] of sortedCommandsByGroups) {
|
|
1251
|
+
const isGrouped = groupCommands.length > 1;
|
|
1252
|
+
if (isGrouped) {
|
|
1253
|
+
this.io.log(chalk.yellow(`${group}:`));
|
|
1254
|
+
}
|
|
1255
|
+
const sortedGroupCommands = groupCommands.sort((a, b) => a.command.toLowerCase().localeCompare(b.command.toLowerCase()));
|
|
1256
|
+
for (const command of sortedGroupCommands) {
|
|
1257
|
+
let spaces = generateSpace(maxCommandLength - command.command.length);
|
|
1258
|
+
if (isGrouped) {
|
|
1259
|
+
spaces = spaces.slice(2);
|
|
1260
|
+
}
|
|
1261
|
+
this.io.log(`${isGrouped ? " " : ""}${chalk.green(command.command)} ${spaces} ${command.description}`);
|
|
1262
|
+
}
|
|
1263
|
+
}
|
|
1264
|
+
}
|
|
1265
|
+
}
|
|
1266
|
+
class Cli {
|
|
1267
|
+
ctx;
|
|
1268
|
+
logger;
|
|
1269
|
+
commandRegistry;
|
|
1270
|
+
exceptionHandler;
|
|
1271
|
+
helpCommand;
|
|
1272
|
+
newCommandRegistry(opts) {
|
|
1273
|
+
return new CommandRegistry(opts);
|
|
1274
|
+
}
|
|
1275
|
+
newHelpCommand(opts) {
|
|
1276
|
+
return new HelpCommand(opts);
|
|
1277
|
+
}
|
|
1278
|
+
newExceptionHandler(opts) {
|
|
1279
|
+
return new ExceptionHandler(opts.logger);
|
|
1280
|
+
}
|
|
1281
|
+
constructor(opts = {}) {
|
|
1282
|
+
this.ctx = opts.ctx;
|
|
1283
|
+
this.logger = opts.logger ?? new Logger();
|
|
1284
|
+
this.commandRegistry = this.newCommandRegistry({
|
|
1285
|
+
logger: this.logger
|
|
1286
|
+
});
|
|
1287
|
+
this.exceptionHandler = this.newExceptionHandler({
|
|
1288
|
+
logger: this.logger
|
|
1289
|
+
});
|
|
1290
|
+
this.helpCommand = this.newHelpCommand({
|
|
1291
|
+
cliName: opts.name,
|
|
1292
|
+
cliVersion: opts.version,
|
|
1293
|
+
commandRegistry: this.commandRegistry
|
|
1294
|
+
});
|
|
1295
|
+
}
|
|
1296
|
+
withCommandResolver(resolver) {
|
|
1297
|
+
this.commandRegistry.withCommandResolver(resolver);
|
|
1298
|
+
return this;
|
|
1299
|
+
}
|
|
1300
|
+
withFileImporter(importer) {
|
|
1301
|
+
this.commandRegistry.withFileImporter(importer);
|
|
1302
|
+
return this;
|
|
1303
|
+
}
|
|
1304
|
+
async withCommands(...commands) {
|
|
1305
|
+
for (const command of commands) {
|
|
1306
|
+
if (typeof command === "string") {
|
|
1307
|
+
await this.commandRegistry.loadCommandsPath(command);
|
|
1308
|
+
} else {
|
|
1309
|
+
if (typeof command === "function") {
|
|
1310
|
+
this.registerCommand(new command());
|
|
1311
|
+
} else {
|
|
1312
|
+
this.registerCommand(command);
|
|
1313
|
+
}
|
|
1314
|
+
}
|
|
1315
|
+
}
|
|
1316
|
+
}
|
|
1317
|
+
async runCommand(command, ...args) {
|
|
1318
|
+
if (!command) {
|
|
1319
|
+
return await this.runHelpCommand();
|
|
1320
|
+
}
|
|
1321
|
+
return await this.commandRegistry.runCommand(this.ctx ?? {}, command, ...args).catch(this.exceptionHandler.handle.bind(this.exceptionHandler));
|
|
1322
|
+
}
|
|
1323
|
+
async runHelpCommand() {
|
|
1324
|
+
return await this.runCommand(this.helpCommand);
|
|
1325
|
+
}
|
|
1326
|
+
registerCommand(command) {
|
|
1327
|
+
this.commandRegistry.registerCommand(command);
|
|
1328
|
+
}
|
|
1329
|
+
}
|
|
1330
|
+
export {
|
|
1331
|
+
BadCommandOption,
|
|
1332
|
+
BadCommandParameter,
|
|
1333
|
+
BobError,
|
|
1334
|
+
Cli,
|
|
1335
|
+
Command,
|
|
1336
|
+
CommandIO,
|
|
1337
|
+
CommandNotFoundError,
|
|
1338
|
+
CommandParser,
|
|
1339
|
+
CommandRegistry,
|
|
1340
|
+
CommandSignatureParser,
|
|
1341
|
+
CommandWithSignature,
|
|
1342
|
+
ExceptionHandler,
|
|
1343
|
+
HelpOption,
|
|
1344
|
+
InvalidOption,
|
|
1345
|
+
Logger,
|
|
1346
|
+
MissingRequiredArgumentValue,
|
|
1347
|
+
MissingRequiredOptionValue,
|
|
1348
|
+
StringSimilarity
|
|
1349
|
+
};
|