Skip to content

Commit e9ea9b7

Browse files
committed
fix: always camelcase parsed options
1 parent 64e22f1 commit e9ea9b7

File tree

10 files changed

+1186
-1090
lines changed

10 files changed

+1186
-1090
lines changed

dist/index.js

Lines changed: 47 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -220,6 +220,28 @@ const getFileName = (input) => {
220220
const m = /([^\\\/]+)$/.exec(input);
221221
return m ? m[1] : '';
222222
};
223+
const camelcaseOptionName = (name) => {
224+
// Camelcase the option name
225+
// Don't camelcase anything after the dot `.`
226+
return name
227+
.split('.')
228+
.map((v, i) => {
229+
return i === 0 ? camelcase(v) : v;
230+
})
231+
.join('.');
232+
};
233+
class CACError extends Error {
234+
constructor(message) {
235+
super(message);
236+
this.name = this.constructor.name;
237+
if (typeof Error.captureStackTrace === 'function') {
238+
Error.captureStackTrace(this, this.constructor);
239+
}
240+
else {
241+
this.stack = new Error(message).stack;
242+
}
243+
}
244+
}
223245

224246
class Option {
225247
constructor(rawName, description, config) {
@@ -237,10 +259,10 @@ class Option {
237259
this.negated = true;
238260
name = name.replace(/^no-/, '');
239261
}
240-
return name;
262+
return camelcaseOptionName(name);
241263
})
242264
.sort((a, b) => (a.length > b.length ? 1 : -1)); // Sort names
243-
// Use the longese name (last one) as actual option name
265+
// Use the longest name (last one) as actual option name
244266
this.name = this.names[this.names.length - 1];
245267
if (this.negated) {
246268
this.config.default = true;
@@ -259,12 +281,9 @@ class Option {
259281
}
260282

261283
const deno = typeof window !== 'undefined' && window.Deno;
262-
const exit = (code) => {
263-
return deno ? Deno.exit(code) : process.exit(code);
264-
};
265284
const processArgs = deno ? ['deno'].concat(Deno.args) : process.argv;
266285
const platformInfo = deno
267-
? `${Deno.platform.os}-${Deno.platform.arch} deno-${Deno.version.deno}`
286+
? `${Deno.build.os}-${Deno.build.arch} deno-${Deno.version.deno}`
268287
: `${process.platform}-${process.arch} node-${process.version}`;
269288

270289
class Command {
@@ -411,21 +430,18 @@ class Command {
411430
: section.body;
412431
})
413432
.join('\n\n'));
414-
exit(0);
415433
}
416434
outputVersion() {
417435
const { name } = this.cli;
418436
const { versionNumber } = this.cli.globalCommand;
419437
if (versionNumber) {
420438
console.log(`${name}/${versionNumber} ${platformInfo}`);
421439
}
422-
exit(0);
423440
}
424441
checkRequiredArgs() {
425442
const minimalArgsCount = this.args.filter(arg => arg.required).length;
426443
if (this.cli.args.length < minimalArgsCount) {
427-
console.error(`error: missing required args for command \`${this.rawName}\``);
428-
exit(1);
444+
throw new CACError(`missing required args for command \`${this.rawName}\``);
429445
}
430446
}
431447
/**
@@ -434,14 +450,13 @@ class Command {
434450
* Exit and output error when true
435451
*/
436452
checkUnknownOptions() {
437-
const { rawOptions, globalCommand } = this.cli;
453+
const { options, globalCommand } = this.cli;
438454
if (!this.config.allowUnknownOptions) {
439-
for (const name of Object.keys(rawOptions)) {
455+
for (const name of Object.keys(options)) {
440456
if (name !== '--' &&
441457
!this.hasOption(name) &&
442458
!globalCommand.hasOption(name)) {
443-
console.error(`error: Unknown option \`${name.length > 1 ? `--${name}` : `-${name}`}\``);
444-
exit(1);
459+
throw new CACError(`Unknown option \`${name.length > 1 ? `--${name}` : `-${name}`}\``);
445460
}
446461
}
447462
}
@@ -450,16 +465,15 @@ class Command {
450465
* Check if the required string-type options exist
451466
*/
452467
checkOptionValue() {
453-
const { rawOptions, globalCommand } = this.cli;
468+
const { options: parsedOptions, globalCommand } = this.cli;
454469
const options = [...globalCommand.options, ...this.options];
455470
for (const option of options) {
456-
const value = rawOptions[option.name.split('.')[0]];
471+
const value = parsedOptions[option.name.split('.')[0]];
457472
// Check required option value
458473
if (option.required) {
459474
const hasNegated = options.some(o => o.negated && o.names.includes(option.name));
460475
if (value === true || (value === false && !hasNegated)) {
461-
console.error(`error: option \`${option.rawName}\` value is missing`);
462-
exit(1);
476+
throw new CACError(`option \`${option.rawName}\` value is missing`);
463477
}
464478
}
465479
}
@@ -542,7 +556,6 @@ class CAC extends events.EventEmitter {
542556
* When a sub-command is matched, output the help message for the command
543557
* Otherwise output the global one.
544558
*
545-
* This will also call `process.exit(0)` to quit the process.
546559
*/
547560
outputHelp() {
548561
if (this.matchedCommand) {
@@ -555,15 +568,13 @@ class CAC extends events.EventEmitter {
555568
/**
556569
* Output the version number.
557570
*
558-
* This will also call `process.exit(0)` to quit the process.
559571
*/
560572
outputVersion() {
561573
this.globalCommand.outputVersion();
562574
}
563-
setParsedInfo({ args, options, rawOptions }, matchedCommand, matchedCommandName) {
575+
setParsedInfo({ args, options }, matchedCommand, matchedCommandName) {
564576
this.args = args;
565577
this.options = options;
566-
this.rawOptions = rawOptions;
567578
if (matchedCommand) {
568579
this.matchedCommand = matchedCommand;
569580
}
@@ -585,11 +596,11 @@ class CAC extends events.EventEmitter {
585596
let shouldParse = true;
586597
// Search sub-commands
587598
for (const command of this.commands) {
588-
const mriResult = this.mri(argv.slice(2), command);
589-
const commandName = mriResult.args[0];
599+
const parsed = this.mri(argv.slice(2), command);
600+
const commandName = parsed.args[0];
590601
if (command.isMatched(commandName)) {
591602
shouldParse = false;
592-
const parsedInfo = Object.assign({}, mriResult, { args: mriResult.args.slice(1) });
603+
const parsedInfo = Object.assign({}, parsed, { args: parsed.args.slice(1) });
593604
this.setParsedInfo(parsedInfo, command, commandName);
594605
this.emit(`command:${commandName}`, command);
595606
}
@@ -599,15 +610,15 @@ class CAC extends events.EventEmitter {
599610
for (const command of this.commands) {
600611
if (command.name === '') {
601612
shouldParse = false;
602-
const mriResult = this.mri(argv.slice(2), command);
603-
this.setParsedInfo(mriResult, command);
613+
const parsed = this.mri(argv.slice(2), command);
614+
this.setParsedInfo(parsed, command);
604615
this.emit(`command:!`, command);
605616
}
606617
}
607618
}
608619
if (shouldParse) {
609-
const mriResult = this.mri(argv.slice(2));
610-
this.setParsedInfo(mriResult);
620+
const parsed = this.mri(argv.slice(2));
621+
this.setParsedInfo(parsed);
611622
}
612623
if (this.options.help && this.showHelpOnExit) {
613624
this.outputHelp();
@@ -639,7 +650,10 @@ class CAC extends events.EventEmitter {
639650
argsAfterDoubleDashes = argv.slice(doubleDashesIndex + 1);
640651
argv = argv.slice(0, doubleDashesIndex);
641652
}
642-
const parsed = lib(argv, mriOptions);
653+
let parsed = lib(argv, mriOptions);
654+
parsed = Object.keys(parsed).reduce((res, name) => {
655+
return Object.assign({}, res, { [camelcaseOptionName(name)]: parsed[name] });
656+
}, { _: [] });
643657
const args = parsed._;
644658
delete parsed._;
645659
const options = {
@@ -666,18 +680,15 @@ class CAC extends events.EventEmitter {
666680
}
667681
}
668682
}
669-
// Camelcase option names and set dot nested option values
683+
// Set dot nested option values
670684
for (const key of Object.keys(parsed)) {
671-
const keys = key.split('.').map((v, i) => {
672-
return i === 0 ? camelcase(v) : v;
673-
});
685+
const keys = key.split('.');
674686
setDotProp(options, keys, parsed[key]);
675687
setByType(options, transforms);
676688
}
677689
return {
678690
args,
679-
options,
680-
rawOptions: parsed
691+
options
681692
};
682693
}
683694
runMatchedCommand() {

0 commit comments

Comments
 (0)