millas 0.2.28 → 0.2.30

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 (47) hide show
  1. package/bin/millas.js +12 -2
  2. package/package.json +2 -1
  3. package/src/cli.js +117 -20
  4. package/src/commands/call.js +1 -1
  5. package/src/commands/createsuperuser.js +137 -182
  6. package/src/commands/key.js +61 -83
  7. package/src/commands/lang.js +423 -515
  8. package/src/commands/make.js +88 -62
  9. package/src/commands/migrate.js +200 -279
  10. package/src/commands/new.js +55 -50
  11. package/src/commands/route.js +78 -80
  12. package/src/commands/schedule.js +52 -150
  13. package/src/commands/serve.js +158 -191
  14. package/src/console/AppCommand.js +106 -0
  15. package/src/console/BaseCommand.js +726 -0
  16. package/src/console/CommandContext.js +66 -0
  17. package/src/console/CommandRegistry.js +88 -0
  18. package/src/console/Style.js +123 -0
  19. package/src/console/index.js +12 -3
  20. package/src/container/AppInitializer.js +10 -0
  21. package/src/facades/DB.js +195 -0
  22. package/src/index.js +2 -1
  23. package/src/scaffold/maker.js +102 -42
  24. package/src/schematics/Collection.js +28 -0
  25. package/src/schematics/SchematicEngine.js +122 -0
  26. package/src/schematics/Template.js +99 -0
  27. package/src/schematics/index.js +7 -0
  28. package/src/templates/command/default.template.js +14 -0
  29. package/src/templates/command/schema.json +19 -0
  30. package/src/templates/controller/default.template.js +10 -0
  31. package/src/templates/controller/resource.template.js +59 -0
  32. package/src/templates/controller/schema.json +30 -0
  33. package/src/templates/job/default.template.js +11 -0
  34. package/src/templates/job/schema.json +19 -0
  35. package/src/templates/middleware/default.template.js +11 -0
  36. package/src/templates/middleware/schema.json +19 -0
  37. package/src/templates/migration/default.template.js +14 -0
  38. package/src/templates/migration/schema.json +19 -0
  39. package/src/templates/model/default.template.js +14 -0
  40. package/src/templates/model/migration.template.js +17 -0
  41. package/src/templates/model/schema.json +30 -0
  42. package/src/templates/service/default.template.js +12 -0
  43. package/src/templates/service/schema.json +19 -0
  44. package/src/templates/shape/default.template.js +11 -0
  45. package/src/templates/shape/schema.json +19 -0
  46. package/src/validation/BaseValidator.js +3 -0
  47. package/src/validation/types.js +3 -3
@@ -0,0 +1,726 @@
1
+ 'use strict';
2
+
3
+ const chalk = require('chalk');
4
+ const Style = require('./Style');
5
+ const AppCommand = require('./AppCommand');
6
+ const v = require("../core/validation");
7
+
8
+ /**
9
+ * @typedef {Object} CommandContext
10
+ * @property {Object} program - Commander program instance
11
+ * @property {Object} container - DI container
12
+ * @property {Object} logger - Logger instance
13
+ * @property {string} cwd - Current working directory
14
+ */
15
+
16
+ /**
17
+ * @callback StringValidatorCallback
18
+ * @param {import('../validation/types').StringValidator} validator - String validator instance
19
+ * @returns {import('../validation/types').StringValidator}
20
+ */
21
+
22
+ /**
23
+ * @callback NumberValidatorCallback
24
+ * @param {import('../validation/types').NumberValidator} validator - Number validator instance
25
+ * @returns {import('../validation/types').NumberValidator}
26
+ */
27
+
28
+ /**
29
+ * @callback EmailValidatorCallback
30
+ * @param {import('../validation/types').EmailValidator} validator - Email validator instance
31
+ * @returns {import('../validation/types').EmailValidator}
32
+ */
33
+
34
+ /**
35
+ * @callback BooleanValidatorCallback
36
+ * @param {import('../validation/types').BooleanValidator} validator - Boolean validator instance
37
+ * @returns {import('../validation/types').BooleanValidator}
38
+ */
39
+
40
+ /**
41
+ * @callback ValidatorCallback
42
+ * @param {typeof import('../core/validation')} v - Validation module with factory functions
43
+ * @returns {import('../validation/BaseValidator').BaseValidator}
44
+ */
45
+
46
+ /**
47
+ * Base class for all CLI commands
48
+ */
49
+ class BaseCommand extends AppCommand {
50
+ static command = '';
51
+ static namespace = ''; // Override to set custom namespace
52
+ static description = '';
53
+ static aliases = [];
54
+ static options = [];
55
+
56
+ constructor(context) {
57
+ super(context);
58
+ this.style = new Style();
59
+ }
60
+
61
+ /**
62
+ * Initialize commands - override in subclasses
63
+ * @param {CommandRegistrar} register - Command registration helper
64
+ * @returns {Promise<void>}
65
+ */
66
+ async onInit(register) {
67
+ // Override in subclasses to register commands
68
+ }
69
+
70
+ /**
71
+ * Auto-derive command name from class name
72
+ * Supports CamelCase, underscores, and hyphens
73
+ */
74
+ static getCommandName() {
75
+ // Use custom namespace if provided
76
+ if (this.namespace) return this.namespace;
77
+ if (this.command) return this.command;
78
+
79
+ const className = this.name.replace(/Command$/i, '');
80
+ if (!className) {
81
+ throw new Error(`Cannot derive command name from class ${this.name}`);
82
+ }
83
+
84
+ return className
85
+ .replace(/[-_]/g, ':')
86
+ .replace(/([A-Z]+)([A-Z][a-z])/g, '$1:$2')
87
+ .replace(/([a-z\d])([A-Z])/g, '$1:$2')
88
+ .toLowerCase();
89
+ }
90
+
91
+ getSubcommands() {
92
+ const proto = Object.getPrototypeOf(this);
93
+ return Object.getOwnPropertyNames(proto)
94
+ .filter(name => {
95
+ return typeof proto[name] === 'function' &&
96
+ name !== 'constructor' &&
97
+ !['register', 'handle', 'validate', 'before', 'after', 'onError', 'onInit', 'addArguments'].includes(name) &&
98
+ !name.startsWith('_');
99
+ });
100
+ }
101
+
102
+ /**
103
+ * Register command with Commander
104
+ * Calls onInit() for subcommands or registerSimpleCommand() for simple commands
105
+ */
106
+ async register() {
107
+ const commandName = this.constructor.getCommandName();
108
+ const subcommands = this.getSubcommands();
109
+
110
+ if (typeof this.onInit === 'function') {
111
+ const registrar = new CommandRegistrar(this, commandName);
112
+ await this.onInit(registrar);
113
+
114
+ // Find subcommand matching base name and make it the default
115
+ for (const cmd of registrar.commands) {
116
+ const subName = cmd.fullCommand.split(':')[1];
117
+ if (subName === commandName) {
118
+ // Register as base command (index/default)
119
+ cmd.fullCommand = commandName;
120
+ break;
121
+ }
122
+ }
123
+
124
+ registrar.finalizeAll();
125
+ return;
126
+ }
127
+
128
+ if (subcommands.length === 0 || subcommands.includes('handle')) {
129
+ this.registerSimpleCommand(commandName);
130
+ } else {
131
+ throw new Error(
132
+ `Command ${commandName} has subcommands but no onInit() method. ` +
133
+ `Add: async onInit(register) { register.command(this.${subcommands[0]}); }`
134
+ );
135
+ }
136
+ }
137
+
138
+ registerSimpleCommand(commandName) {
139
+ const cmd = this.program
140
+ .command(commandName)
141
+ .description(this.constructor.description || 'No description provided');
142
+
143
+ if (this.constructor.aliases && this.constructor.aliases.length) {
144
+ cmd.aliases(this.constructor.aliases);
145
+ }
146
+
147
+ if (this.constructor.options && this.constructor.options.length) {
148
+ for (const opt of this.constructor.options) {
149
+ if (opt.flags) {
150
+ cmd.option(opt.flags, opt.description || '', opt.defaultValue);
151
+ }
152
+ }
153
+ }
154
+
155
+ if (typeof this.addArguments === 'function') {
156
+ this.addArguments(cmd);
157
+ }
158
+
159
+ cmd.action(this.asyncHandler(this.handle.bind(this)));
160
+ }
161
+
162
+ /**
163
+ * Override to add custom arguments/options
164
+ * @param {Command} parser - Commander command instance
165
+ */
166
+ addArguments(parser) {}
167
+
168
+ /**
169
+ * Main command handler - must be implemented by subclasses
170
+ */
171
+ async handle(...args) {
172
+ throw new Error(`Command ${this.constructor.name} must implement handle() method`);
173
+ }
174
+
175
+ asyncHandler(fn) {
176
+ return async (...args) => {
177
+ const startTime = Date.now();
178
+ try {
179
+ await this.before(...args);
180
+ await this.validate(...args);
181
+ const result = await fn.call(this, ...args);
182
+ await this.after(...args);
183
+
184
+ if (process.env.DEBUG) {
185
+ this.info(`Completed in ${Date.now() - startTime}ms`);
186
+ }
187
+ return result;
188
+ } catch (err) {
189
+ await this.onError(err);
190
+ this.handleError(err);
191
+ }
192
+ };
193
+ }
194
+
195
+ /**
196
+ * Lifecycle hook: runs before validation and handler
197
+ */
198
+ async before(...args) {}
199
+
200
+ /**
201
+ * Lifecycle hook: runs after successful execution
202
+ */
203
+ async after(...args) {}
204
+
205
+ /**
206
+ * Lifecycle hook: runs when error occurs
207
+ */
208
+ async onError(err) {}
209
+
210
+ /**
211
+ * Validate command inputs before execution
212
+ */
213
+ async validate(...args) {}
214
+
215
+ handleError(err) {
216
+ if (err.code === 'ENOENT') {
217
+ this.logger.error(this.style.danger(`\n ✖ File not found: ${err.path}\n`));
218
+ } else if (err.code === 'EEXIST') {
219
+ this.logger.error(this.style.danger(`\n ✖ File already exists: ${err.path}\n`));
220
+ } else {
221
+ this.logger.error(this.style.danger(`\n ✖ Error: ${err.message}\n`));
222
+ if (process.env.DEBUG) {
223
+ this.logger.error(err.stack);
224
+ }
225
+ }
226
+
227
+ if (this.program?.exitOverride || process.env.NODE_ENV === 'test') {
228
+ throw err;
229
+ }
230
+
231
+ process.exitCode = 1;
232
+ }
233
+
234
+ success(message) {
235
+ this.logger.log(this.style.success(`\n ✔ ${message}\n`));
236
+ }
237
+
238
+ info(message) {
239
+ this.logger.log(this.style.info(` ${message}`));
240
+ }
241
+
242
+ warn(message) {
243
+ this.logger.log(this.style.warning(` ⚠ ${message}`));
244
+ }
245
+
246
+ error(message) {
247
+ this.logger.error(this.style.danger(` ✖ ${message}`));
248
+ }
249
+ }
250
+
251
+ /**
252
+ * CommandRegistrar - Helper for explicit command registration
253
+ */
254
+ class CommandRegistrar {
255
+ constructor(baseCommand, baseName) {
256
+ this.baseCommand = baseCommand;
257
+ this.baseName = baseName;
258
+ this.program = baseCommand.program;
259
+ this.lastCommand = null;
260
+ this.commands = [];
261
+ }
262
+
263
+ /**
264
+ * Start registering a command
265
+ *
266
+ * @param {Function} fn - The method to register (can be arrow function or method)
267
+ * @returns {CommandRegistrar} - For chaining
268
+ *
269
+ * @example
270
+ * // Using a method
271
+ * register.command(this.create)
272
+ * .arg('name', string().required(), 'User name')
273
+ *
274
+ * // Using inline arrow function
275
+ * register.command(async (name) => {
276
+ * this.success(`Created ${name}`);
277
+ * })
278
+ * .arg('name', 'Item name')
279
+ * .name('create')
280
+ */
281
+ command(fn) {
282
+ if (this.lastCommand) {
283
+ this.#finalize();
284
+ }
285
+
286
+ // Derive method name from function name, or use 'anonymous' for arrow functions
287
+ const methodName = fn.name || 'anonymous';
288
+ const fullCommand = `${this.baseName}:${methodName}`;
289
+ const defaultDescription = methodName !== 'anonymous'
290
+ ? `${methodName.charAt(0).toUpperCase()}${methodName.slice(1)} command`
291
+ : 'Command';
292
+
293
+ this.lastCommand = {
294
+ fn,
295
+ methodName,
296
+ fullCommand,
297
+ description: defaultDescription,
298
+ args: [],
299
+ aliases: [],
300
+ hooks: {
301
+ before: null,
302
+ after: null,
303
+ validate: null,
304
+ onError: null
305
+ }
306
+ };
307
+
308
+
309
+ this.commands.push(this.lastCommand);
310
+ return this;
311
+ }
312
+
313
+ /**
314
+ * Add an argument or flag
315
+ *
316
+ * @param {string} name - Argument name (use '--' prefix for flags)
317
+ * @param {import('../validation/BaseValidator').BaseValidator|ValidatorCallback|string} [validatorOrDescription] - Validator instance, callback, or description
318
+ * @param {string} [description] - Optional description
319
+ * @returns {CommandRegistrar} - For chaining
320
+ *
321
+ * @example
322
+ * .arg('name', v => v.string().required().min(2), 'User full name')
323
+ * .arg('email', v => v.email().required())
324
+ * .arg('--admin', v => v.boolean(), 'Create as admin')
325
+ * .arg('--force', 'Force creation') // boolean by default
326
+ */
327
+ arg(name, validatorOrDescription, description) {
328
+ if (!this.lastCommand) {
329
+ throw new Error('No command to add arg to. Call command() first.');
330
+ }
331
+
332
+ const isFlag = name.startsWith('--') || name.startsWith('-');
333
+ const cleanName = name.replace(/^--?/, '').replace(/^\[|\]$/g, '');
334
+ let validator = null;
335
+ let desc = description;
336
+
337
+ if (typeof validatorOrDescription === 'string') {
338
+ desc = validatorOrDescription;
339
+ } else if (typeof validatorOrDescription === 'function') {
340
+ // Callback receives validation module
341
+ validator = validatorOrDescription(require("../core/validation"));
342
+ } else if (validatorOrDescription && typeof validatorOrDescription === 'object') {
343
+ // Direct validator object
344
+ validator = validatorOrDescription;
345
+ if (!desc) {
346
+ desc = cleanName.charAt(0).toUpperCase() + cleanName.slice(1);
347
+ }
348
+ }
349
+
350
+ this.lastCommand.args.push({
351
+ name: cleanName,
352
+ originalName: name,
353
+ isFlag,
354
+ validator,
355
+ description: desc || cleanName
356
+ });
357
+
358
+ return this;
359
+ }
360
+
361
+ /**
362
+ * Alias for arg() - adds an argument
363
+ * @param {string} name - Argument name
364
+ * @param {import('../validation/BaseValidator').BaseValidator|ValidatorCallback|string} [validatorOrDescription] - Validator instance, callback, or description
365
+ * @param {string} [description] - Optional description
366
+ * @returns {CommandRegistrar}
367
+ */
368
+ argument(name, validatorOrDescription, description) {
369
+ return this.arg(name, validatorOrDescription, description);
370
+ }
371
+
372
+ /**
373
+ * Alias for arg() with '--' prefix - adds an option/flag
374
+ * @param {string} name - Option name (without --)
375
+ * @param {import('../validation/BaseValidator').BaseValidator|ValidatorCallback|string} [validatorOrDescription] - Validator instance, callback, or description
376
+ * @param {string} [description] - Optional description
377
+ * @returns {CommandRegistrar}
378
+ */
379
+ option(name, validatorOrDescription, description) {
380
+ if (!name.startsWith('--') && !name.startsWith('-')) {
381
+ name = '--' + name;
382
+ }
383
+ return this.arg(name, validatorOrDescription, description);
384
+ }
385
+
386
+
387
+ /**
388
+ * Add a string argument with optional validator
389
+ * @param {string} name - Argument name
390
+ * @param {StringValidatorCallback|string} [validatorOrDescription] - Validator callback or description
391
+ * @param {string} [description] - Optional description
392
+ * @returns {CommandRegistrar}
393
+ * @example
394
+ * .str('name', 'User name')
395
+ * .str('email', v => v.min(5).max(100), 'Email address')
396
+ */
397
+ str(name, validatorOrDescription, description) {
398
+ if (typeof validatorOrDescription === 'function') {
399
+ return this.arg(name, v => {
400
+ const base = v.string().required();
401
+ return validatorOrDescription(base);
402
+ }, description);
403
+ }
404
+ return this.arg(name, v => v.string().required(), validatorOrDescription || description);
405
+ }
406
+
407
+ /**
408
+ * Add a number argument with optional validator
409
+ * @param {string} name - Argument name
410
+ * @param {NumberValidatorCallback|string} [validatorOrDescription] - Validator callback or description
411
+ * @param {string} [description] - Optional description
412
+ * @returns {CommandRegistrar}
413
+ * @example
414
+ * .num('age', 'User age')
415
+ * .num('port', v => v.min(1000).max(9999), 'Port number')
416
+ */
417
+ num(name, validatorOrDescription, description) {
418
+ if (typeof validatorOrDescription === 'function') {
419
+ return this.arg(name, v => {
420
+ const base = v.number().required();
421
+ return validatorOrDescription(base);
422
+ }, description);
423
+ }
424
+ return this.arg(name, v => v.number().required(), validatorOrDescription || description);
425
+ }
426
+
427
+ /**
428
+ * Add a boolean flag
429
+ * @param {string} name - Flag name (without --)
430
+ * @param {BooleanValidatorCallback|string} [validatorOrDescription] - Validator callback or description
431
+ * @param {string} [description] - Optional description
432
+ * @returns {CommandRegistrar}
433
+ * @example
434
+ * .bool('force', 'Force operation')
435
+ * .bool('verbose', 'Verbose output')
436
+ */
437
+ bool(name, validatorOrDescription, description) {
438
+ if (!name.startsWith('--') && !name.startsWith('-')) {
439
+ name = '--' + name;
440
+ }
441
+ if (typeof validatorOrDescription === 'function') {
442
+ return this.arg(name, v => {
443
+ const base = v.boolean();
444
+ return validatorOrDescription(base);
445
+ }, description);
446
+ }
447
+ return this.arg(name, v => v.boolean(), validatorOrDescription || description);
448
+ }
449
+
450
+ /**
451
+ * Add an email argument
452
+ * @param {string} name - Argument name
453
+ * @param {EmailValidatorCallback|string} [validatorOrDescription] - Validator callback or description
454
+ * @param {string} [description] - Optional description
455
+ * @returns {CommandRegistrar}
456
+ * @example
457
+ * .email('email', 'User email address')
458
+ * .email('email', v => v.domain('example.com'), 'Company email')
459
+ */
460
+ email(name, validatorOrDescription, description) {
461
+ if (typeof validatorOrDescription === 'function') {
462
+ return this.arg(name, v => {
463
+ const base = v.email().required();
464
+ return validatorOrDescription(base);
465
+ }, description);
466
+ }
467
+ return this.arg(name, v => v.email().required(), validatorOrDescription || description);
468
+ }
469
+
470
+ /**
471
+ * Add an enum argument
472
+ * @param {string} name - Argument name
473
+ * @param {Array<string>} values - Allowed values
474
+ * @param {string} [description] - Optional description
475
+ * @returns {CommandRegistrar}
476
+ * @example
477
+ * .enum('role', ['admin', 'user', 'guest'], 'User role')
478
+ */
479
+ enum(name, values, description) {
480
+ return this.arg(name, v => v.enum(values).required(), description);
481
+ }
482
+
483
+ /**
484
+ * Set description for the command
485
+ */
486
+ description(desc) {
487
+ if (!this.lastCommand) {
488
+ throw new Error('No command to set description for. Call command() first.');
489
+ }
490
+
491
+ this.lastCommand.description = desc;
492
+ return this;
493
+ }
494
+
495
+ /**
496
+ * Set aliases for the subcommand
497
+ *
498
+ * @param {string[]} aliases - Array of alias names
499
+ * @returns {CommandRegistrar} - For chaining
500
+ *
501
+ * @example
502
+ * register.command(this.create)
503
+ * .aliases(['c', 'new'])
504
+ */
505
+ aliases(aliasArray) {
506
+ if (!this.lastCommand) {
507
+ throw new Error('No command to set aliases for. Call command() first.');
508
+ }
509
+
510
+ this.lastCommand.aliases = aliasArray;
511
+ return this;
512
+ }
513
+
514
+ /**
515
+ * Override the command name
516
+ *
517
+ * @param {string} customName - Custom command name (without base)
518
+ * @returns {CommandRegistrar} - For chaining
519
+ *
520
+ * @example
521
+ * register.command(this.update)
522
+ * .name('modify')
523
+ * // Results in: user:modify instead of user:update
524
+ */
525
+ name(customName) {
526
+ if (!this.lastCommand) {
527
+ throw new Error('No command to set name for. Call command() first.');
528
+ }
529
+
530
+ this.lastCommand.fullCommand = `${this.baseName}:${customName}`;
531
+ return this;
532
+ }
533
+
534
+ before(fn) {
535
+ if (!this.lastCommand) {
536
+ throw new Error('No command to set before hook for. Call command() first.');
537
+ }
538
+
539
+ this.lastCommand.hooks.before = fn;
540
+ return this;
541
+ }
542
+
543
+ after(fn) {
544
+ if (!this.lastCommand) {
545
+ throw new Error('No command to set after hook for. Call command() first.');
546
+ }
547
+
548
+ this.lastCommand.hooks.after = fn;
549
+ return this;
550
+ }
551
+
552
+ validate(fn) {
553
+ if (!this.lastCommand) {
554
+ throw new Error('No command to set validate hook for. Call command() first.');
555
+ }
556
+
557
+ this.lastCommand.hooks.validate = fn;
558
+ return this;
559
+ }
560
+
561
+ onError(fn) {
562
+ if (!this.lastCommand) {
563
+ throw new Error('No command to set onError hook for. Call command() first.');
564
+ }
565
+
566
+ this.lastCommand.hooks.onError = fn;
567
+ return this;
568
+ }
569
+
570
+ finalizeAll() {
571
+ this.#finalizeAll();
572
+ }
573
+
574
+ #finalizeAll() {
575
+ for (const cmd of this.commands) {
576
+ if (!cmd._finalized) {
577
+ this.lastCommand = cmd;
578
+ this.#finalize();
579
+ }
580
+ }
581
+ }
582
+
583
+ #finalize() {
584
+
585
+ if (!this.lastCommand || this.lastCommand._finalized) return;
586
+
587
+ const { fn, fullCommand, description, args, hooks, aliases } = this.lastCommand;
588
+
589
+
590
+ let commandStr = fullCommand;
591
+ const positionalArgs = args.filter(a => !a.isFlag);
592
+
593
+ const flagArgs = args.filter(a => a.isFlag);
594
+
595
+ // Build command string with positional args - all optional, validators handle required checks
596
+ for (const arg of positionalArgs) {
597
+ commandStr += ` [${arg.name}]`;
598
+ }
599
+
600
+ const cmd = this.program
601
+ .command(commandStr)
602
+ .description(description);
603
+
604
+ // Add subcommand aliases
605
+ if (aliases && aliases.length > 0) {
606
+ cmd.aliases(aliases);
607
+ }
608
+
609
+ for (const arg of flagArgs) {
610
+ const flagName = `--${arg.name}`;
611
+ const negatedFlagName = `--no-${arg.name}`;
612
+
613
+ // Default validator for flags without one: boolean()
614
+ if (!arg.validator) {
615
+ arg.validator = v.boolean();
616
+ }
617
+
618
+ // Detect boolean validator
619
+ const isBoolean = arg.validator._type === 'boolean';
620
+
621
+ if (isBoolean) {
622
+ cmd.option(flagName, arg.description);
623
+ cmd.option(negatedFlagName, `Disable ${arg.name}`);
624
+ } else {
625
+ cmd.option(`${flagName} [value]`, arg.description);
626
+ }
627
+ }
628
+
629
+ if (process.env.AP_DEBUG) {
630
+ console.log(`Registered: ${commandStr}`);
631
+ console.log(` Args: ${args.map(a => a.originalName).join(', ')}`);
632
+ }
633
+
634
+ cmd.action(async (...cmdArgs) => {
635
+
636
+
637
+ const cmdOptions = cmdArgs[cmdArgs.length - 1];
638
+ const options = cmdOptions.opts ? cmdOptions.opts() : cmdOptions;
639
+ const positionalValues = cmdArgs.slice(0, -1);
640
+
641
+ const handlerArgs = [];
642
+ const validationData = {};
643
+ let positionalIndex = 0;
644
+
645
+ for (const arg of args) {
646
+ let value = undefined;
647
+
648
+ if (arg.isFlag) {
649
+ value = options[arg.name];
650
+ } else {
651
+ value = positionalValues[positionalIndex++];
652
+ }
653
+
654
+
655
+ handlerArgs.push(value);
656
+ validationData[arg.name] = value;
657
+ }
658
+
659
+ const startTime = Date.now();
660
+
661
+ try {
662
+ if (hooks.before) {
663
+ await hooks.before.call(this.baseCommand, ...handlerArgs);
664
+ } else if (typeof this.baseCommand.before === 'function') {
665
+ await this.baseCommand.before(...handlerArgs);
666
+ }
667
+
668
+ for (const arg of args) {
669
+ // Default validator for positional args without one: string().required()
670
+ if (!arg.validator && !arg.isFlag) {
671
+ arg.validator = v.string().required();
672
+ }
673
+
674
+ let value = validationData[arg.name];
675
+
676
+ // Apply default value if undefined and validator has default
677
+ if (value === undefined && arg.validator && arg.validator._default !== undefined) {
678
+ value = arg.validator._default;
679
+ validationData[arg.name] = value;
680
+ handlerArgs[args.indexOf(arg)] = value;
681
+ }
682
+
683
+ try {
684
+ const res = await arg.validator.run(value, arg.name);
685
+ if (res.error)
686
+ throw new Error(res.error);
687
+ } catch (err) {
688
+ throw new Error(`Validation failed for '${arg.name}': ${err.message}`);
689
+ }
690
+ }
691
+
692
+ if (hooks.validate) {
693
+ await hooks.validate.call(this.baseCommand, ...handlerArgs);
694
+ } else if (typeof this.baseCommand.validate === 'function') {
695
+ await this.baseCommand.validate(...handlerArgs);
696
+ }
697
+
698
+ const result = await fn.call(this.baseCommand, ...handlerArgs);
699
+
700
+ if (hooks.after) {
701
+ await hooks.after.call(this.baseCommand, ...handlerArgs);
702
+ } else if (typeof this.baseCommand.after === 'function') {
703
+ await this.baseCommand.after(...handlerArgs);
704
+ }
705
+
706
+ if (process.env.DEBUG) {
707
+ this.baseCommand.info(`Completed in ${Date.now() - startTime}ms`);
708
+ }
709
+
710
+ return result;
711
+ } catch (err) {
712
+ if (hooks.onError) {
713
+ await hooks.onError.call(this.baseCommand, err);
714
+ } else if (typeof this.baseCommand.onError === 'function') {
715
+ await this.baseCommand.onError(err);
716
+ }
717
+
718
+ this.baseCommand.handleError(err);
719
+ }
720
+ });
721
+
722
+ this.lastCommand._finalized = true;
723
+ }
724
+ }
725
+
726
+ module.exports = BaseCommand;