cli-forge 1.1.0 → 1.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/lib/composable-builder.d.ts +5 -1
- package/dist/lib/composable-builder.js +24 -3
- package/dist/lib/composable-builder.js.map +1 -1
- package/dist/lib/interactive-shell.js +2 -0
- package/dist/lib/interactive-shell.js.map +1 -1
- package/dist/lib/internal-cli.d.ts +18 -5
- package/dist/lib/internal-cli.js +161 -36
- package/dist/lib/internal-cli.js.map +1 -1
- package/dist/lib/public-api.d.ts +11 -2
- package/dist/lib/public-api.js.map +1 -1
- package/package.json +2 -2
- package/src/lib/cli-localization.spec.ts +7 -7
- package/src/lib/composable-builder.spec.ts +73 -0
- package/src/lib/composable-builder.ts +26 -5
- package/src/lib/interactive-shell.ts +2 -0
- package/src/lib/internal-cli.spec.ts +822 -3
- package/src/lib/internal-cli.ts +201 -54
- package/src/lib/public-api.ts +25 -10
- package/tsconfig.lib.json.tsbuildinfo +1 -1
package/src/lib/internal-cli.ts
CHANGED
|
@@ -2,6 +2,8 @@
|
|
|
2
2
|
import {
|
|
3
3
|
ArgvParser,
|
|
4
4
|
EnvOptionConfig,
|
|
5
|
+
LocalizationDictionary,
|
|
6
|
+
LocalizationFunction,
|
|
5
7
|
OptionConfig,
|
|
6
8
|
ParsedArgs,
|
|
7
9
|
ValidationFailedError,
|
|
@@ -9,7 +11,8 @@ import {
|
|
|
9
11
|
hideBin,
|
|
10
12
|
type ConfigurationFiles,
|
|
11
13
|
} from '@cli-forge/parser';
|
|
12
|
-
import {
|
|
14
|
+
import { readOptionGroupsForCLI } from './cli-option-groups';
|
|
15
|
+
import { formatHelp } from './format-help';
|
|
13
16
|
import { INTERACTIVE_SHELL, InteractiveShell } from './interactive-shell';
|
|
14
17
|
import {
|
|
15
18
|
CLI,
|
|
@@ -19,8 +22,7 @@ import {
|
|
|
19
22
|
ErrorHandler,
|
|
20
23
|
SDKCommand,
|
|
21
24
|
} from './public-api';
|
|
22
|
-
import {
|
|
23
|
-
import { formatHelp } from './format-help';
|
|
25
|
+
import { getCallingFile, getParentPackageJson } from './utils';
|
|
24
26
|
|
|
25
27
|
/**
|
|
26
28
|
* The base class for a CLI application. This class is used to define the structure of the CLI.
|
|
@@ -83,7 +85,13 @@ export class InternalCLI<
|
|
|
83
85
|
},
|
|
84
86
|
];
|
|
85
87
|
|
|
86
|
-
private registeredMiddleware
|
|
88
|
+
private registeredMiddleware = new Set<
|
|
89
|
+
(args: TArgs) => void | unknown | Promise<void> | Promise<unknown>
|
|
90
|
+
>();
|
|
91
|
+
|
|
92
|
+
private registeredInitHooks: Array<
|
|
93
|
+
(cli: any, args: TArgs) => Promise<void> | void
|
|
94
|
+
> = [];
|
|
87
95
|
|
|
88
96
|
/**
|
|
89
97
|
* A list of option groups that have been registered with the CLI. Grouped Options are displayed together in the help text.
|
|
@@ -167,7 +175,7 @@ export class InternalCLI<
|
|
|
167
175
|
configuration: CLICommandOptions<TArgs, TRootCommandArgs>
|
|
168
176
|
): InternalCLI<TArgs, THandlerReturn, TChildren, TParent> {
|
|
169
177
|
this.configuration = configuration;
|
|
170
|
-
this.requiresCommand = false;
|
|
178
|
+
this.requiresCommand = configuration.handler ? false : 'IMPLICIT';
|
|
171
179
|
return this;
|
|
172
180
|
}
|
|
173
181
|
|
|
@@ -266,18 +274,18 @@ export class InternalCLI<
|
|
|
266
274
|
key
|
|
267
275
|
).withRootCommandConfiguration(options as any);
|
|
268
276
|
cmd._parent = this;
|
|
269
|
-
|
|
277
|
+
|
|
270
278
|
// Get localized command name
|
|
271
279
|
const localizedKey = this.getLocalizedCommandName(key);
|
|
272
|
-
|
|
280
|
+
|
|
273
281
|
// Register under the default key
|
|
274
282
|
this.registeredCommands[key] = cmd;
|
|
275
|
-
|
|
283
|
+
|
|
276
284
|
// If localized name is different, also register under localized name as an alias
|
|
277
285
|
if (localizedKey !== key) {
|
|
278
286
|
this.registeredCommands[localizedKey] = cmd;
|
|
279
287
|
}
|
|
280
|
-
|
|
288
|
+
|
|
281
289
|
if (options.alias) {
|
|
282
290
|
for (const alias of options.alias) {
|
|
283
291
|
this.registeredCommands[alias] = cmd;
|
|
@@ -285,6 +293,10 @@ export class InternalCLI<
|
|
|
285
293
|
}
|
|
286
294
|
} else if (keyOrCommand instanceof InternalCLI) {
|
|
287
295
|
const cmd = keyOrCommand;
|
|
296
|
+
if (cmd.name === '$0') {
|
|
297
|
+
this.withRootCommandConfiguration(cmd.configuration as any);
|
|
298
|
+
return this as any;
|
|
299
|
+
}
|
|
288
300
|
cmd._parent = this;
|
|
289
301
|
this.registeredCommands[cmd.name] = cmd;
|
|
290
302
|
if (cmd.configuration?.alias) {
|
|
@@ -304,17 +316,7 @@ export class InternalCLI<
|
|
|
304
316
|
commands(...a0: Command[] | Command[][]): any {
|
|
305
317
|
const commands = a0.flat();
|
|
306
318
|
for (const val of commands) {
|
|
307
|
-
|
|
308
|
-
val._parent = this;
|
|
309
|
-
this.registeredCommands[val.name] = val;
|
|
310
|
-
// Include any options that were defined via cli(...).option() instead of via builder
|
|
311
|
-
this.parser.augment(val.parser);
|
|
312
|
-
} else {
|
|
313
|
-
const { name, ...configuration } = val as {
|
|
314
|
-
name: string;
|
|
315
|
-
} & CLICommandOptions<any, any>;
|
|
316
|
-
this.command(name, configuration);
|
|
317
|
-
}
|
|
319
|
+
this.command(val);
|
|
318
320
|
}
|
|
319
321
|
return this;
|
|
320
322
|
}
|
|
@@ -367,9 +369,7 @@ export class InternalCLI<
|
|
|
367
369
|
}
|
|
368
370
|
|
|
369
371
|
localize(
|
|
370
|
-
dictionaryOrFn:
|
|
371
|
-
| import('@cli-forge/parser').LocalizationDictionary
|
|
372
|
-
| import('@cli-forge/parser').LocalizationFunction,
|
|
372
|
+
dictionaryOrFn: LocalizationDictionary | LocalizationFunction,
|
|
373
373
|
locale?: string
|
|
374
374
|
): CLI<TArgs, THandlerReturn, TChildren, TParent> {
|
|
375
375
|
if (typeof dictionaryOrFn === 'function') {
|
|
@@ -435,34 +435,48 @@ export class InternalCLI<
|
|
|
435
435
|
}
|
|
436
436
|
|
|
437
437
|
middleware<TArgs2>(
|
|
438
|
-
callback: (args: TArgs) => TArgs2 | Promise<TArgs2>
|
|
438
|
+
callback: (args: TArgs) => TArgs2 | Promise<TArgs2> | void | Promise<void>
|
|
439
439
|
): CLI<
|
|
440
440
|
TArgs2 extends void ? TArgs : TArgs & TArgs2,
|
|
441
441
|
THandlerReturn,
|
|
442
442
|
TChildren,
|
|
443
443
|
TParent
|
|
444
444
|
> {
|
|
445
|
-
this.registeredMiddleware.
|
|
445
|
+
this.registeredMiddleware.add(callback);
|
|
446
446
|
// If middleware returns void, TArgs doesn't change...
|
|
447
447
|
// If it returns something, we need to merge it into TArgs...
|
|
448
448
|
// that's not here though, its where we apply the middleware results.
|
|
449
449
|
return this as any;
|
|
450
450
|
}
|
|
451
451
|
|
|
452
|
+
init(
|
|
453
|
+
callback: (
|
|
454
|
+
cli: CLI<TArgs, THandlerReturn, TChildren, TParent>,
|
|
455
|
+
args: TArgs
|
|
456
|
+
) => Promise<void> | void
|
|
457
|
+
): CLI<TArgs, THandlerReturn, TChildren, TParent> {
|
|
458
|
+
this.registeredInitHooks.push(callback);
|
|
459
|
+
return this as unknown as CLI<TArgs, THandlerReturn, TChildren, TParent>;
|
|
460
|
+
}
|
|
461
|
+
|
|
452
462
|
/**
|
|
453
463
|
* Runs the current command.
|
|
454
464
|
* @param cmd The command to run.
|
|
455
465
|
* @param args The arguments to pass to the command.
|
|
456
466
|
*/
|
|
457
|
-
async runCommand<T extends ParsedArgs>(
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
467
|
+
async runCommand<T extends ParsedArgs>(
|
|
468
|
+
args: T,
|
|
469
|
+
originalArgV: string[],
|
|
470
|
+
executedMiddleware?: Set<(args: any) => void>
|
|
471
|
+
): Promise<T> {
|
|
472
|
+
const middlewares = new Set<(args: any) => void>(this.registeredMiddleware);
|
|
461
473
|
// eslint-disable-next-line @typescript-eslint/no-this-alias
|
|
462
474
|
let cmd: InternalCLI<any, any, any, any> = this;
|
|
463
475
|
for (const command of this.commandChain) {
|
|
464
476
|
cmd = cmd.registeredCommands[command];
|
|
465
|
-
|
|
477
|
+
for (const mw of cmd.registeredMiddleware) {
|
|
478
|
+
middlewares.add(mw);
|
|
479
|
+
}
|
|
466
480
|
}
|
|
467
481
|
try {
|
|
468
482
|
if (cmd.requiresCommand) {
|
|
@@ -472,7 +486,8 @@ export class InternalCLI<
|
|
|
472
486
|
}
|
|
473
487
|
if (cmd.configuration?.handler) {
|
|
474
488
|
for (const middleware of middlewares) {
|
|
475
|
-
|
|
489
|
+
if (executedMiddleware?.has(middleware)) continue;
|
|
490
|
+
const middlewareResult = await middleware(args as any);
|
|
476
491
|
if (
|
|
477
492
|
middlewareResult !== void 0 &&
|
|
478
493
|
typeof middlewareResult === 'object'
|
|
@@ -480,15 +495,25 @@ export class InternalCLI<
|
|
|
480
495
|
args = middlewareResult as T;
|
|
481
496
|
}
|
|
482
497
|
}
|
|
483
|
-
|
|
498
|
+
await cmd.configuration.handler(args, {
|
|
484
499
|
command: cmd as any,
|
|
485
500
|
});
|
|
501
|
+
return args;
|
|
486
502
|
} else {
|
|
487
503
|
// We can treat a command as a subshell if it has subcommands
|
|
488
504
|
if (Object.keys(cmd.registeredCommands).length > 0) {
|
|
489
505
|
if (!process.stdout.isTTY) {
|
|
490
506
|
// If we're not in a TTY, we can't run an interactive shell...
|
|
491
507
|
// Maybe we should warn here?
|
|
508
|
+
} else if (args.unmatched.length > 0) {
|
|
509
|
+
// If there are unmatched args, we don't run an interactive shell...
|
|
510
|
+
// this could represent a user misspelling a subcommand so it gets rather confusing.
|
|
511
|
+
console.warn(
|
|
512
|
+
`Warning: Unrecognized command or arguments: ${args.unmatched.join(
|
|
513
|
+
' '
|
|
514
|
+
)}`
|
|
515
|
+
);
|
|
516
|
+
cmd.printHelp();
|
|
492
517
|
} else if (!INTERACTIVE_SHELL) {
|
|
493
518
|
const tui = new InteractiveShell(
|
|
494
519
|
this as unknown as InternalCLI<any>,
|
|
@@ -519,6 +544,7 @@ export class InternalCLI<
|
|
|
519
544
|
console.error(e);
|
|
520
545
|
this.printHelp();
|
|
521
546
|
}
|
|
547
|
+
return args;
|
|
522
548
|
}
|
|
523
549
|
|
|
524
550
|
getChildren(): TChildren {
|
|
@@ -692,7 +718,13 @@ export class InternalCLI<
|
|
|
692
718
|
chain.unshift(current);
|
|
693
719
|
current = current._parent;
|
|
694
720
|
}
|
|
695
|
-
|
|
721
|
+
const seen = new Set<(args: any) => unknown | Promise<unknown>>();
|
|
722
|
+
for (const c of chain) {
|
|
723
|
+
for (const mw of c.registeredMiddleware) {
|
|
724
|
+
seen.add(mw);
|
|
725
|
+
}
|
|
726
|
+
}
|
|
727
|
+
return [...seen];
|
|
696
728
|
}
|
|
697
729
|
|
|
698
730
|
enableInteractiveShell(): CLI<TArgs, THandlerReturn, TChildren, TParent> {
|
|
@@ -785,32 +817,156 @@ export class InternalCLI<
|
|
|
785
817
|
}
|
|
786
818
|
|
|
787
819
|
/**
|
|
788
|
-
* Parses argv and executes the CLI
|
|
820
|
+
* Parses argv and executes the CLI.
|
|
821
|
+
*
|
|
822
|
+
* Execution proceeds in two phases:
|
|
823
|
+
*
|
|
824
|
+
* **Discovery loop** (per command level): builder → parse (non-strict,
|
|
825
|
+
* no validation) → merge → middleware → init hooks → find next
|
|
826
|
+
* subcommand in unmatched tokens → repeat.
|
|
827
|
+
*
|
|
828
|
+
* **Final parse + execution**: parse (with validation, seeded with
|
|
829
|
+
* accumulated args) → help/version check → handler. Middleware that
|
|
830
|
+
* already ran during discovery is skipped.
|
|
831
|
+
*
|
|
789
832
|
* @param args argv. Defaults to process.argv.slice(2)
|
|
790
833
|
* @returns Promise that resolves when the handler completes.
|
|
791
834
|
*/
|
|
792
835
|
forge = (args: string[] = hideBin(process.argv)) =>
|
|
793
836
|
this.withErrorHandlers(async () => {
|
|
794
|
-
// Parsing the args does two things:
|
|
795
|
-
// - builds argv to pass to handler
|
|
796
|
-
// - fills the command chain + registers commands
|
|
797
837
|
let argv: TArgs & { help?: boolean; version?: boolean };
|
|
798
838
|
let validationFailedError: ValidationFailedError<TArgs> | undefined;
|
|
839
|
+
|
|
840
|
+
// Run root builder (may register options, init hooks, commands)
|
|
841
|
+
this.configuration?.builder?.(this as any);
|
|
842
|
+
|
|
843
|
+
// Merge helper: accumulate defined values without overwriting
|
|
844
|
+
const mergeNew = (target: any, source: any) => {
|
|
845
|
+
for (const [key, value] of Object.entries(source)) {
|
|
846
|
+
if (key !== 'unmatched' && value !== undefined && target[key] === undefined) {
|
|
847
|
+
target[key] = value;
|
|
848
|
+
}
|
|
849
|
+
}
|
|
850
|
+
};
|
|
851
|
+
|
|
852
|
+
// Iterative command discovery with init hooks.
|
|
853
|
+
// Each level: non-strict parse filtered args → run middleware
|
|
854
|
+
// → run init hooks → find next command in unmatched tokens
|
|
855
|
+
// → filter down. Builders stay lazy.
|
|
856
|
+
let currentArgs = [...args];
|
|
857
|
+
// eslint-disable-next-line @typescript-eslint/no-this-alias
|
|
858
|
+
let currentCmd: InternalCLI<any, any, any, any> = this;
|
|
859
|
+
const mergedArgs: any = {};
|
|
860
|
+
const executedMiddleware = new Set<(args: any) => void>();
|
|
861
|
+
|
|
862
|
+
// eslint-disable-next-line no-constant-condition
|
|
863
|
+
while (true) {
|
|
864
|
+
// Non-strict parse to get current arg values for init hooks.
|
|
865
|
+
// Seeded with mergedArgs so required options parsed at earlier
|
|
866
|
+
// levels satisfy validation.
|
|
867
|
+
// The unmatchedParser intercepts subcommand tokens before
|
|
868
|
+
// positional matching can greedily consume them.
|
|
869
|
+
let discoveredCommand: string | null = null;
|
|
870
|
+
const parsed = this.parser
|
|
871
|
+
.clone({
|
|
872
|
+
...this.parser.options,
|
|
873
|
+
unmatchedParser: (arg) => {
|
|
874
|
+
if (!discoveredCommand && !arg.startsWith('-')) {
|
|
875
|
+
const cmd = currentCmd.registeredCommands[arg];
|
|
876
|
+
if (cmd && cmd.configuration) {
|
|
877
|
+
discoveredCommand = arg;
|
|
878
|
+
return true;
|
|
879
|
+
}
|
|
880
|
+
}
|
|
881
|
+
return false;
|
|
882
|
+
},
|
|
883
|
+
strict: false,
|
|
884
|
+
validate: false,
|
|
885
|
+
})
|
|
886
|
+
.parse(currentArgs, mergedArgs) as any;
|
|
887
|
+
|
|
888
|
+
mergeNew(mergedArgs, parsed);
|
|
889
|
+
|
|
890
|
+
// Run middleware for this command level before init hooks,
|
|
891
|
+
// so init hooks can see middleware-transformed args.
|
|
892
|
+
for (const mw of currentCmd.registeredMiddleware) {
|
|
893
|
+
if (!executedMiddleware.has(mw)) {
|
|
894
|
+
executedMiddleware.add(mw);
|
|
895
|
+
const result = await mw(mergedArgs);
|
|
896
|
+
if (result !== void 0 && typeof result === 'object') {
|
|
897
|
+
Object.assign(mergedArgs, result);
|
|
898
|
+
}
|
|
899
|
+
}
|
|
900
|
+
}
|
|
901
|
+
|
|
902
|
+
// Run init hooks with the full accumulated args
|
|
903
|
+
for (const hook of currentCmd.registeredInitHooks) {
|
|
904
|
+
await hook(currentCmd as any, mergedArgs);
|
|
905
|
+
}
|
|
906
|
+
|
|
907
|
+
// Build the next command if one was discovered during parsing
|
|
908
|
+
// (i.e., subcommand token intercepted before positional matching)
|
|
909
|
+
let nextCmd: InternalCLI<any, any, any, any> | null = null;
|
|
910
|
+
if (discoveredCommand) {
|
|
911
|
+
const cmd = currentCmd.registeredCommands[discoveredCommand];
|
|
912
|
+
cmd.parser = this.parser;
|
|
913
|
+
cmd.configuration!.builder?.(cmd as any);
|
|
914
|
+
this.commandChain.push(discoveredCommand);
|
|
915
|
+
nextCmd = cmd;
|
|
916
|
+
}
|
|
917
|
+
|
|
918
|
+
// Scan unmatched tokens for commands that may have been registered
|
|
919
|
+
// dynamically by init hooks (these weren't in registeredCommands
|
|
920
|
+
// during parse, so the unmatchedParser couldn't intercept them).
|
|
921
|
+
const unmatched: string[] = parsed.unmatched ?? [];
|
|
922
|
+
const remainingArgs: string[] = [];
|
|
923
|
+
|
|
924
|
+
if (!nextCmd) {
|
|
925
|
+
for (const token of unmatched) {
|
|
926
|
+
if (!nextCmd && !token.startsWith('-')) {
|
|
927
|
+
const cmd = currentCmd.registeredCommands[token];
|
|
928
|
+
if (cmd && cmd.configuration) {
|
|
929
|
+
cmd.parser = this.parser;
|
|
930
|
+
cmd.configuration.builder?.(cmd as any);
|
|
931
|
+
this.commandChain.push(token);
|
|
932
|
+
nextCmd = cmd;
|
|
933
|
+
continue;
|
|
934
|
+
}
|
|
935
|
+
}
|
|
936
|
+
remainingArgs.push(token);
|
|
937
|
+
}
|
|
938
|
+
}
|
|
939
|
+
|
|
940
|
+
if (!nextCmd) {
|
|
941
|
+
currentArgs = unmatched;
|
|
942
|
+
break;
|
|
943
|
+
}
|
|
944
|
+
// If command found during parse, all unmatched are remaining args.
|
|
945
|
+
// If command found in post-init scan, use filtered remaining args.
|
|
946
|
+
currentArgs = discoveredCommand ? unmatched : remainingArgs;
|
|
947
|
+
currentCmd = nextCmd;
|
|
948
|
+
}
|
|
949
|
+
|
|
950
|
+
// All builders and init hooks have run. The parser now has
|
|
951
|
+
// all options registered. Parse the remaining unmatched tokens
|
|
952
|
+
// seeded with the accumulated values from the discovery loop.
|
|
953
|
+
// The alreadyParsed values ensure proper required-option
|
|
954
|
+
// validation and prevent positional re-consumption.
|
|
799
955
|
try {
|
|
800
|
-
argv = this.parser
|
|
956
|
+
argv = this.parser
|
|
957
|
+
.clone({
|
|
958
|
+
...this.parser.options,
|
|
959
|
+
unmatchedParser: () => false,
|
|
960
|
+
})
|
|
961
|
+
.parse(currentArgs, mergedArgs) as any;
|
|
801
962
|
} catch (e) {
|
|
802
963
|
if (e instanceof ValidationFailedError) {
|
|
803
|
-
argv = e.partialArgV as
|
|
964
|
+
argv = e.partialArgV as any;
|
|
804
965
|
validationFailedError = e;
|
|
805
966
|
} else {
|
|
806
967
|
throw e;
|
|
807
968
|
}
|
|
808
969
|
}
|
|
809
|
-
// eslint-disable-next-line @typescript-eslint/no-this-alias
|
|
810
|
-
let currentCommand: InternalCLI<any, any, any, any> = this;
|
|
811
|
-
for (const command of this.commandChain) {
|
|
812
|
-
currentCommand = currentCommand.registeredCommands[command];
|
|
813
|
-
}
|
|
814
970
|
|
|
815
971
|
if (argv.version) {
|
|
816
972
|
this.versionHandler();
|
|
@@ -824,16 +980,7 @@ export class InternalCLI<
|
|
|
824
980
|
throw validationFailedError;
|
|
825
981
|
}
|
|
826
982
|
|
|
827
|
-
const finalArgV =
|
|
828
|
-
this.commandChain.length === 0 && this.configuration?.builder
|
|
829
|
-
? (
|
|
830
|
-
this.configuration.builder?.(
|
|
831
|
-
this as any
|
|
832
|
-
) as unknown as InternalCLI<TArgs, any, any, any>
|
|
833
|
-
).parser.parse(args)
|
|
834
|
-
: argv;
|
|
835
|
-
|
|
836
|
-
await this.runCommand(finalArgV, args);
|
|
983
|
+
const finalArgV = await this.runCommand(argv, args, executedMiddleware);
|
|
837
984
|
return finalArgV as TArgs;
|
|
838
985
|
});
|
|
839
986
|
|
package/src/lib/public-api.ts
CHANGED
|
@@ -1,20 +1,20 @@
|
|
|
1
1
|
/* eslint-disable @typescript-eslint/ban-types */
|
|
2
2
|
import {
|
|
3
|
+
ArrayOptionConfig,
|
|
4
|
+
BooleanOptionConfig,
|
|
3
5
|
type ConfigurationFiles,
|
|
6
|
+
EnvOptionConfig,
|
|
7
|
+
LocalizationDictionary,
|
|
8
|
+
LocalizationFunction,
|
|
9
|
+
MakeUndefinedPropertiesOptional,
|
|
10
|
+
NumberOptionConfig,
|
|
11
|
+
ObjectOptionConfig,
|
|
4
12
|
OptionConfig,
|
|
5
13
|
OptionConfigToType,
|
|
6
14
|
ParsedArgs,
|
|
7
|
-
EnvOptionConfig,
|
|
8
|
-
ObjectOptionConfig,
|
|
9
|
-
StringOptionConfig,
|
|
10
|
-
NumberOptionConfig,
|
|
11
|
-
BooleanOptionConfig,
|
|
12
|
-
ArrayOptionConfig,
|
|
13
15
|
ResolveProperties,
|
|
16
|
+
StringOptionConfig,
|
|
14
17
|
WithOptional,
|
|
15
|
-
MakeUndefinedPropertiesOptional,
|
|
16
|
-
LocalizationDictionary,
|
|
17
|
-
LocalizationFunction,
|
|
18
18
|
} from '@cli-forge/parser';
|
|
19
19
|
|
|
20
20
|
import { InternalCLI } from './internal-cli';
|
|
@@ -781,6 +781,21 @@ export interface CLI<
|
|
|
781
781
|
TParent
|
|
782
782
|
>;
|
|
783
783
|
|
|
784
|
+
/**
|
|
785
|
+
* Registers an init hook that runs before command resolution.
|
|
786
|
+
* Init hooks receive partially-parsed args (from currently-registered options)
|
|
787
|
+
* and can modify the CLI (register commands, options, middleware) before the
|
|
788
|
+
* full parse runs. This enables plugin loading from config files.
|
|
789
|
+
*
|
|
790
|
+
* @param callback Async function receiving (args, cli). Mutate cli to add commands/options.
|
|
791
|
+
*/
|
|
792
|
+
init(
|
|
793
|
+
callback: (
|
|
794
|
+
cli: CLI<TArgs, THandlerReturn, TChildren, TParent>,
|
|
795
|
+
args: TArgs
|
|
796
|
+
) => Promise<void> | void
|
|
797
|
+
): CLI<TArgs, THandlerReturn, TChildren, TParent>;
|
|
798
|
+
|
|
784
799
|
/**
|
|
785
800
|
* Parses argv and executes the CLI
|
|
786
801
|
* @param args argv. Defaults to process.argv.slice(2)
|
|
@@ -909,7 +924,7 @@ export interface CLICommandOptions<
|
|
|
909
924
|
* The type of the arguments that are registered after `builder` is invoked, and the type that is passed to the handler.
|
|
910
925
|
*/
|
|
911
926
|
TArgs extends TInitial = TInitial,
|
|
912
|
-
THandlerReturn = void
|
|
927
|
+
THandlerReturn = void | Promise<void>,
|
|
913
928
|
/**
|
|
914
929
|
* The children commands that exist before the builder runs.
|
|
915
930
|
*/
|