@@ -3,7 +3,7 @@ const path = require('path');
3
3
const { pathToFileURL } = require ( 'url' ) ;
4
4
const Module = require ( 'module' ) ;
5
5
6
- const { program } = require ( 'commander' ) ;
6
+ const { program, Option } = require ( 'commander' ) ;
7
7
const utils = require ( './utils' ) ;
8
8
9
9
class WebpackCLI {
@@ -116,107 +116,182 @@ class WebpackCLI {
116
116
}
117
117
118
118
makeOption ( command , option ) {
119
- let type = option . type ;
120
- let isMultipleTypes = Array . isArray ( type ) ;
121
- let isOptional = false ;
122
-
123
- if ( isMultipleTypes ) {
124
- if ( type . length === 1 ) {
125
- type = type [ 0 ] ;
126
- isMultipleTypes = false ;
127
- } else {
128
- isOptional = type . includes ( Boolean ) ;
119
+ let mainOption ;
120
+ let negativeOption ;
121
+
122
+ if ( option . configs ) {
123
+ let needNegativeOption = false ;
124
+ let mainOptionType = new Set ( ) ;
125
+
126
+ option . configs . forEach ( ( config ) => {
127
+ // Possible value: "enum" | "string" | "path" | "number" | "boolean" | "RegExp" | "reset"
128
+ switch ( config . type ) {
129
+ case 'reset' :
130
+ mainOptionType . add ( Boolean ) ;
131
+ break ;
132
+ case 'boolean' :
133
+ if ( ! needNegativeOption ) {
134
+ needNegativeOption = true ;
135
+ }
136
+
137
+ mainOptionType . add ( Boolean ) ;
138
+ break ;
139
+ case 'number' :
140
+ mainOptionType . add ( Number ) ;
141
+ break ;
142
+ case 'string' :
143
+ case 'path' :
144
+ case 'RegExp' :
145
+ mainOptionType . add ( String ) ;
146
+ break ;
147
+ case 'enum' : {
148
+ let hasFalseEnum = false ;
149
+
150
+ const enumTypes = config . values . map ( ( value ) => {
151
+ switch ( typeof value ) {
152
+ case 'string' :
153
+ mainOptionType . add ( String ) ;
154
+ break ;
155
+ case 'number' :
156
+ mainOptionType . add ( Number ) ;
157
+ break ;
158
+ case 'boolean' :
159
+ if ( ! hasFalseEnum && value === false ) {
160
+ hasFalseEnum = true ;
161
+ break ;
162
+ }
163
+
164
+ mainOptionType . add ( Boolean ) ;
165
+ break ;
166
+ }
167
+ } ) ;
168
+
169
+ if ( ! needNegativeOption ) {
170
+ needNegativeOption = hasFalseEnum ;
171
+ }
172
+
173
+ return enumTypes ;
174
+ }
175
+ }
176
+ } ) ;
177
+
178
+ mainOption = {
179
+ flags : option . alias ? `-${ option . alias } , --${ option . name } ` : `--${ option . name } ` ,
180
+ description : option . description || '' ,
181
+ type : mainOptionType ,
182
+ multiple : option . multiple ,
183
+ } ;
184
+
185
+ if ( needNegativeOption ) {
186
+ negativeOption = {
187
+ flags : `--no-${ option . name } ` ,
188
+ description : option . negatedDescription ? option . negatedDescription : `Negative '${ option . name } ' option.` ,
189
+ } ;
190
+ }
191
+ } else {
192
+ mainOption = {
193
+ flags : option . alias ? `-${ option . alias } , --${ option . name } ` : `--${ option . name } ` ,
194
+ // TODO `describe` used by `webpack-dev-server@3`
195
+ description : option . description || option . describe || '' ,
196
+ type : option . type ? new Set ( Array . isArray ( option . type ) ? option . type : [ option . type ] ) : new Set ( [ Boolean ] ) ,
197
+ multiple : option . multiple ,
198
+ defaultValue : option . defaultValue ,
199
+ } ;
200
+
201
+ if ( option . negative ) {
202
+ negativeOption = {
203
+ flags : `--no-${ option . name } ` ,
204
+ description : option . negatedDescription ? option . negatedDescription : `Negative '${ option . name } ' option.` ,
205
+ } ;
129
206
}
130
207
}
131
208
132
- const isMultiple = option . multiple ;
133
- const isRequired = type !== Boolean && typeof type !== 'undefined' ;
209
+ if ( mainOption . type . size > 1 && mainOption . type . has ( Boolean ) ) {
210
+ mainOption . flags = `${ mainOption . flags } [value${ mainOption . multiple ? '...' : '' } ]` ;
211
+ } else if ( mainOption . type . size > 0 && ! mainOption . type . has ( Boolean ) ) {
212
+ mainOption . flags = `${ mainOption . flags } <value${ mainOption . multiple ? '...' : '' } >` ;
213
+ }
134
214
135
- let flags = option . alias ? `-${ option . alias } , --${ option . name } ` : `--${ option . name } ` ;
215
+ if ( mainOption . type . size === 1 ) {
216
+ if ( mainOption . type . has ( Number ) ) {
217
+ let skipDefault = true ;
136
218
137
- if ( isOptional ) {
138
- // `commander.js` recognizes [value] as an optional placeholder, making this flag work either as a string or a boolean
139
- flags = `${ flags } [value${ isMultiple ? '...' : '' } ]` ;
140
- } else if ( isRequired ) {
141
- // <value> is a required placeholder for any non-Boolean types
142
- flags = `${ flags } <value${ isMultiple ? '...' : '' } >` ;
143
- }
219
+ const optionForCommand = new Option ( mainOption . flags , mainOption . description )
220
+ . argParser ( ( value , prev = [ ] ) => {
221
+ if ( mainOption . defaultValue && mainOption . multiple && skipDefault ) {
222
+ prev = [ ] ;
223
+ skipDefault = false ;
224
+ }
144
225
145
- // TODO `describe` used by `webpack-dev-server@3`
146
- const description = option . description || option . describe || '' ;
147
- const defaultValue = option . defaultValue ;
226
+ return mainOption . multiple ? [ ] . concat ( prev ) . concat ( Number ( value ) ) : Number ( value ) ;
227
+ } )
228
+ . default ( mainOption . defaultValue ) ;
148
229
149
- if ( type === Boolean ) {
150
- command . option ( flags , description , defaultValue ) ;
151
- } else if ( type === Number ) {
152
- let skipDefault = true ;
230
+ command . addOption ( optionForCommand ) ;
231
+ } else if ( mainOption . type . has ( String ) ) {
232
+ let skipDefault = true ;
153
233
154
- command . option (
155
- flags ,
156
- description ,
157
- ( value , prev = [ ] ) => {
158
- if ( defaultValue && isMultiple && skipDefault ) {
159
- prev = [ ] ;
160
- skipDefault = false ;
161
- }
234
+ const optionForCommand = new Option ( mainOption . flags , mainOption . description )
235
+ . argParser ( ( value , prev = [ ] ) => {
236
+ if ( mainOption . defaultValue && mainOption . multiple && skipDefault ) {
237
+ prev = [ ] ;
238
+ skipDefault = false ;
239
+ }
162
240
163
- return isMultiple ? [ ] . concat ( prev ) . concat ( Number ( value ) ) : Number ( value ) ;
164
- } ,
165
- defaultValue ,
166
- ) ;
167
- } else if ( type === String ) {
168
- let skipDefault = true ;
241
+ return mainOption . multiple ? [ ] . concat ( prev ) . concat ( value ) : value ;
242
+ } )
243
+ . default ( mainOption . defaultValue ) ;
169
244
170
- command . option (
171
- flags ,
172
- description ,
173
- ( value , prev = [ ] ) => {
174
- if ( defaultValue && isMultiple && skipDefault ) {
175
- prev = [ ] ;
176
- skipDefault = false ;
177
- }
245
+ command . addOption ( optionForCommand ) ;
246
+ } else if ( mainOption . type . has ( Boolean ) ) {
247
+ const optionForCommand = new Option ( mainOption . flags , mainOption . description ) . default ( mainOption . defaultValue ) ;
178
248
179
- return isMultiple ? [ ] . concat ( prev ) . concat ( value ) : value ;
180
- } ,
181
- defaultValue ,
182
- ) ;
183
- } else if ( isMultipleTypes ) {
249
+ command . addOption ( optionForCommand ) ;
250
+ } else {
251
+ const optionForCommand = new Option ( mainOption . flags , mainOption . description )
252
+ . argParser ( Array . from ( mainOption . type ) [ 0 ] )
253
+ . default ( mainOption . defaultValue ) ;
254
+
255
+ command . addOption ( optionForCommand ) ;
256
+ }
257
+ } else if ( mainOption . type . size > 1 ) {
184
258
let skipDefault = true ;
185
259
186
- command . option (
187
- flags ,
188
- description ,
189
- ( value , prev = [ ] ) => {
190
- if ( defaultValue && isMultiple && skipDefault ) {
260
+ const optionForCommand = new Option ( mainOption . flags , mainOption . description , mainOption . defaultValue )
261
+ . argParser ( ( value , prev = [ ] ) => {
262
+ if ( mainOption . defaultValue && mainOption . multiple && skipDefault ) {
191
263
prev = [ ] ;
192
264
skipDefault = false ;
193
265
}
194
266
195
- if ( type . includes ( Number ) ) {
267
+ if ( mainOption . type . has ( Number ) ) {
196
268
const numberValue = Number ( value ) ;
197
269
198
270
if ( ! isNaN ( numberValue ) ) {
199
- return isMultiple ? [ ] . concat ( prev ) . concat ( numberValue ) : numberValue ;
271
+ return mainOption . multiple ? [ ] . concat ( prev ) . concat ( numberValue ) : numberValue ;
200
272
}
201
273
}
202
274
203
- if ( type . includes ( String ) ) {
204
- return isMultiple ? [ ] . concat ( prev ) . concat ( value ) : value ;
275
+ if ( mainOption . type . has ( String ) ) {
276
+ return mainOption . multiple ? [ ] . concat ( prev ) . concat ( value ) : value ;
205
277
}
206
278
207
279
return value ;
208
- } ,
209
- defaultValue ,
210
- ) ;
211
- } else {
212
- command . option ( flags , description , type , defaultValue ) ;
213
- }
280
+ } )
281
+ . default ( mainOption . defaultValue ) ;
282
+
283
+ command . addOption ( optionForCommand ) ;
284
+ } else if ( mainOption . type . size === 0 && negativeOption ) {
285
+ const optionForCommand = new Option ( mainOption . flags , mainOption . description ) ;
286
+
287
+ // Hide stub option
288
+ optionForCommand . hideHelp ( ) ;
214
289
215
- if ( option . negative ) {
216
- // commander requires explicitly adding the negated version of boolean flags
217
- const negatedFlag = `--no-${ option . name } ` ;
290
+ command . addOption ( optionForCommand ) ;
291
+ }
218
292
219
- command . option ( negatedFlag , option . negatedDescription ? option . negatedDescription : `Negative '${ option . name } ' option.` ) ;
293
+ if ( negativeOption ) {
294
+ command . addOption ( new Option ( negativeOption . flags , negativeOption . description ) ) ;
220
295
}
221
296
}
222
297
@@ -403,15 +478,6 @@ class WebpackCLI {
403
478
// A list of cli flags generated by core can be found here https://github.com/webpack/webpack/blob/master/test/__snapshots__/Cli.test.js.snap
404
479
const coreFlags = this . webpack . cli
405
480
? Object . entries ( this . webpack . cli . getArguments ( ) ) . map ( ( [ flag , meta ] ) => {
406
- if ( meta . simpleType === 'string' ) {
407
- meta . type = String ;
408
- } else if ( meta . simpleType === 'number' ) {
409
- meta . type = Number ;
410
- } else {
411
- meta . type = Boolean ;
412
- meta . negative = ! flag . endsWith ( '-reset' ) ;
413
- }
414
-
415
481
const inBuiltIn = builtInFlags . find ( ( builtInFlag ) => builtInFlag . name === flag ) ;
416
482
417
483
if ( inBuiltIn ) {
@@ -670,7 +736,7 @@ class WebpackCLI {
670
736
}
671
737
672
738
command . options . forEach ( ( option ) => {
673
- if ( this . utils . levenshtein . distance ( name , option . long . slice ( 2 ) ) < 3 ) {
739
+ if ( ! option . hidden && this . utils . levenshtein . distance ( name , option . long . slice ( 2 ) ) < 3 ) {
674
740
this . logger . error ( `Did you mean '--${ option . name ( ) } '?` ) ;
675
741
}
676
742
} ) ;
0 commit comments