99
1010import { Architect } from '@angular-devkit/architect' ;
1111import { WorkspaceNodeModulesArchitectHost } from '@angular-devkit/architect/node' ;
12- import { JsonValue , json , logging , schema , tags , workspaces } from '@angular-devkit/core' ;
12+ import { JsonValue , json , logging , schema , strings , tags , workspaces } from '@angular-devkit/core' ;
1313import { NodeJsSyncHost , createConsoleLogger } from '@angular-devkit/core/node' ;
1414import { existsSync } from 'node:fs' ;
1515import * as path from 'node:path' ;
16- import { styleText } from 'node:util' ;
17- import yargsParser , { camelCase , decamelize } from 'yargs-parser' ;
16+ import { parseArgs , styleText } from 'node:util' ;
1817
1918function findUp ( names : string | string [ ] , from : string ) {
2019 if ( ! Array . isArray ( names ) ) {
@@ -62,36 +61,22 @@ async function _executeTarget(
6261 parentLogger : logging . Logger ,
6362 workspace : workspaces . WorkspaceDefinition ,
6463 root : string ,
65- argv : ReturnType < typeof yargsParser > ,
64+ targetStr : string ,
65+ options : json . JsonObject ,
6666 registry : schema . SchemaRegistry ,
6767) {
6868 const architectHost = new WorkspaceNodeModulesArchitectHost ( workspace , root ) ;
6969 const architect = new Architect ( architectHost , registry ) ;
7070
7171 // Split a target into its parts.
72- const {
73- _ : [ targetStr = '' ] ,
74- help,
75- ...options
76- } = argv ;
77- const [ project , target , configuration ] = targetStr . toString ( ) . split ( ':' ) ;
72+ const [ project , target , configuration ] = targetStr . split ( ':' ) ;
7873 const targetSpec = { project , target, configuration } ;
7974
8075 const logger = new logging . Logger ( 'jobs' ) ;
8176 const logs : logging . LogEntry [ ] = [ ] ;
8277 logger . subscribe ( ( entry ) => logs . push ( { ...entry , message : `${ entry . name } : ` + entry . message } ) ) ;
8378
84- // Camelize options as yargs will return the object in kebab-case when camel casing is disabled.
85- const camelCasedOptions : json . JsonObject = { } ;
86- for ( const [ key , value ] of Object . entries ( options ) ) {
87- if ( / [ A - Z ] / . test ( key ) ) {
88- throw new Error ( `Unknown argument ${ key } . Did you mean ${ decamelize ( key ) } ?` ) ;
89- }
90-
91- camelCasedOptions [ camelCase ( key ) ] = value as JsonValue ;
92- }
93-
94- const run = await architect . scheduleTarget ( targetSpec , camelCasedOptions , { logger } ) ;
79+ const run = await architect . scheduleTarget ( targetSpec , options , { logger } ) ;
9580
9681 // Wait for full completion of the builder.
9782 try {
@@ -122,20 +107,108 @@ async function _executeTarget(
122107 }
123108}
124109
110+ const CLI_OPTION_DEFINITIONS = {
111+ 'help ': { type : 'boolean ' } ,
112+ 'verbose ': { type : 'boolean ' } ,
113+ } as const ;
114+
115+ interface Options {
116+ positionals : string [ ] ;
117+ builderOptions : json . JsonObject ;
118+ cliOptions : Partial < Record < keyof typeof CLI_OPTION_DEFINITIONS , boolean >> ;
119+ }
120+
121+ /** Parse the command line. */
122+ function parseOptions ( args : string [ ] ) : Options {
123+ const { values, tokens } = parseArgs ( {
124+ args,
125+ strict : false ,
126+ tokens : true ,
127+ allowPositionals : true ,
128+ allowNegative : true ,
129+ options : CLI_OPTION_DEFINITIONS ,
130+ } ) ;
131+
132+ const builderOptions : json . JsonObject = { } ;
133+ const positionals : string [ ] = [ ] ;
134+
135+ for ( let i = 0 ; i < tokens . length ; i ++ ) {
136+ const token = tokens [ i ] ;
137+
138+ if ( token . kind === 'positional' ) {
139+ positionals . push ( token . value ) ;
140+ continue ;
141+ }
142+
143+ if ( token . kind !== 'option' ) {
144+ continue ;
145+ }
146+
147+ const name = token . name ;
148+ let value : JsonValue = token . value ?? true ;
149+
150+ // `parseArgs` already handled known boolean args and their --no- forms.
151+ // Only process options not in CLI_OPTION_DEFINITIONS here.
152+ if ( name in CLI_OPTION_DEFINITIONS ) {
153+ continue ;
154+ }
155+
156+ if ( / [ A - Z ] / . test ( name ) ) {
157+ throw new Error (
158+ `Unknown argument ${ name } . Did you mean ${ strings . decamelize ( name ) . replaceAll ( '_' , '-' ) } ?` ,
159+ ) ;
160+ }
161+
162+ // Handle --no-flag for unknown options, treating it as false
163+ if ( name . startsWith ( 'no-' ) ) {
164+ const realName = name . slice ( 3 ) ;
165+ builderOptions [ strings . camelize ( realName ) ] = false ;
166+ continue ;
167+ }
168+
169+ // Handle value for unknown options
170+ if ( token . inlineValue === undefined ) {
171+ // Look ahead
172+ const nextToken = tokens [ i + 1 ] ;
173+ if ( nextToken ?. kind === 'positional' ) {
174+ value = nextToken . value ;
175+ i ++ ; // Consume next token
176+ } else {
177+ value = true ; // Treat as boolean if no value follows
178+ }
179+ }
180+
181+ // Type inference for numbers
182+ if ( typeof value === 'string' && ! isNaN ( Number ( value ) ) ) {
183+ value = Number ( value ) ;
184+ }
185+
186+ const camelName = strings . camelize ( name ) ;
187+ if ( Object . prototype . hasOwnProperty . call ( builderOptions , camelName ) ) {
188+ const existing = builderOptions [ camelName ] ;
189+ if ( Array . isArray ( existing ) ) {
190+ existing . push ( value ) ;
191+ } else {
192+ builderOptions [ camelName ] = [ existing , value ] as JsonValue ;
193+ }
194+ } else {
195+ builderOptions [ camelName ] = value ;
196+ }
197+ }
198+
199+ return {
200+ positionals,
201+ builderOptions,
202+ cliOptions : values as Options [ 'cliOptions' ] ,
203+ } ;
204+ }
205+
125206async function main ( args : string [ ] ) : Promise < number > {
126207 /** Parse the command line. */
127- const argv = yargsParser ( args , {
128- boolean : [ 'help' ] ,
129- configuration : {
130- 'dot-notation' : false ,
131- 'boolean-negation' : true ,
132- 'strip-aliased' : true ,
133- 'camel-case-expansion' : false ,
134- } ,
135- } ) ;
208+ const { positionals, cliOptions, builderOptions } = parseOptions ( args ) ;
136209
137210 /** Create the DevKit Logger used through the CLI. */
138- const logger = createConsoleLogger ( argv [ 'verbose' ] , process . stdout , process . stderr , {
211+ const logger = createConsoleLogger ( ! ! cliOptions [ 'verbose' ] , process . stdout , process . stderr , {
139212 info : ( s ) => s ,
140213 debug : ( s ) => s ,
141214 warn : ( s ) => styleText ( [ 'yellow' , 'bold' ] , s ) ,
@@ -144,8 +217,8 @@ async function main(args: string[]): Promise<number> {
144217 } ) ;
145218
146219 // Check the target.
147- const targetStr = argv . _ [ 0 ] || '' ;
148- if ( ! targetStr || argv . help ) {
220+ const targetStr = positionals [ 0 ] ;
221+ if ( ! targetStr || cliOptions . help ) {
149222 // Show architect usage if there's no target.
150223 usage ( logger ) ;
151224 }
@@ -181,7 +254,7 @@ async function main(args: string[]): Promise<number> {
181254 // Clear the console.
182255 process . stdout . write ( '\u001Bc' ) ;
183256
184- return await _executeTarget ( logger , workspace , root , argv , registry ) ;
257+ return await _executeTarget ( logger , workspace , root , targetStr , builderOptions , registry ) ;
185258}
186259
187260main ( process . argv . slice ( 2 ) ) . then (
0 commit comments