@@ -15,7 +15,7 @@ import { UnsuccessfulWorkflowExecution } from '@angular-devkit/schematics';
1515import { NodeWorkflow } from '@angular-devkit/schematics/tools' ;
1616import * as ansiColors from 'ansi-colors' ;
1717import * as inquirer from 'inquirer' ;
18- import minimist from 'minimist ' ;
18+ import yargsParser from 'yargs-parser ' ;
1919
2020/**
2121 * 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
3535 let collection = '@angular-devkit/schematics-cli' ;
3636
3737 let schematic = str ;
38- if ( schematic && schematic . indexOf ( ':' ) != - 1 ) {
38+ if ( schematic ?. includes ( ':' ) ) {
39+ const lastIndexOfColon = schematic . lastIndexOf ( ':' ) ;
3940 [ collection , schematic ] = [
40- schematic . slice ( 0 , schematic . lastIndexOf ( ':' ) ) ,
41- schematic . substring ( schematic . lastIndexOf ( ':' ) + 1 ) ,
41+ schematic . slice ( 0 , lastIndexOfColon ) ,
42+ schematic . substring ( lastIndexOfColon + 1 ) ,
4243 ] ;
4344 }
4445
@@ -113,41 +114,41 @@ export async function main({
113114 stdout = process . stdout ,
114115 stderr = process . stderr ,
115116} : MainOptions ) : Promise < 0 | 1 > {
116- const argv = parseArgs ( args ) ;
117+ const { cliOptions , schematicOptions , _ } = parseArgs ( args ) ;
117118
118119 // Create a separate instance to prevent unintended global changes to the color configuration
119120 // Create function is not defined in the typings. See: https://github.com/doowb/ansi-colors/pull/44
120121 const colors = ( ansiColors as typeof ansiColors & { create : ( ) => typeof ansiColors } ) . create ( ) ;
121122
122123 /** Create the DevKit Logger used through the CLI. */
123- const logger = createConsoleLogger ( argv [ ' verbose' ] , stdout , stderr , {
124+ const logger = createConsoleLogger ( ! ! cliOptions . verbose , stdout , stderr , {
124125 info : ( s ) => s ,
125126 debug : ( s ) => s ,
126127 warn : ( s ) => colors . bold . yellow ( s ) ,
127128 error : ( s ) => colors . bold . red ( s ) ,
128129 fatal : ( s ) => colors . bold . red ( s ) ,
129130 } ) ;
130131
131- if ( argv . help ) {
132+ if ( cliOptions . help ) {
132133 logger . info ( getUsage ( ) ) ;
133134
134135 return 0 ;
135136 }
136137
137138 /** Get the collection an schematic name from the first argument. */
138139 const { collection : collectionName , schematic : schematicName } = parseSchematicName (
139- argv . _ . shift ( ) || null ,
140+ _ . shift ( ) || null ,
140141 ) ;
141142
142143 const isLocalCollection = collectionName . startsWith ( '.' ) || collectionName . startsWith ( '/' ) ;
143144
144145 /** 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' ] ;
151152
152153 /** Create the workflow scoped to the working directory that will be executed with this run. */
153154 const workflow = new NodeWorkflow ( process . cwd ( ) , {
@@ -158,7 +159,7 @@ export async function main({
158159 } ) ;
159160
160161 /** If the user wants to list schematics, we simply show all the schematic names. */
161- if ( argv [ 'list-schematics' ] ) {
162+ if ( cliOptions [ 'list-schematics' ] ) {
162163 return _listSchematics ( workflow , collectionName , logger ) ;
163164 }
164165
@@ -236,39 +237,16 @@ export async function main({
236237 }
237238 } ) ;
238239
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-
256240 // Show usage of deprecated options
257241 workflow . registry . useXDeprecatedProvider ( ( msg ) => logger . warn ( msg ) ) ;
258242
259243 // 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+ ) ;
269247
270248 // Add prompts.
271- if ( argv [ ' interactive' ] && isTTY ( ) ) {
249+ if ( cliOptions . interactive && isTTY ( ) ) {
272250 workflow . registry . usePromptProvider ( _createPromptProvider ( ) ) ;
273251 }
274252
@@ -285,7 +263,7 @@ export async function main({
285263 . execute ( {
286264 collection : collectionName ,
287265 schematic : schematicName ,
288- options : parsedArgs ,
266+ options : schematicOptions ,
289267 allowPrivate : allowPrivate ,
290268 debug : debug ,
291269 logger : logger ,
@@ -308,9 +286,9 @@ export async function main({
308286 // "See above" because we already printed the error.
309287 logger . fatal ( 'The Schematic workflow failed. See above.' ) ;
310288 } else if ( debug ) {
311- logger . fatal ( ' An error occured:\n' + err . stack ) ;
289+ logger . fatal ( ` An error occured:\n${ err . stack } ` ) ;
312290 } else {
313- logger . fatal ( err . stack || err . message ) ;
291+ logger . fatal ( `Error: ${ err . message } ` ) ;
314292 }
315293
316294 return 1 ;
@@ -322,7 +300,7 @@ export async function main({
322300 */
323301function getUsage ( ) : string {
324302 return tags . stripIndent `
325- schematics [CollectionName:]SchematicName [options, ...]
303+ schematics [collection-name:]schematic-name [options, ...]
326304
327305 By default, if the collection name is not specified, use the internal collection provided
328306 by the Schematics CLI.
@@ -354,34 +332,75 @@ function getUsage(): string {
354332
355333/** Parse the command line. */
356334const booleanArgs = [
357- 'allowPrivate' ,
358335 'allow-private' ,
359336 'debug' ,
360337 'dry-run' ,
361- 'dryRun' ,
362338 'force' ,
363339 'help' ,
364340 'list-schematics' ,
365- 'listSchematics' ,
366341 'verbose' ,
367342 '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 [ ] ,
378359 default : {
379360 'interactive' : true ,
380361 '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 ,
382369 } ,
383- '--' : true ,
384370 } ) ;
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+ } ;
385404}
386405
387406function isTTY ( ) : boolean {
0 commit comments