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