Skip to content

Commit 8a992f5

Browse files
committed
fix: errors were not bubbling appropriately from sub-commands to top-level (#802)
1 parent 07e39b7 commit 8a992f5

File tree

3 files changed

+114
-103
lines changed

3 files changed

+114
-103
lines changed

lib/command.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -180,7 +180,7 @@ module.exports = function (yargs, usage, validation) {
180180

181181
// we apply validation post-hoc, so that custom
182182
// checks get passed populated positional arguments.
183-
yargs._runValidation(innerArgv, aliases, positionalMap)
183+
yargs._runValidation(innerArgv, aliases, positionalMap, yargs.parsed.error)
184184

185185
if (commandHandler.handler && !yargs._hasOutput()) {
186186
commandHandler.handler(innerArgv)

test/command.js

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1100,6 +1100,20 @@ describe('Command', function () {
11001100
})
11011101
.argv
11021102
})
1103+
1104+
// addresses https://github.com/yargs/yargs/issues/794
1105+
it('should bubble errors thrown by coerce function inside commands', function (done) {
1106+
yargs
1107+
.command('foo', 'the foo command', function (yargs) {
1108+
yargs.coerce('x', function (arg) {
1109+
throw Error('yikes an error')
1110+
})
1111+
})
1112+
.parse('foo -x 99', function (err) {
1113+
err.message.should.match(/yikes an error/)
1114+
return done()
1115+
})
1116+
})
11031117
})
11041118

11051119
describe('defaults', function () {

yargs.js

Lines changed: 99 additions & 102 deletions
Original file line numberDiff line numberDiff line change
@@ -923,16 +923,7 @@ function Yargs (processArgs, cwd, parentRequire) {
923923

924924
Object.defineProperty(self, 'argv', {
925925
get: function () {
926-
var args = null
927-
928-
try {
929-
args = self._parseArgs(processArgs)
930-
} catch (err) {
931-
if (err instanceof YError) usage.fail(err.message, err)
932-
else throw err
933-
}
934-
935-
return args
926+
return self._parseArgs(processArgs)
936927
},
937928
enumerable: true
938929
})
@@ -951,124 +942,130 @@ function Yargs (processArgs, cwd, parentRequire) {
951942
argv.$0 = self.$0
952943
self.parsed = parsed
953944

954-
guessLocale() // guess locale lazily, so that it can be turned off in chain.
945+
try {
946+
guessLocale() // guess locale lazily, so that it can be turned off in chain.
955947

956-
// while building up the argv object, there
957-
// are two passes through the parser. If completion
958-
// is being performed short-circuit on the first pass.
959-
if (shortCircuit) {
960-
return argv
961-
}
948+
// while building up the argv object, there
949+
// are two passes through the parser. If completion
950+
// is being performed short-circuit on the first pass.
951+
if (shortCircuit) {
952+
return argv
953+
}
962954

963-
if (argv._.length) {
964-
// check for helpOpt in argv._ before running commands
965-
// assumes helpOpt must be valid if useHelpOptAsCommand is true
966-
if (useHelpOptAsCommand) {
967-
// consider any multi-char helpOpt alias as a valid help command
968-
// unless all helpOpt aliases are single-char
969-
// note that parsed.aliases is a normalized bidirectional map :)
970-
var helpCmds = [helpOpt].concat(aliases[helpOpt] || [])
971-
var multiCharHelpCmds = helpCmds.filter(function (k) {
972-
return k.length > 1
973-
})
974-
if (multiCharHelpCmds.length) helpCmds = multiCharHelpCmds
975-
// look for and strip any helpCmds from argv._
976-
argv._ = argv._.filter(function (cmd) {
977-
if (~helpCmds.indexOf(cmd)) {
978-
argv[helpOpt] = true
979-
return false
955+
if (argv._.length) {
956+
// check for helpOpt in argv._ before running commands
957+
// assumes helpOpt must be valid if useHelpOptAsCommand is true
958+
if (useHelpOptAsCommand) {
959+
// consider any multi-char helpOpt alias as a valid help command
960+
// unless all helpOpt aliases are single-char
961+
// note that parsed.aliases is a normalized bidirectional map :)
962+
var helpCmds = [helpOpt].concat(aliases[helpOpt] || [])
963+
var multiCharHelpCmds = helpCmds.filter(function (k) {
964+
return k.length > 1
965+
})
966+
if (multiCharHelpCmds.length) helpCmds = multiCharHelpCmds
967+
// look for and strip any helpCmds from argv._
968+
argv._ = argv._.filter(function (cmd) {
969+
if (~helpCmds.indexOf(cmd)) {
970+
argv[helpOpt] = true
971+
return false
972+
}
973+
return true
974+
})
975+
}
976+
977+
// if there's a handler associated with a
978+
// command defer processing to it.
979+
var handlerKeys = command.getCommands()
980+
if (handlerKeys.length) {
981+
var firstUnknownCommand
982+
for (var i = 0, cmd; (cmd = argv._[i]) !== undefined; i++) {
983+
if (~handlerKeys.indexOf(cmd) && cmd !== completionCommand) {
984+
setPlaceholderKeys(argv)
985+
return command.runCommand(cmd, self, parsed)
986+
} else if (!firstUnknownCommand && cmd !== completionCommand) {
987+
firstUnknownCommand = cmd
988+
}
980989
}
981-
return true
982-
})
983-
}
984990

985-
// if there's a handler associated with a
986-
// command defer processing to it.
987-
var handlerKeys = command.getCommands()
988-
if (handlerKeys.length) {
989-
var firstUnknownCommand
990-
for (var i = 0, cmd; (cmd = argv._[i]) !== undefined; i++) {
991-
if (~handlerKeys.indexOf(cmd) && cmd !== completionCommand) {
992-
setPlaceholderKeys(argv)
993-
return command.runCommand(cmd, self, parsed)
994-
} else if (!firstUnknownCommand && cmd !== completionCommand) {
995-
firstUnknownCommand = cmd
991+
// recommend a command if recommendCommands() has
992+
// been enabled, and no commands were found to execute
993+
if (recommendCommands && firstUnknownCommand) {
994+
validation.recommendCommands(firstUnknownCommand, handlerKeys)
996995
}
997996
}
998997

999-
// recommend a command if recommendCommands() has
1000-
// been enabled, and no commands were found to execute
1001-
if (recommendCommands && firstUnknownCommand) {
1002-
validation.recommendCommands(firstUnknownCommand, handlerKeys)
998+
// generate a completion script for adding to ~/.bashrc.
999+
if (completionCommand && ~argv._.indexOf(completionCommand) && !argv[completion.completionKey]) {
1000+
if (exitProcess) setBlocking(true)
1001+
self.showCompletionScript()
1002+
self.exit(0)
10031003
}
10041004
}
10051005

1006-
// generate a completion script for adding to ~/.bashrc.
1007-
if (completionCommand && ~argv._.indexOf(completionCommand) && !argv[completion.completionKey]) {
1006+
// we must run completions first, a user might
1007+
// want to complete the --help or --version option.
1008+
if (completion.completionKey in argv) {
10081009
if (exitProcess) setBlocking(true)
1009-
self.showCompletionScript()
1010-
self.exit(0)
1011-
}
1012-
}
10131010

1014-
// we must run completions first, a user might
1015-
// want to complete the --help or --version option.
1016-
if (completion.completionKey in argv) {
1017-
if (exitProcess) setBlocking(true)
1018-
1019-
// we allow for asynchronous completions,
1020-
// e.g., loading in a list of commands from an API.
1021-
var completionArgs = args.slice(args.indexOf('--' + completion.completionKey) + 1)
1022-
completion.getCompletion(completionArgs, function (completions) {
1023-
;(completions || []).forEach(function (completion) {
1024-
_logger.log(completion)
1011+
// we allow for asynchronous completions,
1012+
// e.g., loading in a list of commands from an API.
1013+
var completionArgs = args.slice(args.indexOf('--' + completion.completionKey) + 1)
1014+
completion.getCompletion(completionArgs, function (completions) {
1015+
;(completions || []).forEach(function (completion) {
1016+
_logger.log(completion)
1017+
})
1018+
1019+
self.exit(0)
10251020
})
1021+
return setPlaceholderKeys(argv)
1022+
}
10261023

1027-
self.exit(0)
1028-
})
1029-
return setPlaceholderKeys(argv)
1030-
}
1024+
// Handle 'help' and 'version' options
1025+
Object.keys(argv).forEach(function (key) {
1026+
if (key === helpOpt && argv[key]) {
1027+
if (exitProcess) setBlocking(true)
10311028

1032-
// Handle 'help' and 'version' options
1033-
Object.keys(argv).forEach(function (key) {
1034-
if (key === helpOpt && argv[key]) {
1035-
if (exitProcess) setBlocking(true)
1029+
skipValidation = true
1030+
self.showHelp('log')
1031+
self.exit(0)
1032+
} else if (key === versionOpt && argv[key]) {
1033+
if (exitProcess) setBlocking(true)
10361034

1037-
skipValidation = true
1038-
self.showHelp('log')
1039-
self.exit(0)
1040-
} else if (key === versionOpt && argv[key]) {
1041-
if (exitProcess) setBlocking(true)
1035+
skipValidation = true
1036+
usage.showVersion()
1037+
self.exit(0)
1038+
}
1039+
})
10421040

1043-
skipValidation = true
1044-
usage.showVersion()
1045-
self.exit(0)
1041+
// Check if any of the options to skip validation were provided
1042+
if (!skipValidation && options.skipValidation.length > 0) {
1043+
skipValidation = Object.keys(argv).some(function (key) {
1044+
return options.skipValidation.indexOf(key) >= 0 && argv[key] === true
1045+
})
10461046
}
1047-
})
10481047

1049-
// Check if any of the options to skip validation were provided
1050-
if (!skipValidation && options.skipValidation.length > 0) {
1051-
skipValidation = Object.keys(argv).some(function (key) {
1052-
return options.skipValidation.indexOf(key) >= 0 && argv[key] === true
1053-
})
1054-
}
1048+
// If the help or version options where used and exitProcess is false,
1049+
// or if explicitly skipped, we won't run validations.
1050+
if (!skipValidation) {
1051+
if (parsed.error) throw new YError(parsed.error.message)
10551052

1056-
// If the help or version options where used and exitProcess is false,
1057-
// or if explicitly skipped, we won't run validations.
1058-
if (!skipValidation) {
1059-
if (parsed.error) throw new YError(parsed.error.message)
1060-
1061-
// if we're executed via bash completion, don't
1062-
// bother with validation.
1063-
if (!argv[completion.completionKey]) {
1064-
self._runValidation(argv, aliases, {})
1053+
// if we're executed via bash completion, don't
1054+
// bother with validation.
1055+
if (!argv[completion.completionKey]) {
1056+
self._runValidation(argv, aliases, {}, parsed.error)
1057+
}
10651058
}
1059+
} catch (err) {
1060+
if (err instanceof YError) usage.fail(err.message, err)
1061+
else throw err
10661062
}
10671063

10681064
return setPlaceholderKeys(argv)
10691065
}
10701066

1071-
self._runValidation = function (argv, aliases, positionalMap) {
1067+
self._runValidation = function (argv, aliases, positionalMap, parseErrors) {
1068+
if (parseErrors) throw new YError(parseErrors.message)
10721069
validation.nonOptionCount(argv)
10731070
validation.missingArgumentValue(argv)
10741071
validation.requiredArguments(argv)

0 commit comments

Comments
 (0)