@@ -15,7 +15,7 @@ import { UnsuccessfulWorkflowExecution } from '@angular-devkit/schematics';
15
15
import { NodeWorkflow } from '@angular-devkit/schematics/tools' ;
16
16
import * as ansiColors from 'ansi-colors' ;
17
17
import * as inquirer from 'inquirer' ;
18
- import minimist from 'minimist ' ;
18
+ import yargsParser from 'yargs-parser ' ;
19
19
20
20
/**
21
21
* Parse the name of schematic passed in argument, and return a {collection, schematic} named
@@ -35,10 +35,11 @@ function parseSchematicName(str: string | null): { collection: string; schematic
35
35
let collection = '@angular-devkit/schematics-cli' ;
36
36
37
37
let schematic = str ;
38
- if ( schematic && schematic . indexOf ( ':' ) != - 1 ) {
38
+ if ( schematic ?. includes ( ':' ) ) {
39
+ const lastIndexOfColon = schematic . lastIndexOf ( ':' ) ;
39
40
[ collection , schematic ] = [
40
- schematic . slice ( 0 , schematic . lastIndexOf ( ':' ) ) ,
41
- schematic . substring ( schematic . lastIndexOf ( ':' ) + 1 ) ,
41
+ schematic . slice ( 0 , lastIndexOfColon ) ,
42
+ schematic . substring ( lastIndexOfColon + 1 ) ,
42
43
] ;
43
44
}
44
45
@@ -113,41 +114,41 @@ export async function main({
113
114
stdout = process . stdout ,
114
115
stderr = process . stderr ,
115
116
} : MainOptions ) : Promise < 0 | 1 > {
116
- const argv = parseArgs ( args ) ;
117
+ const { cliOptions , schematicOptions , _ } = parseArgs ( args ) ;
117
118
118
119
// Create a separate instance to prevent unintended global changes to the color configuration
119
120
// Create function is not defined in the typings. See: https://github.com/doowb/ansi-colors/pull/44
120
121
const colors = ( ansiColors as typeof ansiColors & { create : ( ) => typeof ansiColors } ) . create ( ) ;
121
122
122
123
/** Create the DevKit Logger used through the CLI. */
123
- const logger = createConsoleLogger ( argv [ ' verbose' ] , stdout , stderr , {
124
+ const logger = createConsoleLogger ( ! ! cliOptions . verbose , stdout , stderr , {
124
125
info : ( s ) => s ,
125
126
debug : ( s ) => s ,
126
127
warn : ( s ) => colors . bold . yellow ( s ) ,
127
128
error : ( s ) => colors . bold . red ( s ) ,
128
129
fatal : ( s ) => colors . bold . red ( s ) ,
129
130
} ) ;
130
131
131
- if ( argv . help ) {
132
+ if ( cliOptions . help ) {
132
133
logger . info ( getUsage ( ) ) ;
133
134
134
135
return 0 ;
135
136
}
136
137
137
138
/** Get the collection an schematic name from the first argument. */
138
139
const { collection : collectionName , schematic : schematicName } = parseSchematicName (
139
- argv . _ . shift ( ) || null ,
140
+ _ . shift ( ) || null ,
140
141
) ;
141
142
142
143
const isLocalCollection = collectionName . startsWith ( '.' ) || collectionName . startsWith ( '/' ) ;
143
144
144
145
/** Gather the arguments for later use. */
145
- const debugPresent = argv [ ' debug' ] !== null ;
146
- const debug = debugPresent ? ! ! argv [ ' debug' ] : isLocalCollection ;
147
- const dryRunPresent = argv [ 'dry-run' ] !== null ;
148
- const dryRun = dryRunPresent ? ! ! argv [ 'dry-run' ] : debug ;
149
- const force = argv [ ' force' ] ;
150
- const allowPrivate = argv [ 'allow-private' ] ;
146
+ const debugPresent = cliOptions . debug !== null ;
147
+ const debug = debugPresent ? ! ! cliOptions . debug : isLocalCollection ;
148
+ const dryRunPresent = cliOptions [ 'dry-run' ] !== null ;
149
+ const dryRun = dryRunPresent ? ! ! cliOptions [ 'dry-run' ] : debug ;
150
+ const force = ! ! cliOptions . force ;
151
+ const allowPrivate = ! ! cliOptions [ 'allow-private' ] ;
151
152
152
153
/** Create the workflow scoped to the working directory that will be executed with this run. */
153
154
const workflow = new NodeWorkflow ( process . cwd ( ) , {
@@ -158,7 +159,7 @@ export async function main({
158
159
} ) ;
159
160
160
161
/** If the user wants to list schematics, we simply show all the schematic names. */
161
- if ( argv [ 'list-schematics' ] ) {
162
+ if ( cliOptions [ 'list-schematics' ] ) {
162
163
return _listSchematics ( workflow , collectionName , logger ) ;
163
164
}
164
165
@@ -236,39 +237,16 @@ export async function main({
236
237
}
237
238
} ) ;
238
239
239
- /**
240
- * Remove every options from argv that we support in schematics itself.
241
- */
242
- const parsedArgs = Object . assign ( { } , argv ) as Record < string , unknown > ;
243
- delete parsedArgs [ '--' ] ;
244
- for ( const key of booleanArgs ) {
245
- delete parsedArgs [ key ] ;
246
- }
247
-
248
- /**
249
- * Add options from `--` to args.
250
- */
251
- const argv2 = minimist ( argv [ '--' ] ) ;
252
- for ( const key of Object . keys ( argv2 ) ) {
253
- parsedArgs [ key ] = argv2 [ key ] ;
254
- }
255
-
256
240
// Show usage of deprecated options
257
241
workflow . registry . useXDeprecatedProvider ( ( msg ) => logger . warn ( msg ) ) ;
258
242
259
243
// Pass the rest of the arguments as the smart default "argv". Then delete it.
260
- workflow . registry . addSmartDefaultProvider ( 'argv' , ( schema ) => {
261
- if ( 'index' in schema ) {
262
- return argv . _ [ Number ( schema [ 'index' ] ) ] ;
263
- } else {
264
- return argv . _ ;
265
- }
266
- } ) ;
267
-
268
- delete parsedArgs . _ ;
244
+ workflow . registry . addSmartDefaultProvider ( 'argv' , ( schema ) =>
245
+ 'index' in schema ? _ [ Number ( schema [ 'index' ] ) ] : _ ,
246
+ ) ;
269
247
270
248
// Add prompts.
271
- if ( argv [ ' interactive' ] && isTTY ( ) ) {
249
+ if ( cliOptions . interactive && isTTY ( ) ) {
272
250
workflow . registry . usePromptProvider ( _createPromptProvider ( ) ) ;
273
251
}
274
252
@@ -285,7 +263,7 @@ export async function main({
285
263
. execute ( {
286
264
collection : collectionName ,
287
265
schematic : schematicName ,
288
- options : parsedArgs ,
266
+ options : schematicOptions ,
289
267
allowPrivate : allowPrivate ,
290
268
debug : debug ,
291
269
logger : logger ,
@@ -308,9 +286,9 @@ export async function main({
308
286
// "See above" because we already printed the error.
309
287
logger . fatal ( 'The Schematic workflow failed. See above.' ) ;
310
288
} else if ( debug ) {
311
- logger . fatal ( ' An error occured:\n' + err . stack ) ;
289
+ logger . fatal ( ` An error occured:\n${ err . stack } ` ) ;
312
290
} else {
313
- logger . fatal ( err . stack || err . message ) ;
291
+ logger . fatal ( `Error: ${ err . message } ` ) ;
314
292
}
315
293
316
294
return 1 ;
@@ -322,7 +300,7 @@ export async function main({
322
300
*/
323
301
function getUsage ( ) : string {
324
302
return tags . stripIndent `
325
- schematics [CollectionName:]SchematicName [options, ...]
303
+ schematics [collection-name:]schematic-name [options, ...]
326
304
327
305
By default, if the collection name is not specified, use the internal collection provided
328
306
by the Schematics CLI.
@@ -354,34 +332,75 @@ function getUsage(): string {
354
332
355
333
/** Parse the command line. */
356
334
const booleanArgs = [
357
- 'allowPrivate' ,
358
335
'allow-private' ,
359
336
'debug' ,
360
337
'dry-run' ,
361
- 'dryRun' ,
362
338
'force' ,
363
339
'help' ,
364
340
'list-schematics' ,
365
- 'listSchematics' ,
366
341
'verbose' ,
367
342
'interactive' ,
368
- ] ;
369
-
370
- function parseArgs ( args : string [ ] | undefined ) : minimist . ParsedArgs {
371
- return minimist ( args , {
372
- boolean : booleanArgs ,
373
- alias : {
374
- 'dryRun' : 'dry-run' ,
375
- 'listSchematics' : 'list-schematics' ,
376
- 'allowPrivate' : 'allow-private' ,
377
- } ,
343
+ ] as const ;
344
+
345
+ type ElementType < T extends ReadonlyArray < unknown >> = T extends ReadonlyArray < infer ElementType >
346
+ ? ElementType
347
+ : never ;
348
+
349
+ interface Options {
350
+ _: string [ ] ;
351
+ schematicOptions: Record < string , unknown > ;
352
+ cliOptions: Partial < Record < ElementType < typeof booleanArgs > , boolean | null >> ;
353
+ }
354
+
355
+ /** Parse the command line. */
356
+ function parseArgs ( args : string [ ] ) : Options {
357
+ const { _, ...options } = yargsParser ( args , {
358
+ boolean : booleanArgs as unknown as string [ ] ,
378
359
default : {
379
360
'interactive' : true ,
380
361
'debug' : null ,
381
- 'dryRun' : null ,
362
+ 'dry-run' : null ,
363
+ } ,
364
+ configuration : {
365
+ 'dot-notation' : false ,
366
+ 'boolean-negation' : true ,
367
+ 'strip-aliased' : true ,
368
+ 'camel-case-expansion' : false ,
382
369
} ,
383
- '--' : true ,
384
370
} ) ;
371
+
372
+ // Camelize options as yargs will return the object in kebab-case when camel casing is disabled.
373
+ const schematicOptions : Options [ 'schematicOptions' ] = { } ;
374
+ const cliOptions : Options [ 'cliOptions' ] = { } ;
375
+
376
+ const isCliOptions = (
377
+ key : ElementType < typeof booleanArgs > | string ,
378
+ ) : key is ElementType < typeof booleanArgs > =>
379
+ booleanArgs . includes ( key as ElementType < typeof booleanArgs > ) ;
380
+
381
+ // Casting temporary until https://github.com/DefinitelyTyped/DefinitelyTyped/pull/59065 is merged and released.
382
+ const { camelCase, decamelize } = yargsParser as yargsParser . Parser & {
383
+ camelCase ( str : string ) : string ;
384
+ decamelize ( str : string , joinString ? : string ) : string ;
385
+ } ;
386
+
387
+ for ( const [ key , value ] of Object . entries ( options ) ) {
388
+ if ( / [ A - Z ] / . test ( key ) ) {
389
+ throw new Error ( `Unknown argument ${ key } . Did you mean ${ decamelize ( key ) } ?` ) ;
390
+ }
391
+
392
+ if ( isCliOptions ( key ) ) {
393
+ cliOptions [ key ] = value ;
394
+ } else {
395
+ schematicOptions[ camelCase ( key ) ] = value ;
396
+ }
397
+ }
398
+
399
+ return {
400
+ _,
401
+ schematicOptions,
402
+ cliOptions,
403
+ } ;
385
404
}
386
405
387
406
function isTTY ( ) : boolean {
0 commit comments