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.
@@ -1,3 +1,4 @@
1
+ /* eslint-disable @typescript-eslint/ban-types */
1
2
  import {
2
3
  type ConfigurationFiles,
3
4
  OptionConfig,
@@ -10,12 +11,58 @@ import {
10
11
  BooleanOptionConfig,
11
12
  ArrayOptionConfig,
12
13
  ResolveProperties,
13
- AdditionalPropertiesType,
14
14
  WithOptional,
15
+ MakeUndefinedPropertiesOptional,
15
16
  } from '@cli-forge/parser';
16
17
 
17
18
  import { InternalCLI } from './internal-cli';
18
19
 
20
+ /**
21
+ * Extracts the command name from a Command type.
22
+ * Works with both CLI instances and command config objects.
23
+ */
24
+ export type ExtractCommandName<T> = T extends CLI<any, any, any>
25
+ ? T extends InternalCLI<any, any, any>
26
+ ? string
27
+ : string
28
+ : T extends { name: infer N }
29
+ ? N extends string
30
+ ? N
31
+ : string
32
+ : string;
33
+
34
+ /**
35
+ * Extracts the args type from a Command.
36
+ * Works with both CLI instances and command config objects.
37
+ */
38
+ export type ExtractCommandArgs<T> = T extends CLI<infer A, any, any>
39
+ ? A
40
+ : T extends CLICommandOptions<any, infer A, any, any>
41
+ ? A
42
+ : ParsedArgs;
43
+
44
+ /**
45
+ * Extracts the handler return type from a Command.
46
+ */
47
+ export type ExtractCommandHandlerReturn<T> = T extends CLI<any, infer R, any>
48
+ ? R
49
+ : T extends CLICommandOptions<any, any, infer R, any>
50
+ ? R
51
+ : void;
52
+
53
+ /**
54
+ * Converts a Command to its child CLI entry for TChildren tracking.
55
+ * TParentCLI is the parent CLI type that will be set as the child's TParent.
56
+ */
57
+ export type CommandToChildEntry<T, TParentCLI = undefined> = {
58
+ [K in ExtractCommandName<T>]: CLI<
59
+ ExtractCommandArgs<T>,
60
+ ExtractCommandHandlerReturn<T>,
61
+ {},
62
+ TParentCLI
63
+ >;
64
+ };
65
+
19
66
  /**
20
67
  * The interface for a CLI application or subcommands.
21
68
  *
@@ -35,10 +82,31 @@ import { InternalCLI } from './internal-cli';
35
82
  * }).forge();
36
83
  * ```
37
84
  */
38
- export interface CLI<TArgs extends ParsedArgs = ParsedArgs> {
85
+ export interface CLI<
86
+ TArgs extends ParsedArgs = ParsedArgs,
87
+ THandlerReturn = void,
88
+ // eslint-disable-next-line @typescript-eslint/ban-types
89
+ TChildren = {},
90
+ TParent = undefined
91
+ > {
39
92
  command<TCommandArgs extends TArgs>(
40
93
  cmd: Command<TArgs, TCommandArgs>
41
- ): CLI<TArgs>;
94
+ ): CLI<
95
+ TArgs,
96
+ THandlerReturn,
97
+ TChildren &
98
+ (typeof cmd extends Command<TArgs, infer TCommandArgs, infer TCmdName>
99
+ ? {
100
+ [key in TCmdName]: CLI<
101
+ TCommandArgs,
102
+ void,
103
+ {},
104
+ CLI<TArgs, THandlerReturn, TChildren, TParent>
105
+ >;
106
+ }
107
+ : {}),
108
+ TParent
109
+ >;
42
110
 
43
111
  /**
44
112
  * Registers a new command with the CLI.
@@ -46,35 +114,300 @@ export interface CLI<TArgs extends ParsedArgs = ParsedArgs> {
46
114
  * @param options Settings for the new command. See {@link CLICommandOptions}.
47
115
  * @returns Updated CLI instance with the new command registered.
48
116
  */
49
- command<TCommandArgs extends TArgs>(
50
- key: string,
51
- options: CLICommandOptions<TArgs, TCommandArgs>
52
- ): CLI<TArgs>;
117
+ command<
118
+ TCommandArgs extends TArgs,
119
+ TChildHandlerReturn,
120
+ TKey extends string,
121
+ // eslint-disable-next-line @typescript-eslint/ban-types
122
+ TChildChildren = {}
123
+ >(
124
+ key: TKey,
125
+ options: CLICommandOptions<
126
+ TArgs,
127
+ TCommandArgs,
128
+ TChildHandlerReturn,
129
+ TChildren,
130
+ CLI<TArgs, THandlerReturn, TChildren, TParent>,
131
+ TChildChildren
132
+ >
133
+ ): CLI<
134
+ TArgs,
135
+ THandlerReturn,
136
+ TChildren & {
137
+ [key in TKey]: CLI<
138
+ TCommandArgs,
139
+ TChildHandlerReturn,
140
+ TChildChildren,
141
+ CLI<TArgs, THandlerReturn, TChildren, TParent>
142
+ >;
143
+ },
144
+ TParent
145
+ >;
53
146
 
54
147
  /**
55
148
  * Registers multiple subcommands with the CLI.
56
149
  * @param commands Several commands to register. Can be the result of a call to {@link cli} or a configuration object.
150
+ * @returns Updated CLI instance with the commands registered and their types tracked in TChildren.
57
151
  */
58
- commands(commands: Command[]): CLI<TArgs>;
59
- /**
60
- * Registers multiple subcommands with the CLI.
61
- * @param commands Several commands to register. Can be the result of a call to {@link cli} or a configuration object.
62
- */
63
- commands(...commands: Command[]): CLI<TArgs>;
152
+ // Typed overloads for 1-10 commands to preserve individual command types
153
+ // Each child command gets this CLI as its TParent
154
+ commands<C1 extends Command>(
155
+ c1: C1
156
+ ): CLI<
157
+ TArgs,
158
+ THandlerReturn,
159
+ TChildren &
160
+ CommandToChildEntry<C1, CLI<TArgs, THandlerReturn, TChildren, TParent>>,
161
+ TParent
162
+ >;
163
+ commands<C1 extends Command, C2 extends Command>(
164
+ c1: C1,
165
+ c2: C2
166
+ ): CLI<
167
+ TArgs,
168
+ THandlerReturn,
169
+ TChildren &
170
+ CommandToChildEntry<C1, CLI<TArgs, THandlerReturn, TChildren, TParent>> &
171
+ CommandToChildEntry<C2, CLI<TArgs, THandlerReturn, TChildren, TParent>>,
172
+ TParent
173
+ >;
174
+ commands<C1 extends Command, C2 extends Command, C3 extends Command>(
175
+ c1: C1,
176
+ c2: C2,
177
+ c3: C3
178
+ ): CLI<
179
+ TArgs,
180
+ THandlerReturn,
181
+ TChildren &
182
+ CommandToChildEntry<C1, CLI<TArgs, THandlerReturn, TChildren, TParent>> &
183
+ CommandToChildEntry<C2, CLI<TArgs, THandlerReturn, TChildren, TParent>> &
184
+ CommandToChildEntry<C3, CLI<TArgs, THandlerReturn, TChildren, TParent>>,
185
+ TParent
186
+ >;
187
+ commands<
188
+ C1 extends Command,
189
+ C2 extends Command,
190
+ C3 extends Command,
191
+ C4 extends Command
192
+ >(
193
+ c1: C1,
194
+ c2: C2,
195
+ c3: C3,
196
+ c4: C4
197
+ ): CLI<
198
+ TArgs,
199
+ THandlerReturn,
200
+ TChildren &
201
+ CommandToChildEntry<C1, CLI<TArgs, THandlerReturn, TChildren, TParent>> &
202
+ CommandToChildEntry<C2, CLI<TArgs, THandlerReturn, TChildren, TParent>> &
203
+ CommandToChildEntry<C3, CLI<TArgs, THandlerReturn, TChildren, TParent>> &
204
+ CommandToChildEntry<C4, CLI<TArgs, THandlerReturn, TChildren, TParent>>,
205
+ TParent
206
+ >;
207
+ commands<
208
+ C1 extends Command,
209
+ C2 extends Command,
210
+ C3 extends Command,
211
+ C4 extends Command,
212
+ C5 extends Command
213
+ >(
214
+ c1: C1,
215
+ c2: C2,
216
+ c3: C3,
217
+ c4: C4,
218
+ c5: C5
219
+ ): CLI<
220
+ TArgs,
221
+ THandlerReturn,
222
+ TChildren &
223
+ CommandToChildEntry<C1, CLI<TArgs, THandlerReturn, TChildren, TParent>> &
224
+ CommandToChildEntry<C2, CLI<TArgs, THandlerReturn, TChildren, TParent>> &
225
+ CommandToChildEntry<C3, CLI<TArgs, THandlerReturn, TChildren, TParent>> &
226
+ CommandToChildEntry<C4, CLI<TArgs, THandlerReturn, TChildren, TParent>> &
227
+ CommandToChildEntry<C5, CLI<TArgs, THandlerReturn, TChildren, TParent>>,
228
+ TParent
229
+ >;
230
+ commands<
231
+ C1 extends Command,
232
+ C2 extends Command,
233
+ C3 extends Command,
234
+ C4 extends Command,
235
+ C5 extends Command,
236
+ C6 extends Command
237
+ >(
238
+ c1: C1,
239
+ c2: C2,
240
+ c3: C3,
241
+ c4: C4,
242
+ c5: C5,
243
+ c6: C6
244
+ ): CLI<
245
+ TArgs,
246
+ THandlerReturn,
247
+ TChildren &
248
+ CommandToChildEntry<C1, CLI<TArgs, THandlerReturn, TChildren, TParent>> &
249
+ CommandToChildEntry<C2, CLI<TArgs, THandlerReturn, TChildren, TParent>> &
250
+ CommandToChildEntry<C3, CLI<TArgs, THandlerReturn, TChildren, TParent>> &
251
+ CommandToChildEntry<C4, CLI<TArgs, THandlerReturn, TChildren, TParent>> &
252
+ CommandToChildEntry<C5, CLI<TArgs, THandlerReturn, TChildren, TParent>> &
253
+ CommandToChildEntry<C6, CLI<TArgs, THandlerReturn, TChildren, TParent>>,
254
+ TParent
255
+ >;
256
+ commands<
257
+ C1 extends Command,
258
+ C2 extends Command,
259
+ C3 extends Command,
260
+ C4 extends Command,
261
+ C5 extends Command,
262
+ C6 extends Command,
263
+ C7 extends Command
264
+ >(
265
+ c1: C1,
266
+ c2: C2,
267
+ c3: C3,
268
+ c4: C4,
269
+ c5: C5,
270
+ c6: C6,
271
+ c7: C7
272
+ ): CLI<
273
+ TArgs,
274
+ THandlerReturn,
275
+ TChildren &
276
+ CommandToChildEntry<C1, CLI<TArgs, THandlerReturn, TChildren, TParent>> &
277
+ CommandToChildEntry<C2, CLI<TArgs, THandlerReturn, TChildren, TParent>> &
278
+ CommandToChildEntry<C3, CLI<TArgs, THandlerReturn, TChildren, TParent>> &
279
+ CommandToChildEntry<C4, CLI<TArgs, THandlerReturn, TChildren, TParent>> &
280
+ CommandToChildEntry<C5, CLI<TArgs, THandlerReturn, TChildren, TParent>> &
281
+ CommandToChildEntry<C6, CLI<TArgs, THandlerReturn, TChildren, TParent>> &
282
+ CommandToChildEntry<C7, CLI<TArgs, THandlerReturn, TChildren, TParent>>,
283
+ TParent
284
+ >;
285
+ commands<
286
+ C1 extends Command,
287
+ C2 extends Command,
288
+ C3 extends Command,
289
+ C4 extends Command,
290
+ C5 extends Command,
291
+ C6 extends Command,
292
+ C7 extends Command,
293
+ C8 extends Command
294
+ >(
295
+ c1: C1,
296
+ c2: C2,
297
+ c3: C3,
298
+ c4: C4,
299
+ c5: C5,
300
+ c6: C6,
301
+ c7: C7,
302
+ c8: C8
303
+ ): CLI<
304
+ TArgs,
305
+ THandlerReturn,
306
+ TChildren &
307
+ CommandToChildEntry<C1, CLI<TArgs, THandlerReturn, TChildren, TParent>> &
308
+ CommandToChildEntry<C2, CLI<TArgs, THandlerReturn, TChildren, TParent>> &
309
+ CommandToChildEntry<C3, CLI<TArgs, THandlerReturn, TChildren, TParent>> &
310
+ CommandToChildEntry<C4, CLI<TArgs, THandlerReturn, TChildren, TParent>> &
311
+ CommandToChildEntry<C5, CLI<TArgs, THandlerReturn, TChildren, TParent>> &
312
+ CommandToChildEntry<C6, CLI<TArgs, THandlerReturn, TChildren, TParent>> &
313
+ CommandToChildEntry<C7, CLI<TArgs, THandlerReturn, TChildren, TParent>> &
314
+ CommandToChildEntry<C8, CLI<TArgs, THandlerReturn, TChildren, TParent>>,
315
+ TParent
316
+ >;
317
+ commands<
318
+ C1 extends Command,
319
+ C2 extends Command,
320
+ C3 extends Command,
321
+ C4 extends Command,
322
+ C5 extends Command,
323
+ C6 extends Command,
324
+ C7 extends Command,
325
+ C8 extends Command,
326
+ C9 extends Command
327
+ >(
328
+ c1: C1,
329
+ c2: C2,
330
+ c3: C3,
331
+ c4: C4,
332
+ c5: C5,
333
+ c6: C6,
334
+ c7: C7,
335
+ c8: C8,
336
+ c9: C9
337
+ ): CLI<
338
+ TArgs,
339
+ THandlerReturn,
340
+ TChildren &
341
+ CommandToChildEntry<C1, CLI<TArgs, THandlerReturn, TChildren, TParent>> &
342
+ CommandToChildEntry<C2, CLI<TArgs, THandlerReturn, TChildren, TParent>> &
343
+ CommandToChildEntry<C3, CLI<TArgs, THandlerReturn, TChildren, TParent>> &
344
+ CommandToChildEntry<C4, CLI<TArgs, THandlerReturn, TChildren, TParent>> &
345
+ CommandToChildEntry<C5, CLI<TArgs, THandlerReturn, TChildren, TParent>> &
346
+ CommandToChildEntry<C6, CLI<TArgs, THandlerReturn, TChildren, TParent>> &
347
+ CommandToChildEntry<C7, CLI<TArgs, THandlerReturn, TChildren, TParent>> &
348
+ CommandToChildEntry<C8, CLI<TArgs, THandlerReturn, TChildren, TParent>> &
349
+ CommandToChildEntry<C9, CLI<TArgs, THandlerReturn, TChildren, TParent>>,
350
+ TParent
351
+ >;
352
+ commands<
353
+ C1 extends Command,
354
+ C2 extends Command,
355
+ C3 extends Command,
356
+ C4 extends Command,
357
+ C5 extends Command,
358
+ C6 extends Command,
359
+ C7 extends Command,
360
+ C8 extends Command,
361
+ C9 extends Command,
362
+ C10 extends Command
363
+ >(
364
+ c1: C1,
365
+ c2: C2,
366
+ c3: C3,
367
+ c4: C4,
368
+ c5: C5,
369
+ c6: C6,
370
+ c7: C7,
371
+ c8: C8,
372
+ c9: C9,
373
+ c10: C10
374
+ ): CLI<
375
+ TArgs,
376
+ THandlerReturn,
377
+ TChildren &
378
+ CommandToChildEntry<C1, CLI<TArgs, THandlerReturn, TChildren, TParent>> &
379
+ CommandToChildEntry<C2, CLI<TArgs, THandlerReturn, TChildren, TParent>> &
380
+ CommandToChildEntry<C3, CLI<TArgs, THandlerReturn, TChildren, TParent>> &
381
+ CommandToChildEntry<C4, CLI<TArgs, THandlerReturn, TChildren, TParent>> &
382
+ CommandToChildEntry<C5, CLI<TArgs, THandlerReturn, TChildren, TParent>> &
383
+ CommandToChildEntry<C6, CLI<TArgs, THandlerReturn, TChildren, TParent>> &
384
+ CommandToChildEntry<C7, CLI<TArgs, THandlerReturn, TChildren, TParent>> &
385
+ CommandToChildEntry<C8, CLI<TArgs, THandlerReturn, TChildren, TParent>> &
386
+ CommandToChildEntry<C9, CLI<TArgs, THandlerReturn, TChildren, TParent>> &
387
+ CommandToChildEntry<C10, CLI<TArgs, THandlerReturn, TChildren, TParent>>,
388
+ TParent
389
+ >;
390
+ // Fallback for arrays or more than 10 commands (loses individual type tracking)
391
+ commands(commands: Command[]): CLI<TArgs, THandlerReturn, TChildren, TParent>;
392
+ commands(
393
+ ...commands: Command[]
394
+ ): CLI<TArgs, THandlerReturn, TChildren, TParent>;
64
395
 
65
396
  /**
66
397
  * Register's a configuration provider for the CLI. See {@link ConfigurationProviders} for built-in providers.
67
398
  *
68
399
  * @param provider Provider to register.
69
400
  */
70
- config(provider: ConfigurationFiles.ConfigurationProvider<TArgs>): CLI<TArgs>;
401
+ config(
402
+ provider: ConfigurationFiles.ConfigurationProvider<TArgs>
403
+ ): CLI<TArgs, THandlerReturn, TChildren, TParent>;
71
404
 
72
405
  /**
73
406
  * Enables the ability to run CLI commands that contain subcommands as an interactive shell.
74
407
  * This presents as a small shell that only knows the current command and its subcommands.
75
408
  * Any flags already consumed by the command will be passed to every subcommand invocation.
76
409
  */
77
- enableInteractiveShell(): CLI<TArgs>;
410
+ enableInteractiveShell(): CLI<TArgs, THandlerReturn, TChildren, TParent>;
78
411
 
79
412
  /**
80
413
  * Registers a custom global error handler for the CLI. This handler will be called when an error is thrown
@@ -84,7 +417,9 @@ export interface CLI<TArgs extends ParsedArgs = ParsedArgs> {
84
417
  * @param handler Typically called with an Error object, but you should be prepared to handle any type of error.
85
418
  * @param actions Actions that can be taken by the error handler. Prefer using these over process.exit for better support of interactive shells.
86
419
  */
87
- errorHandler(handler: ErrorHandler): CLI<TArgs>;
420
+ errorHandler(
421
+ handler: ErrorHandler
422
+ ): CLI<TArgs, THandlerReturn, TChildren, TParent>;
88
423
 
89
424
  /**
90
425
  * Registers a new option for the CLI command. This option will be accessible
@@ -100,20 +435,21 @@ export interface CLI<TArgs extends ParsedArgs = ParsedArgs> {
100
435
  option<
101
436
  TOption extends string,
102
437
  TCoerce,
103
- const TProps extends Record<string, { type: string }>,
104
- TAdditionalProps extends false | 'string' | 'number' | 'boolean' = false
438
+ const TProps extends Record<string, { type: string }>
105
439
  >(
106
440
  name: TOption,
107
- config: ObjectOptionConfig<TCoerce, TProps, TAdditionalProps>
441
+ config: ObjectOptionConfig<TCoerce, TProps>
108
442
  ): CLI<
109
- TArgs & {
110
- [key in TOption]: WithOptional<
111
- unknown extends TCoerce
112
- ? ResolveProperties<TProps> & AdditionalPropertiesType<TAdditionalProps>
113
- : TCoerce,
114
- ObjectOptionConfig<TCoerce, TProps, TAdditionalProps>
115
- >;
116
- }
443
+ TArgs &
444
+ MakeUndefinedPropertiesOptional<{
445
+ [key in TOption]: WithOptional<
446
+ unknown extends TCoerce ? ResolveProperties<TProps> : TCoerce,
447
+ ObjectOptionConfig<TCoerce, TProps>
448
+ >;
449
+ }>,
450
+ THandlerReturn,
451
+ TChildren,
452
+ TParent
117
453
  >;
118
454
  // String option overload
119
455
  option<
@@ -123,9 +459,13 @@ export interface CLI<TArgs extends ParsedArgs = ParsedArgs> {
123
459
  name: TOption,
124
460
  config: TConfig
125
461
  ): CLI<
126
- TArgs & {
127
- [key in TOption]: OptionConfigToType<TConfig>;
128
- }
462
+ TArgs &
463
+ MakeUndefinedPropertiesOptional<{
464
+ [key in TOption]: OptionConfigToType<TConfig>;
465
+ }>,
466
+ THandlerReturn,
467
+ TChildren,
468
+ TParent
129
469
  >;
130
470
  // Number option overload
131
471
  option<
@@ -135,9 +475,13 @@ export interface CLI<TArgs extends ParsedArgs = ParsedArgs> {
135
475
  name: TOption,
136
476
  config: TConfig
137
477
  ): CLI<
138
- TArgs & {
139
- [key in TOption]: OptionConfigToType<TConfig>;
140
- }
478
+ TArgs &
479
+ MakeUndefinedPropertiesOptional<{
480
+ [key in TOption]: OptionConfigToType<TConfig>;
481
+ }>,
482
+ THandlerReturn,
483
+ TChildren,
484
+ TParent
141
485
  >;
142
486
  // Boolean option overload
143
487
  option<
@@ -147,9 +491,13 @@ export interface CLI<TArgs extends ParsedArgs = ParsedArgs> {
147
491
  name: TOption,
148
492
  config: TConfig
149
493
  ): CLI<
150
- TArgs & {
151
- [key in TOption]: OptionConfigToType<TConfig>;
152
- }
494
+ TArgs &
495
+ MakeUndefinedPropertiesOptional<{
496
+ [key in TOption]: OptionConfigToType<TConfig>;
497
+ }>,
498
+ THandlerReturn,
499
+ TChildren,
500
+ TParent
153
501
  >;
154
502
  // Array option overload
155
503
  option<
@@ -159,21 +507,29 @@ export interface CLI<TArgs extends ParsedArgs = ParsedArgs> {
159
507
  name: TOption,
160
508
  config: TConfig
161
509
  ): CLI<
162
- TArgs & {
163
- [key in TOption]: OptionConfigToType<TConfig>;
164
- }
510
+ TArgs &
511
+ MakeUndefinedPropertiesOptional<{
512
+ [key in TOption]: OptionConfigToType<TConfig>;
513
+ }>,
514
+ THandlerReturn,
515
+ TChildren,
516
+ TParent
165
517
  >;
166
518
  // Generic fallback overload
167
519
  option<
168
520
  TOption extends string,
169
- const TOptionConfig extends OptionConfig<any, any, any, any>
521
+ const TOptionConfig extends OptionConfig<any, any, any>
170
522
  >(
171
523
  name: TOption,
172
524
  config: TOptionConfig
173
525
  ): CLI<
174
- TArgs & {
175
- [key in TOption]: OptionConfigToType<TOptionConfig>;
176
- }
526
+ TArgs &
527
+ MakeUndefinedPropertiesOptional<{
528
+ [key in TOption]: OptionConfigToType<TOptionConfig>;
529
+ }>,
530
+ THandlerReturn,
531
+ TChildren,
532
+ TParent
177
533
  >;
178
534
 
179
535
  /**
@@ -189,20 +545,21 @@ export interface CLI<TArgs extends ParsedArgs = ParsedArgs> {
189
545
  positional<
190
546
  TOption extends string,
191
547
  TCoerce,
192
- const TProps extends Record<string, { type: string }>,
193
- TAdditionalProps extends false | 'string' | 'number' | 'boolean' = false
548
+ const TProps extends Record<string, { type: string }>
194
549
  >(
195
550
  name: TOption,
196
- config: ObjectOptionConfig<TCoerce, TProps, TAdditionalProps>
551
+ config: ObjectOptionConfig<TCoerce, TProps>
197
552
  ): CLI<
198
- TArgs & {
199
- [key in TOption]: WithOptional<
200
- unknown extends TCoerce
201
- ? ResolveProperties<TProps> & AdditionalPropertiesType<TAdditionalProps>
202
- : TCoerce,
203
- ObjectOptionConfig<TCoerce, TProps, TAdditionalProps>
204
- >;
205
- }
553
+ TArgs &
554
+ MakeUndefinedPropertiesOptional<{
555
+ [key in TOption]: WithOptional<
556
+ unknown extends TCoerce ? ResolveProperties<TProps> : TCoerce,
557
+ ObjectOptionConfig<TCoerce, TProps>
558
+ >;
559
+ }>,
560
+ THandlerReturn,
561
+ TChildren,
562
+ TParent
206
563
  >;
207
564
  // String option overload
208
565
  positional<
@@ -212,9 +569,13 @@ export interface CLI<TArgs extends ParsedArgs = ParsedArgs> {
212
569
  name: TOption,
213
570
  config: TConfig
214
571
  ): CLI<
215
- TArgs & {
216
- [key in TOption]: OptionConfigToType<TConfig>;
217
- }
572
+ TArgs &
573
+ MakeUndefinedPropertiesOptional<{
574
+ [key in TOption]: OptionConfigToType<TConfig>;
575
+ }>,
576
+ THandlerReturn,
577
+ TChildren,
578
+ TParent
218
579
  >;
219
580
  // Number option overload
220
581
  positional<
@@ -224,9 +585,13 @@ export interface CLI<TArgs extends ParsedArgs = ParsedArgs> {
224
585
  name: TOption,
225
586
  config: TConfig
226
587
  ): CLI<
227
- TArgs & {
228
- [key in TOption]: OptionConfigToType<TConfig>;
229
- }
588
+ TArgs &
589
+ MakeUndefinedPropertiesOptional<{
590
+ [key in TOption]: OptionConfigToType<TConfig>;
591
+ }>,
592
+ THandlerReturn,
593
+ TChildren,
594
+ TParent
230
595
  >;
231
596
  // Boolean option overload
232
597
  positional<
@@ -236,9 +601,13 @@ export interface CLI<TArgs extends ParsedArgs = ParsedArgs> {
236
601
  name: TOption,
237
602
  config: TConfig
238
603
  ): CLI<
239
- TArgs & {
240
- [key in TOption]: OptionConfigToType<TConfig>;
241
- }
604
+ TArgs &
605
+ MakeUndefinedPropertiesOptional<{
606
+ [key in TOption]: OptionConfigToType<TConfig>;
607
+ }>,
608
+ THandlerReturn,
609
+ TChildren,
610
+ TParent
242
611
  >;
243
612
  // Array option overload
244
613
  positional<
@@ -248,69 +617,84 @@ export interface CLI<TArgs extends ParsedArgs = ParsedArgs> {
248
617
  name: TOption,
249
618
  config: TConfig
250
619
  ): CLI<
251
- TArgs & {
252
- [key in TOption]: OptionConfigToType<TConfig>;
253
- }
620
+ TArgs &
621
+ MakeUndefinedPropertiesOptional<{
622
+ [key in TOption]: OptionConfigToType<TConfig>;
623
+ }>,
624
+ THandlerReturn,
625
+ TChildren,
626
+ TParent
254
627
  >;
255
628
  // Generic fallback overload
256
629
  positional<
257
630
  TOption extends string,
258
- const TOptionConfig extends OptionConfig<any, any, any, any>
631
+ const TOptionConfig extends OptionConfig<any, any, any>
259
632
  >(
260
633
  name: TOption,
261
634
  config: TOptionConfig
262
635
  ): CLI<
263
- TArgs & {
264
- [key in TOption]: OptionConfigToType<TOptionConfig>;
265
- }
636
+ TArgs &
637
+ MakeUndefinedPropertiesOptional<{
638
+ [key in TOption]: OptionConfigToType<TOptionConfig>;
639
+ }>,
640
+ THandlerReturn,
641
+ TChildren,
642
+ TParent
266
643
  >;
267
644
 
268
645
  /**
269
646
  * Adds support for reading CLI options from environment variables.
270
647
  * @param prefix The prefix to use when looking up environment variables. Defaults to the command name.
271
648
  */
272
- env(prefix?: string): CLI<TArgs>;
649
+ env(prefix?: string): CLI<TArgs, THandlerReturn, TChildren, TParent>;
273
650
 
274
- env(options: EnvOptionConfig): CLI<TArgs>;
651
+ env(options: EnvOptionConfig): CLI<TArgs, THandlerReturn, TChildren, TParent>;
275
652
 
276
653
  /**
277
654
  * Sets a group of options as mutually exclusive. If more than one option is provided, there will be a validation error.
278
655
  * @param options The options that should be mutually exclusive.
279
656
  */
280
- conflicts(...options: [string, string, ...string[]]): CLI<TArgs>;
657
+ conflicts(
658
+ ...options: [string, string, ...string[]]
659
+ ): CLI<TArgs, THandlerReturn, TChildren, TParent>;
281
660
 
282
661
  /**
283
662
  * Sets a group of options as mutually inclusive. If one option is provided, all other options must also be provided.
284
663
  * @param option The option that implies the other options.
285
664
  * @param impliedOptions The options which become required when the option is provided.
286
665
  */
287
- implies(option: string, ...impliedOptions: string[]): CLI<TArgs>;
666
+ implies(
667
+ option: string,
668
+ ...impliedOptions: string[]
669
+ ): CLI<TArgs, THandlerReturn, TChildren, TParent>;
288
670
 
289
671
  /**
290
672
  * Requires a command to be provided when executing the CLI. Useful if your parent command
291
673
  * cannot be executed on its own.
292
674
  * @returns Updated CLI instance.
293
675
  */
294
- demandCommand(): CLI<TArgs>;
676
+ demandCommand(): CLI<TArgs, THandlerReturn, TChildren, TParent>;
295
677
 
296
678
  /**
297
679
  * Sets the usage text for the CLI. This text will be displayed in place of the default usage text
298
680
  * @param usageText Text displayed in place of the default usage text for `--help` and in generated docs.
299
681
  */
300
- usage(usageText: string): CLI<TArgs>;
682
+ usage(usageText: string): CLI<TArgs, THandlerReturn, TChildren, TParent>;
301
683
 
302
684
  /**
303
685
  * Sets the description for the CLI. This text will be displayed in the help text and generated docs.
304
686
  * @param examples Examples to display in the help text and generated docs.
305
687
  */
306
- examples(...examples: string[]): CLI<TArgs>;
688
+ examples(
689
+ ...examples: string[]
690
+ ): CLI<TArgs, THandlerReturn, TChildren, TParent>;
307
691
 
308
692
  /**
309
693
  * Allows overriding the version displayed when passing `--version`. Defaults to crawling
310
694
  * the file system to get the package.json of the currently executing command.
311
695
  * @param override
312
696
  */
313
- version(override?: string): CLI<TArgs>;
697
+ version(override?: string): CLI<TArgs, THandlerReturn, TChildren, TParent>;
314
698
 
315
699
  /**
316
700
  * Prints help text to stdout.
@@ -325,12 +709,20 @@ export interface CLI<TArgs extends ParsedArgs = ParsedArgs> {
325
709
  label: string;
326
710
  keys: (keyof TArgs)[];
327
711
  sortOrder: number;
328
- }): CLI<TArgs>;
329
- group(label: string, keys: (keyof TArgs)[]): CLI<TArgs>;
712
+ }): CLI<TArgs, THandlerReturn, TChildren, TParent>;
713
+ group(
714
+ label: string,
715
+ keys: (keyof TArgs)[]
716
+ ): CLI<TArgs, THandlerReturn, TChildren, TParent>;
330
717
 
331
718
  middleware<TArgs2>(
332
719
  callback: MiddlewareFunction<TArgs, TArgs2>
333
- ): CLI<TArgs2 extends void ? TArgs : TArgs & TArgs2>;
720
+ ): CLI<
721
+ TArgs2 extends void ? TArgs : TArgs & TArgs2,
722
+ THandlerReturn,
723
+ TChildren,
724
+ TParent
725
+ >;
334
726
 
335
727
  /**
336
728
  * Parses argv and executes the CLI
@@ -338,12 +730,106 @@ export interface CLI<TArgs extends ParsedArgs = ParsedArgs> {
338
730
  * @returns Promise that resolves when the handler completes.
339
731
  */
340
732
  forge(args?: string[]): Promise<TArgs>;
733
+
734
+ /**
735
+ * Returns the typed children commands registered with this CLI.
736
+ * The return type is determined by the commands registered via `command()` or `commands()`.
737
+ *
738
+ * @example
739
+ * ```ts
740
+ * const app = cli('app')
741
+ * .command('init', { ... })
742
+ * .command('build', { ... });
743
+ *
744
+ * const children = app.getChildren();
745
+ * // children.init and children.build are typed CLI instances
746
+ * const initHandler = children.init.getHandler();
747
+ * ```
748
+ */
749
+ getChildren(): TChildren;
750
+
751
+ /**
752
+ * Returns the parent CLI instance, if this command was registered as a subcommand.
753
+ * Returns undefined for root-level CLI instances.
754
+ *
755
+ * @example
756
+ * ```ts
757
+ * const build = cli('build', {
758
+ * handler: (args, ctx) => {
759
+ * const parent = ctx.command.getParent();
760
+ * const siblings = parent?.getChildren();
761
+ * // Access sibling commands
762
+ * }
763
+ * });
764
+ * ```
765
+ */
766
+ getParent(): TParent;
767
+
768
+ /**
769
+ * Returns a programmatic SDK for invoking this CLI and its subcommands.
770
+ * The SDK provides typed function calls instead of argv parsing.
771
+ *
772
+ * @example
773
+ * ```ts
774
+ * const myCLI = cli('my-app')
775
+ * .option('verbose', { type: 'boolean' })
776
+ * .command('build', {
777
+ * builder: (cmd) => cmd.option('watch', { type: 'boolean' }),
778
+ * handler: (args) => ({ success: true, files: ['a.js'] })
779
+ * });
780
+ *
781
+ * const sdk = myCLI.sdk();
782
+ *
783
+ * // Invoke root command (if it has a handler)
784
+ * await sdk({ verbose: true });
785
+ *
786
+ * // Invoke subcommand with typed args
787
+ * const result = await sdk.build({ watch: true });
788
+ * console.log(result.files); // ['a.js']
789
+ * console.log(result.$args.watch); // true
790
+ *
791
+ * // Use CLI-style args for -- support
792
+ * await sdk.build(['--watch', '--', 'extra-arg']);
793
+ * ```
794
+ *
795
+ * @returns An SDK object that is callable (if this command has a handler)
796
+ * and has properties for each subcommand.
797
+ */
798
+ sdk(): SDKCommand<TArgs, THandlerReturn, TChildren>;
799
+
800
+ /**
801
+ * Returns the builder function for this command as a composable builder.
802
+ * The returned function can be used with `chain` to compose multiple builders.
803
+ *
804
+ * @example
805
+ * ```ts
806
+ * const siblings = args.getParent().getChildren();
807
+ * const withBuildArgs = siblings.build.getBuilder()!;
808
+ * const withServeArgs = siblings.serve.getBuilder()!;
809
+ * return chain(args, withBuildArgs, withServeArgs);
810
+ * ```
811
+ */
812
+ getBuilder():
813
+ | (<TInit extends ParsedArgs, TInitHandlerReturn, TInitChildren, TInitParent>(
814
+ parser: CLI<TInit, TInitHandlerReturn, TInitChildren, TInitParent>
815
+ ) => CLI<TInit & TArgs, TInitHandlerReturn, TInitChildren & TChildren, TInitParent>)
816
+ | undefined;
817
+ getHandler():
818
+ | ((args: Omit<TArgs, keyof ParsedArgs>) => THandlerReturn)
819
+ | undefined;
341
820
  }
342
821
 
343
- export interface CLIHandlerContext {
344
- command: CLI<any>;
822
+ export interface CLIHandlerContext<TChildren = {}, TParent = any> {
823
+ command: CLI<any, any, TChildren, TParent>;
345
824
  }
346
825
 
826
+ /**
827
+ * Extracts the TChildren type parameter from a CLI type.
828
+ */
829
+ export type ExtractCLIChildren<T> = T extends CLI<any, any, infer C, any>
830
+ ? C
831
+ : {};
832
+
347
833
  /**
348
834
  * Represents the configuration needed to create a CLI command.
349
835
  */
@@ -355,7 +841,19 @@ export interface CLICommandOptions<
355
841
  /**
356
842
  * The type of the arguments that are registered after `builder` is invoked, and the type that is passed to the handler.
357
843
  */
358
- TArgs extends TInitial = TInitial
844
+ TArgs extends TInitial = TInitial,
845
+ THandlerReturn = void,
846
+ /**
847
+ * The children commands that exist before the builder runs.
848
+ */
849
+ // eslint-disable-next-line @typescript-eslint/ban-types
850
+ TInitialChildren = {},
851
+ TParent = any,
852
+ /**
853
+ * The children commands after the builder runs (includes TInitialChildren plus any added by builder).
854
+ */
855
+ // eslint-disable-next-line @typescript-eslint/ban-types
856
+ TChildren = {}
359
857
  > {
360
858
  /**
361
859
  * If set the command will be registered under the provided name and any aliases.
@@ -373,14 +871,21 @@ export interface CLICommandOptions<
373
871
  * The command builder. This function is called before the command is executed, and is used to register options and positional parameters.
374
872
  * @param parser The parser instance to register options and positionals with.
375
873
  */
376
- builder?: (parser: CLI<TInitial>) => CLI<TArgs>;
874
+ // Note: Builder uses 'any' for THandlerReturn to avoid inference conflicts with the handler.
875
+ // The handler's return type is inferred independently from the handler function itself.
876
+ builder?: (
877
+ parser: CLI<TInitial, any, TInitialChildren, TParent>
878
+ ) => CLI<TArgs, any, TChildren, any>;
377
879
 
378
880
  /**
379
881
  * The command handler. This function is called when the command is executed.
380
882
  * @param args The parsed arguments.
381
883
  * @param context Context for the handler. Contains the command instance.
382
884
  */
383
- handler?: (args: TArgs, context: CLIHandlerContext) => void | Promise<void>;
885
+ handler?: (
886
+ args: NoInfer<TArgs>,
887
+ context: CLIHandlerContext<NoInfer<TChildren>, TParent>
888
+ ) => THandlerReturn;
384
889
 
385
890
  /**
386
891
  * The usage text for the command. This text will be displayed in place of the default usage text in the help text and generated docs.
@@ -405,10 +910,11 @@ export interface CLICommandOptions<
405
910
 
406
911
  export type Command<
407
912
  TInitial extends ParsedArgs = any,
408
- TArgs extends TInitial = TInitial
913
+ TArgs extends TInitial = TInitial,
914
+ TCommandName extends string = string
409
915
  > =
410
916
  | ({
411
- name: string;
917
+ name: TCommandName;
412
918
  } & CLICommandOptions<TInitial, TArgs>)
413
919
  | CLI<TArgs>;
414
920
 
@@ -429,21 +935,101 @@ export type ErrorHandler = (
429
935
  }
430
936
  ) => void;
431
937
 
938
+ export type UnknownCLI = CLI<ParsedArgs, any, any, any>;
939
+
432
940
  export type MiddlewareFunction<TArgs extends ParsedArgs, TArgs2> = (
433
941
  args: TArgs
434
942
  ) => TArgs2 | Promise<TArgs2>;
435
943
 
944
+ // ============================================================================
945
+ // SDK Types
946
+ // ============================================================================
947
+
948
+ /**
949
+ * Result type that conditionally includes $args.
950
+ * Only attaches $args when result is an object type.
951
+ * Uses Awaited<T> to handle async handlers that return Promise<U>.
952
+ */
953
+ export type SDKResult<TArgs, THandlerReturn> =
954
+ Awaited<THandlerReturn> extends object
955
+ ? Awaited<THandlerReturn> & { $args: TArgs }
956
+ : Awaited<THandlerReturn>;
957
+
958
+ /**
959
+ * The callable signature for a command with a handler.
960
+ * Supports both object-style args (typed, skips validation) and
961
+ * string array args (CLI-style, full validation pipeline).
962
+ */
963
+ export type SDKInvokable<TArgs, THandlerReturn> = {
964
+ /**
965
+ * Invoke the command with typed object args.
966
+ * Skips validation (TypeScript handles it), applies defaults, runs middleware.
967
+ */
968
+ (args?: Partial<Omit<TArgs, 'unmatched' | '--'>>): Promise<
969
+ SDKResult<TArgs, THandlerReturn>
970
+ >;
971
+ /**
972
+ * Invoke the command with CLI-style string args.
973
+ * Runs full pipeline: parse → validate → middleware → handler.
974
+ * Use this when you need to pass `--` extra args.
975
+ */
976
+ (args: string[]): Promise<SDKResult<TArgs, THandlerReturn>>;
977
+ };
978
+
979
+ /**
980
+ * Recursively builds SDK type from TChildren.
981
+ * Each child command becomes a property on the SDK object.
982
+ */
983
+ export type SDKChildren<TChildren> = {
984
+ [K in keyof TChildren]: TChildren[K] extends CLI<
985
+ infer A,
986
+ infer R,
987
+ infer C,
988
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
989
+ infer _P
990
+ >
991
+ ? SDKCommand<A, R, C>
992
+ : never;
993
+ };
994
+
995
+ /**
996
+ * A single SDK command - callable if it has a handler, with nested children as properties.
997
+ * Container commands (no handler) are not callable but still provide access to children.
998
+ */
999
+ export type SDKCommand<TArgs, THandlerReturn, TChildren> =
1000
+ // eslint-disable-next-line @typescript-eslint/ban-types
1001
+ // THandlerReturn extends void | undefined
1002
+ // ? SDKChildren<TChildren> // No handler = just children (not callable)
1003
+ SDKInvokable<TArgs, THandlerReturn> & SDKChildren<TChildren>;
1004
+
436
1005
  /**
437
1006
  * Constructs a CLI instance. See {@link CLI} for more information.
438
1007
  * @param name Name for the top level CLI
439
1008
  * @param rootCommandConfiguration Configuration used when running the bare CLI. e.g. npx my-cli, rather than npx my-cli [cmd]
440
1009
  * @returns A {@link CLI} instance.
441
1010
  */
442
- export function cli<TArgs extends ParsedArgs>(
443
- name: string,
444
- rootCommandConfiguration?: CLICommandOptions<ParsedArgs, TArgs>
1011
+ export function cli<
1012
+ TArgs extends ParsedArgs,
1013
+ THandlerReturn = void,
1014
+ // eslint-disable-next-line @typescript-eslint/ban-types
1015
+ TChildren = {},
1016
+ TName extends string = string
1017
+ >(
1018
+ name: TName,
1019
+ rootCommandConfiguration?: CLICommandOptions<
1020
+ ParsedArgs,
1021
+ TArgs,
1022
+ THandlerReturn,
1023
+ {},
1024
+ any,
1025
+ TChildren
1026
+ >
445
1027
  ) {
446
- return new InternalCLI(name, rootCommandConfiguration) as any as CLI<TArgs>;
1028
+ return new InternalCLI(name, rootCommandConfiguration as any) as any as CLI<
1029
+ TArgs,
1030
+ THandlerReturn,
1031
+ TChildren
1032
+ >;
447
1033
  }
448
1034
 
449
1035
  export default cli;