Skip to content

Commit 97fda7c

Browse files
committed
Throw errors & show warnings on wrong library use
1 parent d038570 commit 97fda7c

File tree

1 file changed

+58
-10
lines changed

1 file changed

+58
-10
lines changed

lib/command.js

Lines changed: 58 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@ const { suggestSimilar } = require('./suggestSimilar');
1212

1313
// @ts-check
1414

15+
const PRODUCTION = process.env.NODE_ENV === 'production';
16+
1517
class Command extends EventEmitter {
1618
/**
1719
* Initialize a new `Command`.
@@ -77,6 +79,9 @@ class Command extends EventEmitter {
7779
this._helpCommandnameAndArgs = 'help [command]';
7880
this._helpCommandDescription = 'display help for command';
7981
this._helpConfiguration = {};
82+
83+
/** @type {boolean | undefined} */
84+
this._asyncParsing = undefined;
8085
}
8186

8287
/**
@@ -265,6 +270,10 @@ class Command extends EventEmitter {
265270
throw new Error(`Command passed to .addCommand() must have a name
266271
- specify the name in Command constructor or using .name()`);
267272
}
273+
if (cmd.parent) {
274+
throw new Error(`'${cmd._name}' cannot be added using .addCommand()
275+
- it already has a parent command`);
276+
}
268277

269278
opts = opts || {};
270279
if (opts.isDefault) this._defaultCommandName = cmd._name;
@@ -887,6 +896,28 @@ Expecting one of '${allowedValues.join("', '")}'`);
887896
return userArgs;
888897
}
889898

899+
/**
900+
* @param {boolean} async
901+
* @param {Function} userArgsCallback
902+
* @param {string[]} [argv]
903+
* @param {Object} [parseOptions]
904+
* @param {string} [parseOptions.from]
905+
* @return {Command|Promise}
906+
* @api private
907+
*/
908+
909+
_parseSubroutine(async, userArgsCallback, argv, parseOptions) {
910+
this._asyncParsing = async;
911+
const methodName = async ? 'parseAsync' : 'parse';
912+
if (!PRODUCTION && this.parent) {
913+
console.warn(`Called .${methodName}() on subcommand '${this._name}'.
914+
Call on top-level command instead`);
915+
}
916+
917+
const userArgs = this._prepareUserArgs(argv, parseOptions);
918+
return userArgsCallback(userArgs);
919+
}
920+
890921
/**
891922
* Parse `argv`, setting options and invoking commands when defined.
892923
*
@@ -905,10 +936,10 @@ Expecting one of '${allowedValues.join("', '")}'`);
905936
*/
906937

907938
parse(argv, parseOptions) {
908-
const userArgs = this._prepareUserArgs(argv, parseOptions);
909-
this._parseCommand([], userArgs);
910-
911-
return this;
939+
return this._parseSubroutine(false, (userArgs) => {
940+
this._parseCommand([], userArgs);
941+
return this;
942+
}, argv, parseOptions);
912943
}
913944

914945
/**
@@ -931,10 +962,10 @@ Expecting one of '${allowedValues.join("', '")}'`);
931962
*/
932963

933964
async parseAsync(argv, parseOptions) {
934-
const userArgs = this._prepareUserArgs(argv, parseOptions);
935-
await this._parseCommand([], userArgs);
936-
937-
return this;
965+
return this._parseSubroutine(true, async(userArgs) => {
966+
await this._parseCommand([], userArgs);
967+
return this;
968+
}, argv, parseOptions);
938969
}
939970

940971
/**
@@ -1071,6 +1102,7 @@ Expecting one of '${allowedValues.join("', '")}'`);
10711102
_dispatchSubcommand(commandName, operands, unknown) {
10721103
const subCommand = this._findCommand(commandName);
10731104
if (!subCommand) this.help({ error: true });
1105+
subCommand._asyncParsing = this._asyncParsing;
10741106

10751107
let hookResult;
10761108
hookResult = this._chainOrCallSubCommandHook(hookResult, subCommand, 'preSubcommand');
@@ -1189,12 +1221,18 @@ Expecting one of '${allowedValues.join("', '")}'`);
11891221

11901222
_chainOrCall(promise, fn) {
11911223
// thenable
1192-
if (promise && promise.then && typeof promise.then === 'function') {
1224+
if (isThenable(promise)) {
11931225
// already have a promise, chain callback
11941226
return promise.then(() => fn());
11951227
}
1228+
11961229
// callback might return a promise
1197-
return fn();
1230+
const result = fn();
1231+
if (!PRODUCTION && !this._asyncParsing && isThenable(result)) {
1232+
console.warn(`.parse() is incompatible with async argParsers, hooks and actions.
1233+
Use .parseAsync() instead.`);
1234+
}
1235+
return result;
11981236
}
11991237

12001238
/**
@@ -2193,4 +2231,14 @@ function getCommandAndParents(startCommand) {
21932231
return result;
21942232
}
21952233

2234+
/**
2235+
* @param {*} value
2236+
* @returns {boolean}
2237+
* @api private
2238+
*/
2239+
2240+
function isThenable(value) {
2241+
return typeof value?.then === 'function';
2242+
}
2243+
21962244
exports.Command = Command;

0 commit comments

Comments
 (0)