Skip to content

Commit

Permalink
Merge pull request #999 from shadowspawn/feature/specifyExecutableFile
Browse files Browse the repository at this point in the history
Add option to specify executable file name
  • Loading branch information
shadowspawn committed Jul 23, 2019
2 parents 6d68637 + facd66f commit 831d52f
Show file tree
Hide file tree
Showing 7 changed files with 623 additions and 1,050 deletions.
1 change: 0 additions & 1 deletion .travis.yml
@@ -1,6 +1,5 @@
language: node_js
node_js:
- "6"
- "8"
- "10"
- "12"
Expand Down
3 changes: 3 additions & 0 deletions Readme.md
Expand Up @@ -330,6 +330,7 @@ Configuration options can be passed with the call to `.command()`. Specifying `t
When `.command()` is invoked with a description argument, this tells commander that you're going to use separate executables for sub-commands, much like `git(1)` and other popular tools.
Commander will search the executables in the directory of the entry script (like `./examples/pm`) with the name `program-subcommand`, like `pm-install`, `pm-search`.
You can specify a custom name with the `executableFile` configuration option.
You handle the options for an executable (sub)command in the executable, and don't declare them at the top-level.
Expand All @@ -341,11 +342,13 @@ program
.version('0.1.0')
.command('install [name]', 'install one or more packages')
.command('search [query]', 'search with optional query')
.command('update', 'update installed packages', {executableFile: 'myUpdateSubCommand'})
.command('list', 'list packages installed', {isDefault: true})
.parse(process.argv);
```
Configuration options can be passed with the call to `.command()`. Specifying `true` for `opts.noHelp` will remove the command from the generated help output. Specifying `true` for `opts.isDefault` will run the subcommand if no other subcommand is specified.
Specifying a name with `executableFile` will override the default constructed name.
If the program is designed to be installed globally, make sure the executables have proper modes, like `755`.
Expand Down
60 changes: 41 additions & 19 deletions index.js
Expand Up @@ -157,6 +157,7 @@ Command.prototype.command = function(nameAndArgs, actionOptsOrExecDesc, execOpts
cmd._helpDescription = this._helpDescription;
cmd._helpShortFlag = this._helpShortFlag;
cmd._helpLongFlag = this._helpLongFlag;
cmd._executableFile = opts.executableFile; // Custom name for executable file
this.commands.push(cmd);
cmd.parseExpectedArgs(args);
cmd.parent = this;
Expand Down Expand Up @@ -458,26 +459,39 @@ Command.prototype.parse = function(argv) {
var result = this.parseArgs(this.args, parsed.unknown);

// executable sub-commands
// (Debugging note for future: args[0] is not right if an action has been called)
var name = result.args[0];
var subCommand = null;

var aliasCommand = null;
// check alias of sub commands
// Look for subcommand
if (name) {
aliasCommand = this.commands.filter(function(command) {
subCommand = this.commands.find(function(command) {
return command._name === name;
});
}

// Look for alias
if (!subCommand && name) {
subCommand = this.commands.find(function(command) {
return command.alias() === name;
})[0];
});
if (subCommand) {
name = subCommand._name;
args[0] = name;
}
}

// Look for default subcommand
if (!subCommand && this.defaultExecutable) {
name = this.defaultExecutable;
args.unshift(name);
subCommand = this.commands.find(function(command) {
return command._name === name;
});
}

if (this._execs[name] && typeof this._execs[name] !== 'function') {
return this.executeSubCommand(argv, args, parsed.unknown);
} else if (aliasCommand) {
// is alias of a subCommand
args[0] = aliasCommand._name;
return this.executeSubCommand(argv, args, parsed.unknown);
} else if (this.defaultExecutable) {
// use the default subcommand
args.unshift(this.defaultExecutable);
return this.executeSubCommand(argv, args, parsed.unknown);
return this.executeSubCommand(argv, args, parsed.unknown, subCommand ? subCommand._executableFile : undefined);
}

return result;
Expand All @@ -489,10 +503,11 @@ Command.prototype.parse = function(argv) {
* @param {Array} argv
* @param {Array} args
* @param {Array} unknown
* @param {String} specifySubcommand
* @api private
*/

Command.prototype.executeSubCommand = function(argv, args, unknown) {
Command.prototype.executeSubCommand = function(argv, args, unknown, executableFile) {
args = args.concat(unknown);

if (!args.length) this.help();
Expand All @@ -504,24 +519,31 @@ Command.prototype.executeSubCommand = function(argv, args, unknown) {
args[1] = this._helpLongFlag;
}

var isExplicitJS = false; // Whether to use node to launch "executable"

// executable
var f = argv[1];
// name of the subcommand, link `pm-install`
var bin = basename(f, path.extname(f)) + '-' + args[0];
var pm = argv[1];
// name of the subcommand, like `pm-install`
var bin = basename(pm, path.extname(pm)) + '-' + args[0];
if (executableFile != null) {
bin = executableFile;
// Check for same extensions as we scan for below so get consistent launch behaviour.
var executableExt = path.extname(executableFile);
isExplicitJS = executableExt === '.js' || executableExt === '.ts' || executableExt === '.mjs';
}

// In case of globally installed, get the base dir where executable
// subcommand file should be located at
var baseDir;

var resolvedLink = fs.realpathSync(f);
var resolvedLink = fs.realpathSync(pm);

baseDir = dirname(resolvedLink);

// prefer local `./<bin>` to bin in the $PATH
var localBin = path.join(baseDir, bin);

// whether bin file is a js script with explicit `.js` or `.ts` extension
var isExplicitJS = false;
if (exists(localBin + '.js')) {
bin = localBin + '.js';
isExplicitJS = true;
Expand Down

0 comments on commit 831d52f

Please sign in to comment.