@@ -25,19 +25,50 @@ type InferArgTypeFromArg<Builder extends TaggedArgBuilder<ArgTag, unknown>> = Bu
2525 : unknown ;
2626
2727type If < T , Y , N > = T extends true ? Y : N ;
28-
29- type InferFlagTypeFromFlag < Builder extends TaggedFlagBuilder < FlagTag , unknown , unknown > > =
30- // Handle special case where there can be no choices
31- Builder extends TaggedFlagBuilder < infer ReturnedType , never , infer Required >
32- ? If < Required , FlagTagToTSType [ ReturnedType ] , FlagTagToTSType [ ReturnedType ] | undefined >
33- : // Might have choices, in which case we branch based on that
34- Builder extends TaggedFlagBuilder < infer ReturnedType , infer ChoiceType , infer Required >
35- ? // If choices is a valid array
36- ChoiceType extends unknown [ ] | readonly unknown [ ]
37- ? // Then assert on required status
28+ type IfNotUnknown < T , Y , N > = T extends unknown ? Y : N ;
29+
30+ type InferFlagTypeFromFlag <
31+ Builder extends TaggedFlagBuilder < FlagTag , unknown , unknown , unknown > ,
32+ OptionalIfHasDefault = false ,
33+ > = Builder extends TaggedFlagBuilder < infer ReturnedType , never , infer Required , infer HasDefault > // Handle special case where there can be no choices
34+ ? If <
35+ // If we want to mark flags as optional if they have a default
36+ OptionalIfHasDefault ,
37+ // If the flag actually has a default value, assert on that
38+ IfNotUnknown <
39+ HasDefault ,
40+ FlagTagToTSType [ ReturnedType ] | undefined ,
41+ // Otherwise fall back to required status
42+ If < Required , FlagTagToTSType [ ReturnedType ] , FlagTagToTSType [ ReturnedType ] | undefined >
43+ > ,
44+ // fallback to required status
45+ If < Required , FlagTagToTSType [ ReturnedType ] , FlagTagToTSType [ ReturnedType ] | undefined >
46+ >
47+ : // Might have choices, in which case we branch based on that
48+ Builder extends TaggedFlagBuilder < infer ReturnedType , infer ChoiceType , infer Required , infer HasDefault >
49+ ? // If choices is a valid array
50+ ChoiceType extends unknown [ ] | readonly unknown [ ]
51+ ? // If we want optional flags to stay as optional
52+ If <
53+ OptionalIfHasDefault ,
54+ ChoiceType [ number ] | undefined ,
55+ // fallback to required status
3856 If < Required , ChoiceType [ number ] , ChoiceType [ number ] | undefined >
39- : If < Required , FlagTagToTSType [ ReturnedType ] , FlagTagToTSType [ ReturnedType ] | undefined >
40- : unknown ;
57+ >
58+ : If <
59+ // If we want to mark flags as optional if they have a default
60+ OptionalIfHasDefault ,
61+ // If the flag actually has a default value, assert on that
62+ IfNotUnknown <
63+ HasDefault ,
64+ FlagTagToTSType [ ReturnedType ] | undefined ,
65+ // Fallback to required status
66+ If < Required , FlagTagToTSType [ ReturnedType ] , FlagTagToTSType [ ReturnedType ] | undefined >
67+ > ,
68+ // fallback to required status
69+ If < Required , FlagTagToTSType [ ReturnedType ] , FlagTagToTSType [ ReturnedType ] | undefined >
70+ >
71+ : unknown ;
4172
4273// Adapted from https://gist.github.com/kuroski/9a7ae8e5e5c9e22985364d1ddbf3389d to support kebab-case and "string a"
4374type CamelCase < S extends string > = S extends
@@ -51,16 +82,23 @@ type _InferArgsFromCommand<O extends Record<string, TaggedArgBuilder<ArgTag, unk
5182 [ K in keyof O as CamelCase < string & K > ] : InferArgTypeFromArg < O [ K ] > ;
5283} ;
5384
54- type _InferFlagsFromCommand < O extends Record < string , TaggedFlagBuilder < FlagTag , unknown , unknown > > > = {
55- [ K in keyof O as CamelCase < string & K > ] : InferFlagTypeFromFlag < O [ K ] > ;
85+ type _InferFlagsFromCommand <
86+ O extends Record < string , TaggedFlagBuilder < FlagTag , unknown , unknown , unknown > > ,
87+ OptionalIfHasDefault = false ,
88+ > = {
89+ [ K in keyof O as CamelCase < string & K > ] : InferFlagTypeFromFlag < O [ K ] , OptionalIfHasDefault > ;
5690} ;
5791
5892type InferArgsFromCommand < O extends Record < string , TaggedArgBuilder < ArgTag , unknown > > | undefined > = O extends undefined
5993 ? Record < string , unknown >
6094 : _InferArgsFromCommand < Exclude < O , undefined > > ;
6195
62- type InferFlagsFromCommand < O extends Record < string , TaggedFlagBuilder < FlagTag , unknown , unknown > > | undefined > =
63- ( O extends undefined ? Record < string , unknown > : _InferFlagsFromCommand < Exclude < O , undefined > > ) & { json : boolean } ;
96+ type InferFlagsFromCommand <
97+ O extends Record < string , TaggedFlagBuilder < FlagTag , unknown , unknown , unknown > > | undefined ,
98+ OptionalIfHasDefault = false ,
99+ > = ( O extends undefined
100+ ? Record < string , unknown >
101+ : _InferFlagsFromCommand < Exclude < O , undefined > , OptionalIfHasDefault > ) & { json : boolean } ;
64102
65103function camelCaseString ( str : string ) : string {
66104 return str . replace ( / [ - _ \s ] ( .) / g, ( _ , group1 ) => group1 . toUpperCase ( ) ) ;
@@ -74,16 +112,12 @@ function camelCaseToKebabCase(str: string): string {
74112 return str . replace ( / ( [ A - Z ] ) / g, '-$1' ) . toLowerCase ( ) ;
75113}
76114
77- function kebabCaseToSnakeCase ( str : string ) : string {
78- return str . replaceAll ( '-' , '_' ) . toLowerCase ( ) ;
79- }
80-
81115export abstract class ApifyCommand < T extends typeof BuiltApifyCommand = typeof BuiltApifyCommand > {
82116 static args ?: Record < string , TaggedArgBuilder < ArgTag , unknown > > & {
83117 json ?: 'Do not use json as the key of an argument, as it will prevent the --json flag from working' ;
84118 } ;
85119
86- static flags ?: Record < string , TaggedFlagBuilder < FlagTag , unknown , unknown > > & {
120+ static flags ?: Record < string , TaggedFlagBuilder < FlagTag , unknown , unknown , unknown > > & {
87121 json ?: 'Do not use json as the key of a flag, override the enableJsonFlag static property instead' ;
88122 } ;
89123
@@ -101,7 +135,7 @@ export abstract class ApifyCommand<T extends typeof BuiltApifyCommand = typeof B
101135
102136 static hiddenAliases ?: string [ ] ;
103137
104- protected telemetryData ! : Record < string , unknown > ;
138+ protected telemetryData : Record < string , unknown > = { } ;
105139
106140 protected flags ! : InferFlagsFromCommand < T [ 'flags' ] > ;
107141 protected args ! : InferArgsFromCommand < T [ 'args' ] > ;
@@ -116,6 +150,10 @@ export abstract class ApifyCommand<T extends typeof BuiltApifyCommand = typeof B
116150 return amount === 1 ? singular : plural ;
117151 }
118152
153+ protected printHelp ( ) {
154+ console . log ( `TODO!!! Command ${ this . ctor . name } wants to print help manually` ) ;
155+ }
156+
119157 private async _run ( rawArgs : ArgumentsCamelCase ) {
120158 // Cheating a bit here with the types, but its fine
121159 // eslint-disable-next-line @typescript-eslint/no-explicit-any -- makes parsing easier
@@ -193,11 +231,15 @@ export abstract class ApifyCommand<T extends typeof BuiltApifyCommand = typeof B
193231 }
194232 }
195233
234+ // TODO: remove me
235+ // console.log({ rawInput: rawArgs, args: this.args, flags: this.flags });
236+
196237 try {
197238 await this . run ( ) ;
198- } catch ( err ) {
239+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
240+ } catch ( err : any ) {
199241 // TODO: handle errors gracefully with a logger
200- console . error ( err ) ;
242+ console . error ( err . message ) ;
201243 }
202244 }
203245
@@ -256,10 +298,8 @@ export abstract class ApifyCommand<T extends typeof BuiltApifyCommand = typeof B
256298 }
257299
258300 const flagKey = kebabCaseString ( camelCaseToKebabCase ( key ) ) . toLowerCase ( ) ;
259- const camelCaseKey = camelCaseString ( key ) ;
260- const snakeCaseKey = kebabCaseToSnakeCase ( flagKey ) ;
261301
262- finalYargs = internalBuilderData . builder ( finalYargs , flagKey , [ camelCaseKey , snakeCaseKey ] ) ;
302+ finalYargs = internalBuilderData . builder ( finalYargs , flagKey ) ;
263303 }
264304 }
265305
@@ -315,6 +355,96 @@ export abstract class ApifyCommand<T extends typeof BuiltApifyCommand = typeof B
315355 }
316356}
317357
358+ // Utility type to extract only the keys that are optional
359+ type ExtractOptionalFlagKeys < Cmd extends typeof BuiltApifyCommand > = {
360+ [ K in keyof InferFlagsFromCommand < Cmd [ 'flags' ] , true > ] : [ undefined ] extends [
361+ InferFlagsFromCommand < Cmd [ 'flags' ] , true > [ K ] ,
362+ ]
363+ ? K
364+ : never ;
365+ } [ keyof InferFlagsFromCommand < Cmd [ 'flags' ] , true > ] ;
366+
367+ type ExtractOptionalArgKeys < Cmd extends typeof BuiltApifyCommand > = {
368+ [ K in keyof InferArgsFromCommand < Cmd [ 'args' ] > ] : [ undefined ] extends [ InferArgsFromCommand < Cmd [ 'args' ] > [ K ] ]
369+ ? K
370+ : never ;
371+ } [ keyof InferArgsFromCommand < Cmd [ 'args' ] > ] ;
372+
373+ // Messy type...
374+ type StaticArgsFlagsInput < Cmd extends typeof BuiltApifyCommand > = Omit <
375+ {
376+ // This ensures we only get the required args
377+ [ K in Exclude <
378+ keyof InferArgsFromCommand < Cmd [ 'args' ] > ,
379+ ExtractOptionalArgKeys < Cmd >
380+ > as `args_${string & K } `] : InferArgsFromCommand < Cmd [ 'args' ] > [ K ] ;
381+ } ,
382+ // Omit args_json as it is used only to throw an error if the user provides it
383+ 'args_json'
384+ > &
385+ Omit <
386+ {
387+ // Fill in the rest of the args, this will not override what the code above does
388+ [ K in keyof InferArgsFromCommand < Cmd [ 'args' ] > as `args_${string & K } `] ?: InferArgsFromCommand <
389+ Cmd [ 'args' ]
390+ > [ K ] ;
391+ } ,
392+ 'args_json'
393+ > &
394+ Omit <
395+ {
396+ // This ensures we only ever get the required flags into this object, as `key?: type` and `key: type | undefined` are not the same (one is optionally present, the other is mandatory)
397+ [ K in Exclude <
398+ keyof InferFlagsFromCommand < Cmd [ 'flags' ] , true > ,
399+ ExtractOptionalFlagKeys < Cmd >
400+ > as `flags_${string & K } `] : InferFlagsFromCommand < Cmd [ 'flags' ] , true > [ K ] ;
401+ } ,
402+ // Omit flags_json as it is used only to throw an error if the user provides it
403+ 'flags_json'
404+ > &
405+ Omit <
406+ {
407+ // Fill in the rest of the flags, this will not override what the code above does
408+ [ K in keyof InferFlagsFromCommand < Cmd [ 'flags' ] , true > as `flags_${string & K } `] ?: InferFlagsFromCommand <
409+ Cmd [ 'flags' ] ,
410+ true
411+ > [ K ] ;
412+ } ,
413+ // Omit flags_json as it is used only to throw an error if the user provides it
414+ 'flags_json'
415+ > & {
416+ // Define it at the end exactly like it is
417+ flags_json ?: boolean ;
418+ } ;
419+
420+ export async function runCommand < Cmd extends typeof BuiltApifyCommand > (
421+ command : Cmd ,
422+ argsFlags : StaticArgsFlagsInput < Cmd > ,
423+ ) {
424+ // This is very much yolo'd in, but its purpose is for testing only
425+ const rawObject : ArgumentsCamelCase = {
426+ _ : [ ] ,
427+ $0 : 'apify' ,
428+ } ;
429+
430+ for ( const [ key , value ] of Object . entries ( argsFlags ) ) {
431+ const [ type , rawKey ] = key . split ( '_' ) ;
432+
433+ if ( type === 'args' ) {
434+ rawObject [ rawKey ] = value ;
435+ } else {
436+ const yargsFlagName = kebabCaseString ( camelCaseToKebabCase ( rawKey ) ) . toLowerCase ( ) ;
437+
438+ rawObject [ yargsFlagName ] = value ;
439+ }
440+ }
441+
442+ const instance = new ( command as typeof BuiltApifyCommand ) ( ) ;
443+
444+ // eslint-disable-next-line dot-notation
445+ await instance [ '_run' ] ( rawObject ) ;
446+ }
447+
318448export declare class BuiltApifyCommand extends ApifyCommand {
319- override run ( ) : void ;
449+ override run ( ) : Awaitable < void > ;
320450}
0 commit comments