cli-forge 0.11.0 → 1.0.0
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/.eslintrc.json +1 -0
- package/README.md +181 -5
- package/dist/bin/cli.d.ts +18 -1
- package/dist/bin/commands/generate-documentation.d.ts +8 -8
- package/dist/bin/commands/generate-documentation.js.map +1 -1
- package/dist/bin/commands/init.d.ts +10 -10
- package/dist/index.d.ts +1 -0
- package/dist/index.js.map +1 -1
- package/dist/lib/composable-builder.d.ts +22 -1
- package/dist/lib/composable-builder.js +11 -1
- package/dist/lib/composable-builder.js.map +1 -1
- package/dist/lib/documentation.js.map +1 -1
- package/dist/lib/internal-cli.d.ts +39 -22
- package/dist/lib/internal-cli.js +137 -2
- package/dist/lib/internal-cli.js.map +1 -1
- package/dist/lib/public-api.d.ts +221 -58
- package/dist/lib/public-api.js.map +1 -1
- package/package.json +8 -3
- package/src/bin/commands/generate-documentation.ts +1 -1
- package/src/index.ts +5 -0
- package/src/lib/composable-builder.ts +52 -4
- package/src/lib/documentation.spec.ts +1 -0
- package/src/lib/documentation.ts +1 -1
- package/src/lib/internal-cli.spec.ts +6 -5
- package/src/lib/internal-cli.ts +335 -53
- package/src/lib/public-api.ts +678 -92
- package/src/lib/sdk.spec.ts +285 -0
- package/src/lib/test-harness.spec.ts +1 -0
- package/src/lib/utils.spec.ts +1 -0
- package/tsconfig.lib.json.tsbuildinfo +1 -1
package/src/lib/internal-cli.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
/* eslint-disable @typescript-eslint/ban-types */
|
|
1
2
|
import {
|
|
2
3
|
ArgvParser,
|
|
3
4
|
EnvOptionConfig,
|
|
@@ -10,7 +11,14 @@ import {
|
|
|
10
11
|
} from '@cli-forge/parser';
|
|
11
12
|
import { getCallingFile, getParentPackageJson } from './utils';
|
|
12
13
|
import { INTERACTIVE_SHELL, InteractiveShell } from './interactive-shell';
|
|
13
|
-
import {
|
|
14
|
+
import {
|
|
15
|
+
CLI,
|
|
16
|
+
CLICommandOptions,
|
|
17
|
+
CLIHandlerContext,
|
|
18
|
+
Command,
|
|
19
|
+
ErrorHandler,
|
|
20
|
+
SDKCommand,
|
|
21
|
+
} from './public-api';
|
|
14
22
|
import { readOptionGroupsForCLI } from './cli-option-groups';
|
|
15
23
|
import { formatHelp } from './format-help';
|
|
16
24
|
|
|
@@ -33,19 +41,30 @@ import { formatHelp } from './format-help';
|
|
|
33
41
|
* }).forge();
|
|
34
42
|
* ```
|
|
35
43
|
*/
|
|
36
|
-
export class InternalCLI<
|
|
37
|
-
|
|
44
|
+
export class InternalCLI<
|
|
45
|
+
TArgs extends ParsedArgs = ParsedArgs,
|
|
46
|
+
THandlerReturn = void,
|
|
47
|
+
// eslint-disable-next-line @typescript-eslint/ban-types
|
|
48
|
+
TChildren = {},
|
|
49
|
+
TParent = undefined
|
|
50
|
+
> implements CLI<TArgs, THandlerReturn, TChildren, TParent>
|
|
38
51
|
{
|
|
39
52
|
/**
|
|
40
53
|
* For internal use only. Stick to properties available on {@link CLI}.
|
|
41
54
|
*/
|
|
42
|
-
registeredCommands: Record<string, InternalCLI<any>> = {};
|
|
55
|
+
registeredCommands: Record<string, InternalCLI<any, any, any, any>> = {};
|
|
43
56
|
|
|
44
57
|
/**
|
|
45
58
|
* For internal use only. Stick to properties available on {@link CLI}.
|
|
46
59
|
*/
|
|
47
60
|
commandChain: string[] = [];
|
|
48
61
|
|
|
62
|
+
/**
|
|
63
|
+
* Reference to the parent CLI instance, if this command was registered as a subcommand.
|
|
64
|
+
* For internal use only. Use `getParent()` instead.
|
|
65
|
+
*/
|
|
66
|
+
private _parent?: InternalCLI<any, any, any, any>;
|
|
67
|
+
|
|
49
68
|
private requiresCommand: 'IMPLICIT' | 'EXPLICIT' | false = 'IMPLICIT';
|
|
50
69
|
|
|
51
70
|
private _configuration?: CLICommandOptions<any, any>;
|
|
@@ -100,14 +119,14 @@ export class InternalCLI<TArgs extends ParsedArgs = ParsedArgs>
|
|
|
100
119
|
parser = new ArgvParser<TArgs>({
|
|
101
120
|
unmatchedParser: (arg) => {
|
|
102
121
|
// eslint-disable-next-line @typescript-eslint/no-this-alias
|
|
103
|
-
let currentCommand: InternalCLI<any> = this;
|
|
122
|
+
let currentCommand: InternalCLI<any, any, any, any> = this;
|
|
104
123
|
for (const command of this.commandChain) {
|
|
105
124
|
currentCommand = currentCommand.registeredCommands[command];
|
|
106
125
|
}
|
|
107
126
|
const command = currentCommand.registeredCommands[arg];
|
|
108
127
|
if (command && command.configuration) {
|
|
109
128
|
command.parser = this.parser;
|
|
110
|
-
command.configuration.builder?.(command);
|
|
129
|
+
command.configuration.builder?.(command as any);
|
|
111
130
|
this.commandChain.push(arg);
|
|
112
131
|
return true;
|
|
113
132
|
}
|
|
@@ -130,10 +149,15 @@ export class InternalCLI<TArgs extends ParsedArgs = ParsedArgs>
|
|
|
130
149
|
*/
|
|
131
150
|
constructor(
|
|
132
151
|
public name: string,
|
|
133
|
-
rootCommandConfiguration?: CLICommandOptions<
|
|
152
|
+
rootCommandConfiguration?: CLICommandOptions<
|
|
153
|
+
TArgs,
|
|
154
|
+
any,
|
|
155
|
+
THandlerReturn,
|
|
156
|
+
TChildren
|
|
157
|
+
>
|
|
134
158
|
) {
|
|
135
159
|
if (rootCommandConfiguration) {
|
|
136
|
-
this.withRootCommandConfiguration(rootCommandConfiguration);
|
|
160
|
+
this.withRootCommandConfiguration(rootCommandConfiguration as any);
|
|
137
161
|
} else {
|
|
138
162
|
this.requiresCommand = 'IMPLICIT';
|
|
139
163
|
}
|
|
@@ -141,16 +165,87 @@ export class InternalCLI<TArgs extends ParsedArgs = ParsedArgs>
|
|
|
141
165
|
|
|
142
166
|
withRootCommandConfiguration<TRootCommandArgs extends TArgs>(
|
|
143
167
|
configuration: CLICommandOptions<TArgs, TRootCommandArgs>
|
|
144
|
-
): InternalCLI<TArgs> {
|
|
168
|
+
): InternalCLI<TArgs, THandlerReturn, TChildren, TParent> {
|
|
145
169
|
this.configuration = configuration;
|
|
146
170
|
this.requiresCommand = false;
|
|
147
171
|
return this;
|
|
148
172
|
}
|
|
149
173
|
|
|
150
174
|
command<TCommandArgs extends TArgs>(
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
175
|
+
cmd: Command<TArgs, TCommandArgs>
|
|
176
|
+
): CLI<
|
|
177
|
+
TArgs,
|
|
178
|
+
THandlerReturn,
|
|
179
|
+
TChildren &
|
|
180
|
+
(typeof cmd extends Command<TArgs, infer TCmdArgs, infer TCmdName>
|
|
181
|
+
? {
|
|
182
|
+
[key in TCmdName]: CLI<
|
|
183
|
+
TCmdArgs,
|
|
184
|
+
void,
|
|
185
|
+
{},
|
|
186
|
+
CLI<TArgs, THandlerReturn, TChildren, TParent>
|
|
187
|
+
>;
|
|
188
|
+
}
|
|
189
|
+
: {}),
|
|
190
|
+
TParent
|
|
191
|
+
>;
|
|
192
|
+
command<
|
|
193
|
+
TCommandArgs extends TArgs,
|
|
194
|
+
TChildHandlerReturn = void,
|
|
195
|
+
TCommandName extends string = string
|
|
196
|
+
>(
|
|
197
|
+
key: TCommandName,
|
|
198
|
+
options: CLICommandOptions<TArgs, TCommandArgs, TChildHandlerReturn>
|
|
199
|
+
): CLI<
|
|
200
|
+
TArgs,
|
|
201
|
+
THandlerReturn,
|
|
202
|
+
TChildren & {
|
|
203
|
+
[key in TCommandName]: CLI<
|
|
204
|
+
TCommandArgs,
|
|
205
|
+
TChildHandlerReturn,
|
|
206
|
+
{},
|
|
207
|
+
CLI<TArgs, THandlerReturn, TChildren, TParent>
|
|
208
|
+
>;
|
|
209
|
+
},
|
|
210
|
+
TParent
|
|
211
|
+
>;
|
|
212
|
+
command<
|
|
213
|
+
TCommandArgs extends TArgs,
|
|
214
|
+
TChildHandlerReturn = void,
|
|
215
|
+
TCommandName extends string = string
|
|
216
|
+
>(
|
|
217
|
+
keyOrCommand: TCommandName | Command<TArgs, TCommandArgs>,
|
|
218
|
+
options?: CLICommandOptions<TArgs, TCommandArgs, TChildHandlerReturn>
|
|
219
|
+
): CLI<
|
|
220
|
+
TArgs,
|
|
221
|
+
THandlerReturn,
|
|
222
|
+
TChildren &
|
|
223
|
+
(typeof keyOrCommand extends string
|
|
224
|
+
? {
|
|
225
|
+
[key in typeof keyOrCommand]: CLI<
|
|
226
|
+
TCommandArgs,
|
|
227
|
+
TChildHandlerReturn,
|
|
228
|
+
{},
|
|
229
|
+
CLI<TArgs, THandlerReturn, TChildren, TParent>
|
|
230
|
+
>;
|
|
231
|
+
}
|
|
232
|
+
: typeof keyOrCommand extends Command<
|
|
233
|
+
TArgs,
|
|
234
|
+
infer TCmdArgs,
|
|
235
|
+
infer TCmdName
|
|
236
|
+
>
|
|
237
|
+
? {
|
|
238
|
+
[key in TCmdName]: CLI<
|
|
239
|
+
TCmdArgs,
|
|
240
|
+
void,
|
|
241
|
+
{},
|
|
242
|
+
CLI<TArgs, THandlerReturn, TChildren, TParent>
|
|
243
|
+
>;
|
|
244
|
+
}
|
|
245
|
+
: // eslint-disable-next-line @typescript-eslint/ban-types
|
|
246
|
+
{}),
|
|
247
|
+
TParent
|
|
248
|
+
> {
|
|
154
249
|
if (typeof keyOrCommand === 'string') {
|
|
155
250
|
const key = keyOrCommand;
|
|
156
251
|
if (!options) {
|
|
@@ -166,9 +261,10 @@ export class InternalCLI<TArgs extends ParsedArgs = ParsedArgs>
|
|
|
166
261
|
description: options.description,
|
|
167
262
|
});
|
|
168
263
|
}
|
|
169
|
-
const cmd = new InternalCLI<TArgs>(
|
|
170
|
-
|
|
171
|
-
);
|
|
264
|
+
const cmd = new InternalCLI<TArgs, TChildHandlerReturn>(
|
|
265
|
+
key
|
|
266
|
+
).withRootCommandConfiguration(options as any);
|
|
267
|
+
cmd._parent = this;
|
|
172
268
|
this.registeredCommands[key] = cmd;
|
|
173
269
|
if (options.alias) {
|
|
174
270
|
for (const alias of options.alias) {
|
|
@@ -177,6 +273,7 @@ export class InternalCLI<TArgs extends ParsedArgs = ParsedArgs>
|
|
|
177
273
|
}
|
|
178
274
|
} else if (keyOrCommand instanceof InternalCLI) {
|
|
179
275
|
const cmd = keyOrCommand;
|
|
276
|
+
cmd._parent = this;
|
|
180
277
|
this.registeredCommands[cmd.name] = cmd;
|
|
181
278
|
if (cmd.configuration?.alias) {
|
|
182
279
|
for (const alias of cmd.configuration.alias) {
|
|
@@ -189,13 +286,14 @@ export class InternalCLI<TArgs extends ParsedArgs = ParsedArgs>
|
|
|
189
286
|
} & CLICommandOptions<TArgs, TCommandArgs>;
|
|
190
287
|
this.command<TCommandArgs>(name, configuration);
|
|
191
288
|
}
|
|
192
|
-
return this;
|
|
289
|
+
return this as any;
|
|
193
290
|
}
|
|
194
291
|
|
|
195
|
-
commands(...a0: Command[] | Command[][]):
|
|
292
|
+
commands(...a0: Command[] | Command[][]): any {
|
|
196
293
|
const commands = a0.flat();
|
|
197
294
|
for (const val of commands) {
|
|
198
295
|
if (val instanceof InternalCLI) {
|
|
296
|
+
val._parent = this;
|
|
199
297
|
this.registeredCommands[val.name] = val;
|
|
200
298
|
// Include any options that were defined via cli(...).option() instead of via builder
|
|
201
299
|
this.parser.augment(val.parser);
|
|
@@ -211,7 +309,7 @@ export class InternalCLI<TArgs extends ParsedArgs = ParsedArgs>
|
|
|
211
309
|
|
|
212
310
|
option<
|
|
213
311
|
TOption extends string,
|
|
214
|
-
const TOptionConfig extends OptionConfig<any, any, any
|
|
312
|
+
const TOptionConfig extends OptionConfig<any, any, any>
|
|
215
313
|
>(name: TOption, config: TOptionConfig) {
|
|
216
314
|
this.parser.option(name, config);
|
|
217
315
|
// Interface modifies the return type to reflect new params, cast is necessay.... I think 🤔
|
|
@@ -220,58 +318,65 @@ export class InternalCLI<TArgs extends ParsedArgs = ParsedArgs>
|
|
|
220
318
|
|
|
221
319
|
positional<
|
|
222
320
|
TOption extends string,
|
|
223
|
-
const TOptionConfig extends OptionConfig<any, any, any
|
|
321
|
+
const TOptionConfig extends OptionConfig<any, any, any>
|
|
224
322
|
>(name: TOption, config: TOptionConfig) {
|
|
225
323
|
this.parser.positional(name, config);
|
|
226
324
|
// Interface modifies the return type to reflect new params, cast is necessay.... I think 🤔
|
|
227
325
|
return this as any;
|
|
228
326
|
}
|
|
229
327
|
|
|
230
|
-
conflicts(
|
|
328
|
+
conflicts(
|
|
329
|
+
...args: [string, string, ...string[]]
|
|
330
|
+
): CLI<TArgs, THandlerReturn, TChildren, TParent> {
|
|
231
331
|
this.parser.conflicts(...args);
|
|
232
|
-
return this
|
|
332
|
+
return this as unknown as CLI<TArgs, THandlerReturn, TChildren, TParent>;
|
|
233
333
|
}
|
|
234
334
|
|
|
235
|
-
implies(
|
|
335
|
+
implies(
|
|
336
|
+
option: string,
|
|
337
|
+
...impliedOptions: string[]
|
|
338
|
+
): CLI<TArgs, THandlerReturn, TChildren, TParent> {
|
|
236
339
|
this.parser.implies(option, ...impliedOptions);
|
|
237
|
-
return this
|
|
340
|
+
return this as unknown as CLI<TArgs, THandlerReturn, TChildren, TParent>;
|
|
238
341
|
}
|
|
239
342
|
|
|
240
343
|
env(
|
|
241
344
|
a0: string | EnvOptionConfig | undefined = fromCamelOrDashedCaseToConstCase(
|
|
242
345
|
this.name
|
|
243
346
|
)
|
|
244
|
-
) {
|
|
347
|
+
): CLI<TArgs, THandlerReturn, TChildren, TParent> {
|
|
245
348
|
if (typeof a0 === 'string') {
|
|
246
349
|
this.parser.env(a0);
|
|
247
350
|
} else {
|
|
248
351
|
a0.prefix ??= fromCamelOrDashedCaseToConstCase(this.name);
|
|
249
352
|
this.parser.env(a0);
|
|
250
353
|
}
|
|
251
|
-
return this
|
|
354
|
+
return this as unknown as CLI<TArgs, THandlerReturn, TChildren, TParent>;
|
|
252
355
|
}
|
|
253
356
|
|
|
254
|
-
demandCommand() {
|
|
357
|
+
demandCommand(): CLI<TArgs, THandlerReturn, TChildren, TParent> {
|
|
255
358
|
this.requiresCommand = 'EXPLICIT';
|
|
256
|
-
return this
|
|
359
|
+
return this as unknown as CLI<TArgs, THandlerReturn, TChildren, TParent>;
|
|
257
360
|
}
|
|
258
361
|
|
|
259
|
-
usage(usageText: string) {
|
|
362
|
+
usage(usageText: string): CLI<TArgs, THandlerReturn, TChildren, TParent> {
|
|
260
363
|
this.configuration ??= {};
|
|
261
364
|
this.configuration.usage = usageText;
|
|
262
|
-
return this
|
|
365
|
+
return this as unknown as CLI<TArgs, THandlerReturn, TChildren, TParent>;
|
|
263
366
|
}
|
|
264
367
|
|
|
265
|
-
examples(
|
|
368
|
+
examples(
|
|
369
|
+
...examples: string[]
|
|
370
|
+
): CLI<TArgs, THandlerReturn, TChildren, TParent> {
|
|
266
371
|
this.configuration ??= {};
|
|
267
372
|
this.configuration.examples ??= [];
|
|
268
373
|
this.configuration.examples.push(...examples);
|
|
269
|
-
return this
|
|
374
|
+
return this as unknown as CLI<TArgs, THandlerReturn, TChildren, TParent>;
|
|
270
375
|
}
|
|
271
376
|
|
|
272
|
-
version(version?: string) {
|
|
377
|
+
version(version?: string): CLI<TArgs, THandlerReturn, TChildren, TParent> {
|
|
273
378
|
this._versionOverride = version;
|
|
274
|
-
return this
|
|
379
|
+
return this as unknown as CLI<TArgs, THandlerReturn, TChildren, TParent>;
|
|
275
380
|
}
|
|
276
381
|
|
|
277
382
|
/**
|
|
@@ -291,7 +396,12 @@ export class InternalCLI<TArgs extends ParsedArgs = ParsedArgs>
|
|
|
291
396
|
|
|
292
397
|
middleware<TArgs2>(
|
|
293
398
|
callback: (args: TArgs) => TArgs2 | Promise<TArgs2>
|
|
294
|
-
): CLI<
|
|
399
|
+
): CLI<
|
|
400
|
+
TArgs2 extends void ? TArgs : TArgs & TArgs2,
|
|
401
|
+
THandlerReturn,
|
|
402
|
+
TChildren,
|
|
403
|
+
TParent
|
|
404
|
+
> {
|
|
295
405
|
this.registeredMiddleware.push(callback);
|
|
296
406
|
// If middleware returns void, TArgs doesn't change...
|
|
297
407
|
// If it returns something, we need to merge it into TArgs...
|
|
@@ -309,7 +419,7 @@ export class InternalCLI<TArgs extends ParsedArgs = ParsedArgs>
|
|
|
309
419
|
...this.registeredMiddleware,
|
|
310
420
|
];
|
|
311
421
|
// eslint-disable-next-line @typescript-eslint/no-this-alias
|
|
312
|
-
let cmd: InternalCLI<any> = this;
|
|
422
|
+
let cmd: InternalCLI<any, any, any, any> = this;
|
|
313
423
|
for (const command of this.commandChain) {
|
|
314
424
|
cmd = cmd.registeredCommands[command];
|
|
315
425
|
middlewares.push(...cmd.registeredMiddleware);
|
|
@@ -330,8 +440,8 @@ export class InternalCLI<TArgs extends ParsedArgs = ParsedArgs>
|
|
|
330
440
|
args = middlewareResult as T;
|
|
331
441
|
}
|
|
332
442
|
}
|
|
333
|
-
|
|
334
|
-
command: cmd,
|
|
443
|
+
return cmd.configuration.handler(args, {
|
|
444
|
+
command: cmd as any,
|
|
335
445
|
});
|
|
336
446
|
} else {
|
|
337
447
|
// We can treat a command as a subshell if it has subcommands
|
|
@@ -340,9 +450,12 @@ export class InternalCLI<TArgs extends ParsedArgs = ParsedArgs>
|
|
|
340
450
|
// If we're not in a TTY, we can't run an interactive shell...
|
|
341
451
|
// Maybe we should warn here?
|
|
342
452
|
} else if (!INTERACTIVE_SHELL) {
|
|
343
|
-
const tui = new InteractiveShell(
|
|
344
|
-
|
|
345
|
-
|
|
453
|
+
const tui = new InteractiveShell(
|
|
454
|
+
this as unknown as InternalCLI<any>,
|
|
455
|
+
{
|
|
456
|
+
prependArgs: originalArgV,
|
|
457
|
+
}
|
|
458
|
+
);
|
|
346
459
|
await new Promise<void>((res) => {
|
|
347
460
|
['SIGINT', 'SIGTERM', 'SIGQUIT'].forEach((s) =>
|
|
348
461
|
process.on(s, () => {
|
|
@@ -368,7 +481,171 @@ export class InternalCLI<TArgs extends ParsedArgs = ParsedArgs>
|
|
|
368
481
|
}
|
|
369
482
|
}
|
|
370
483
|
|
|
371
|
-
|
|
484
|
+
getChildren(): TChildren {
|
|
485
|
+
// Return a copy of registered commands, excluding aliases (same command registered under different keys)
|
|
486
|
+
const children: Record<string, InternalCLI<any, any, any, any>> = {};
|
|
487
|
+
const seen = new Set<InternalCLI<any, any, any, any>>();
|
|
488
|
+
for (const [key, cmd] of Object.entries(this.registeredCommands)) {
|
|
489
|
+
if (!seen.has(cmd)) {
|
|
490
|
+
seen.add(cmd);
|
|
491
|
+
children[key] = cmd;
|
|
492
|
+
}
|
|
493
|
+
}
|
|
494
|
+
return children as TChildren;
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
getParent(): TParent {
|
|
498
|
+
return this._parent as TParent;
|
|
499
|
+
}
|
|
500
|
+
|
|
501
|
+
getBuilder():
|
|
502
|
+
| (<TInit extends ParsedArgs, TInitHandlerReturn, TInitChildren, TInitParent>(
|
|
503
|
+
parser: CLI<TInit, TInitHandlerReturn, TInitChildren, TInitParent>
|
|
504
|
+
) => CLI<TInit & TArgs, TInitHandlerReturn, TInitChildren & TChildren, TInitParent>)
|
|
505
|
+
| undefined {
|
|
506
|
+
const builder = this.configuration?.builder;
|
|
507
|
+
if (!builder) return undefined;
|
|
508
|
+
// Return a composable builder that preserves input types
|
|
509
|
+
return ((parser: CLI<any, any, any, any>) => builder(parser)) as any;
|
|
510
|
+
}
|
|
511
|
+
|
|
512
|
+
getHandler():
|
|
513
|
+
| ((args: Omit<TArgs, keyof ParsedArgs>) => THandlerReturn)
|
|
514
|
+
| undefined {
|
|
515
|
+
const context: CLIHandlerContext<TChildren, TParent> = {
|
|
516
|
+
command: this as unknown as CLI<any, any, TChildren, TParent>,
|
|
517
|
+
};
|
|
518
|
+
const handler = this._configuration?.handler;
|
|
519
|
+
if (!handler) {
|
|
520
|
+
return undefined;
|
|
521
|
+
}
|
|
522
|
+
return (args: Omit<TArgs, keyof ParsedArgs>) =>
|
|
523
|
+
handler(
|
|
524
|
+
args as TArgs,
|
|
525
|
+
context as CLIHandlerContext<any, any>
|
|
526
|
+
) as THandlerReturn;
|
|
527
|
+
}
|
|
528
|
+
|
|
529
|
+
sdk(): SDKCommand<TArgs, THandlerReturn, TChildren> {
|
|
530
|
+
return this.buildSDKProxy(this) as SDKCommand<
|
|
531
|
+
TArgs,
|
|
532
|
+
THandlerReturn,
|
|
533
|
+
TChildren
|
|
534
|
+
>;
|
|
535
|
+
}
|
|
536
|
+
|
|
537
|
+
private buildSDKProxy(targetCmd: InternalCLI<any, any, any, any>): unknown {
|
|
538
|
+
// eslint-disable-next-line @typescript-eslint/no-this-alias
|
|
539
|
+
const self = this;
|
|
540
|
+
|
|
541
|
+
const invoke = async (
|
|
542
|
+
argsOrArgv?: Record<string, unknown> | string[]
|
|
543
|
+
): Promise<THandlerReturn & { $args?: TArgs }> => {
|
|
544
|
+
// Clone the target command to avoid mutating the original
|
|
545
|
+
const cmd = targetCmd.clone();
|
|
546
|
+
|
|
547
|
+
const handler = cmd._configuration?.handler;
|
|
548
|
+
if (!handler) {
|
|
549
|
+
throw new Error(`Command '${cmd.name}' has no handler`);
|
|
550
|
+
}
|
|
551
|
+
|
|
552
|
+
let parsedArgs: any;
|
|
553
|
+
|
|
554
|
+
if (Array.isArray(argsOrArgv)) {
|
|
555
|
+
// String array: full pipeline (parse → validate → middleware)
|
|
556
|
+
// Run the builder first if present
|
|
557
|
+
if (cmd._configuration?.builder) {
|
|
558
|
+
cmd._configuration.builder(cmd as any);
|
|
559
|
+
}
|
|
560
|
+
parsedArgs = cmd.parser.parse(argsOrArgv);
|
|
561
|
+
} else {
|
|
562
|
+
// Object args: skip validation, apply defaults, run middleware
|
|
563
|
+
// Run the builder first to register options and get defaults
|
|
564
|
+
if (cmd._configuration?.builder) {
|
|
565
|
+
cmd._configuration.builder(cmd as any);
|
|
566
|
+
}
|
|
567
|
+
// Build defaults from configured options
|
|
568
|
+
const defaults: Record<string, unknown> = {};
|
|
569
|
+
for (const [key, config] of Object.entries(
|
|
570
|
+
cmd.parser.configuredOptions
|
|
571
|
+
)) {
|
|
572
|
+
if (config.default !== undefined) {
|
|
573
|
+
defaults[key] = config.default;
|
|
574
|
+
}
|
|
575
|
+
}
|
|
576
|
+
parsedArgs = {
|
|
577
|
+
...defaults,
|
|
578
|
+
...argsOrArgv,
|
|
579
|
+
unmatched: [],
|
|
580
|
+
};
|
|
581
|
+
}
|
|
582
|
+
|
|
583
|
+
// Collect and run middleware from the command chain
|
|
584
|
+
const middlewares = self.collectMiddlewareChain(targetCmd);
|
|
585
|
+
for (const mw of middlewares) {
|
|
586
|
+
const middlewareResult = await mw(parsedArgs);
|
|
587
|
+
if (
|
|
588
|
+
middlewareResult !== void 0 &&
|
|
589
|
+
typeof middlewareResult === 'object'
|
|
590
|
+
) {
|
|
591
|
+
parsedArgs = middlewareResult;
|
|
592
|
+
}
|
|
593
|
+
}
|
|
594
|
+
|
|
595
|
+
// Execute handler
|
|
596
|
+
const context: CLIHandlerContext<any, any> = {
|
|
597
|
+
command: cmd as unknown as CLI<any, any, any, any>,
|
|
598
|
+
};
|
|
599
|
+
const result = await handler(parsedArgs, context);
|
|
600
|
+
|
|
601
|
+
// Try to attach $args to the result (fails silently for primitives)
|
|
602
|
+
if (result !== null && typeof result === 'object') {
|
|
603
|
+
try {
|
|
604
|
+
(result as any).$args = parsedArgs;
|
|
605
|
+
} catch {
|
|
606
|
+
// Cannot attach to frozen objects or primitives, return as-is
|
|
607
|
+
}
|
|
608
|
+
}
|
|
609
|
+
|
|
610
|
+
return result as any as THandlerReturn & { $args?: TArgs };
|
|
611
|
+
};
|
|
612
|
+
|
|
613
|
+
// Ensure builder has run to register all subcommands
|
|
614
|
+
if (targetCmd._configuration?.builder) {
|
|
615
|
+
targetCmd._configuration.builder(targetCmd as any);
|
|
616
|
+
}
|
|
617
|
+
|
|
618
|
+
// Create proxy that is both callable and has child properties
|
|
619
|
+
return new Proxy(invoke, {
|
|
620
|
+
get(_, prop: string) {
|
|
621
|
+
// Handle special properties
|
|
622
|
+
if (prop === 'then' || prop === 'catch' || prop === 'finally') {
|
|
623
|
+
// Don't intercept Promise methods - this prevents issues with await
|
|
624
|
+
return undefined;
|
|
625
|
+
}
|
|
626
|
+
|
|
627
|
+
const child = targetCmd.registeredCommands[prop];
|
|
628
|
+
if (child) {
|
|
629
|
+
return self.buildSDKProxy(child);
|
|
630
|
+
}
|
|
631
|
+
return undefined;
|
|
632
|
+
},
|
|
633
|
+
});
|
|
634
|
+
}
|
|
635
|
+
|
|
636
|
+
private collectMiddlewareChain(
|
|
637
|
+
cmd: InternalCLI<any, any, any, any>
|
|
638
|
+
): Array<(args: any) => unknown | Promise<unknown>> {
|
|
639
|
+
const chain: InternalCLI<any, any, any, any>[] = [];
|
|
640
|
+
let current: InternalCLI<any, any, any, any> | undefined = cmd;
|
|
641
|
+
while (current) {
|
|
642
|
+
chain.unshift(current);
|
|
643
|
+
current = current._parent;
|
|
644
|
+
}
|
|
645
|
+
return chain.flatMap((c) => c.registeredMiddleware);
|
|
646
|
+
}
|
|
647
|
+
|
|
648
|
+
enableInteractiveShell(): CLI<TArgs, THandlerReturn, TChildren, TParent> {
|
|
372
649
|
if (this.requiresCommand === 'EXPLICIT') {
|
|
373
650
|
throw new Error(
|
|
374
651
|
'Interactive shell is not supported for commands that require a command.'
|
|
@@ -376,7 +653,7 @@ export class InternalCLI<TArgs extends ParsedArgs = ParsedArgs>
|
|
|
376
653
|
} else if (process.stdout.isTTY) {
|
|
377
654
|
this.requiresCommand = false;
|
|
378
655
|
}
|
|
379
|
-
return this
|
|
656
|
+
return this as unknown as CLI<TArgs, THandlerReturn, TChildren, TParent>;
|
|
380
657
|
}
|
|
381
658
|
|
|
382
659
|
private versionHandler() {
|
|
@@ -415,9 +692,11 @@ export class InternalCLI<TArgs extends ParsedArgs = ParsedArgs>
|
|
|
415
692
|
}
|
|
416
693
|
}
|
|
417
694
|
|
|
418
|
-
errorHandler(
|
|
695
|
+
errorHandler(
|
|
696
|
+
handler: ErrorHandler
|
|
697
|
+
): CLI<TArgs, THandlerReturn, TChildren, TParent> {
|
|
419
698
|
this.registeredErrorHandlers.unshift(handler);
|
|
420
|
-
return this
|
|
699
|
+
return this as unknown as CLI<TArgs, THandlerReturn, TChildren, TParent>;
|
|
421
700
|
}
|
|
422
701
|
|
|
423
702
|
group(
|
|
@@ -425,7 +704,7 @@ export class InternalCLI<TArgs extends ParsedArgs = ParsedArgs>
|
|
|
425
704
|
| string
|
|
426
705
|
| { label: string; keys: (keyof TArgs)[]; sortOrder: number },
|
|
427
706
|
keys?: (keyof TArgs)[]
|
|
428
|
-
): CLI<TArgs> {
|
|
707
|
+
): CLI<TArgs, THandlerReturn, TChildren, TParent> {
|
|
429
708
|
const config =
|
|
430
709
|
typeof labelOrConfigObject === 'object'
|
|
431
710
|
? labelOrConfigObject
|
|
@@ -440,16 +719,16 @@ export class InternalCLI<TArgs extends ParsedArgs = ParsedArgs>
|
|
|
440
719
|
}
|
|
441
720
|
|
|
442
721
|
this.registeredOptionGroups.push(config);
|
|
443
|
-
return this
|
|
722
|
+
return this as unknown as CLI<TArgs, THandlerReturn, TChildren, TParent>;
|
|
444
723
|
}
|
|
445
724
|
|
|
446
725
|
config(
|
|
447
726
|
provider: ConfigurationFiles.ConfigurationProvider<TArgs>
|
|
448
|
-
): CLI<TArgs> {
|
|
727
|
+
): CLI<TArgs, THandlerReturn, TChildren, TParent> {
|
|
449
728
|
this.parser.config(
|
|
450
729
|
provider as ConfigurationFiles.ConfigurationProvider<any>
|
|
451
730
|
);
|
|
452
|
-
return this
|
|
731
|
+
return this as unknown as CLI<TArgs, THandlerReturn, TChildren, TParent>;
|
|
453
732
|
}
|
|
454
733
|
|
|
455
734
|
/**
|
|
@@ -475,7 +754,7 @@ export class InternalCLI<TArgs extends ParsedArgs = ParsedArgs>
|
|
|
475
754
|
}
|
|
476
755
|
}
|
|
477
756
|
// eslint-disable-next-line @typescript-eslint/no-this-alias
|
|
478
|
-
let currentCommand: InternalCLI<any> = this;
|
|
757
|
+
let currentCommand: InternalCLI<any, any, any, any> = this;
|
|
479
758
|
for (const command of this.commandChain) {
|
|
480
759
|
currentCommand = currentCommand.registeredCommands[command];
|
|
481
760
|
}
|
|
@@ -495,7 +774,9 @@ export class InternalCLI<TArgs extends ParsedArgs = ParsedArgs>
|
|
|
495
774
|
const finalArgV =
|
|
496
775
|
this.commandChain.length === 0 && this.configuration?.builder
|
|
497
776
|
? (
|
|
498
|
-
this.configuration.builder?.(
|
|
777
|
+
this.configuration.builder?.(
|
|
778
|
+
this as any
|
|
779
|
+
) as unknown as InternalCLI<TArgs, any, any, any>
|
|
499
780
|
).parser.parse(args)
|
|
500
781
|
: argv;
|
|
501
782
|
|
|
@@ -512,15 +793,16 @@ export class InternalCLI<TArgs extends ParsedArgs = ParsedArgs>
|
|
|
512
793
|
}
|
|
513
794
|
|
|
514
795
|
clone() {
|
|
515
|
-
const clone = new InternalCLI<TArgs>(
|
|
796
|
+
const clone = new InternalCLI<TArgs, THandlerReturn, TChildren, TParent>(
|
|
797
|
+
this.name
|
|
798
|
+
);
|
|
516
799
|
clone.parser = this.parser.clone(clone.parser.options) as any;
|
|
517
800
|
if (this.configuration) {
|
|
518
801
|
clone.withRootCommandConfiguration(this.configuration);
|
|
519
802
|
}
|
|
520
803
|
clone.registeredCommands = {};
|
|
521
804
|
for (const command in this.registeredCommands ?? {}) {
|
|
522
|
-
clone.command(this.registeredCommands[command].clone());
|
|
523
|
-
// this.registeredCommands[command].clone();
|
|
805
|
+
clone.command(this.registeredCommands[command].clone() as any);
|
|
524
806
|
}
|
|
525
807
|
clone.commandChain = [...this.commandChain];
|
|
526
808
|
clone.requiresCommand = this.requiresCommand;
|