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
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
+ };