Skip to content

Instantly share code, notes, and snippets.

@kevyworks
Last active August 27, 2019 05:52
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save kevyworks/653b40af20dc9a2509f865cd7fca1ef0 to your computer and use it in GitHub Desktop.
Save kevyworks/653b40af20dc9a2509f865cd7fca1ef0 to your computer and use it in GitHub Desktop.
NodeJS CLI Commander w/ separate commands and simple overrides for colors output help. Testing a sample output: `$ node cli.js`
"use strict";
/**
* @param {typeof import("./commander").Command} program Commander Instance
*/
module.exports = function(program) {
return program
.command("greet [name]")
.description("A greet command.")
.action((name) => {
console.log(`Hello ${name || "there"}!`);
});
};
// Note: The pattern used to detect a sub command file must be. "<parent-command>-<command>.cmd.js"
"use strict";
const program = require("./commander");
program
.name("cli runner")
.option("-a, --aaa", "Something 'aaa'")
.option("-b, --bbb", "Something 'bbb'")
.option("-c, --ccc", "Something 'ccc'")
.option("-d, --ddd", "Oh why not last something 'ddd'")
.parse(process.argv);
console.log(program);
#!/usr/bin/env node
"use strict";
const path = require("path");
const { Command, installCommands } = require("./commander");
const { version = "0.0.1" } = require("./../package.json");
/**
* New Command instance
*
* @type {Command} cli
*/
const program = new Command(path.basename(process.argv[1], ".js"));
// Install additional commands
installCommands(program);
// Setup
program
.version(version)
.usage("[options] [<command>]")
.command("runner", "moleculer node runner",);
let result = program.parse(process.argv);
// Check
if (!program.args.length) {
program.help();
} else {
if (program.args.length === 1 && result && result.name() === program.name()) {
console.log(`${program.name()}: '${program.args[0]}' is not a valid command. See '${program.name()} --help'`);
}
}
"use strict";
const fg = require("fast-glob").sync;
const path = require("path");
const C = require("kleur");
const { Command } = require("commander");
/**
* Return help for options.
*
* @return {String}
*/
Command.prototype.optionHelp = function() {
let width = this.padWidth();
// Append the help information
return this.options.map(function(option) {
return C.green(pad(option.flags, width)) + " " + option.description +
((option.bool && option.defaultValue !== undefined) ? " (default: " + JSON.stringify(option.defaultValue) + ")" : "");
}).concat([C.green(pad("-h, --help", width)) + " " + "output usage information"])
.join("\n");
};
/**
* Return command help documentation.
*
* @return {String}
*/
Command.prototype.commandHelp = function() {
if (!this.commands.length) return "";
let commands = this.prepareCommands();
let width = this.padWidth();
return [
C.yellow("Available commands:"),
commands.map(function(cmd) {
let desc = cmd[1] ? " " + strTruncate(cmd[1]) : "";
return (desc ? C.green(pad(cmd[0].split(/ +/)[0], width)) : C.green(cmd[0])) + desc;
}).join("\n").replace(/^/gm, " "),
""
].join("\n");
};
/**
* Return sub command help documentation.
*
* @return {String}
*/
Command.prototype.helpInformation = function() {
let desc = [];
if (this._description) {
desc = [
this._description,
""
];
let argsDescription = this._argsDescription;
if (argsDescription && this._args.length) {
let width = this.padWidth();
desc.push(C.yellow("Arguments:"));
desc.push("");
this._args.forEach(function(arg) {
desc.push(" " + pad(arg.name, width) + " " + argsDescription[arg.name]);
});
desc.push("");
}
}
let cmdName = this._name;
if (this._alias) {
cmdName = cmdName + "|" + this._alias;
}
let usage = [
C.yellow("Usage: ") + cmdName + " " + C.dim(this.usage()),
""
];
let cmds = [];
let commandHelp = this.commandHelp();
if (commandHelp) cmds = [commandHelp];
let options = [
C.yellow("Options:"),
"" + this.optionHelp().replace(/^/gm, " "),
""
];
return [""]
.concat(usage)
.concat(desc)
.concat(options)
.concat(cmds)
.concat("")
.join("\n");
};
/**
* Pad `str` to `width`.
*
* @param {String} str
* @param {Number} width
* @return {String}
*/
function pad(str, width) {
let len = Math.max(0, width - str.length);
return str + Array(len + 1).join(" ");
}
/**
* Truncate string if longer that the specified length
*
* @param {String} str
* @param {Number} [length]
* @return {String}
*/
function strTruncate(str, length = 30) {
let dots = str.length > length ? "..." : "";
return str.substring(0, length) + dots;
}
/**
* Check if an Object type
*
* @param {Object} o
* @return {Boolean}
*/
function isPlainObject(o) {
return o && o.toString && o.toString() === "[object Object]";
}
/**
* Install Commands
*
* @param {Command} program
* @param {String} [commandsDir]
* @return {Set<String>}
*/
function installCommands(program, commandsDir = null) {
const result = new Set();
if (!(program instanceof Command)) return;
if (!program.name() || !program.name().length) {
console.error(C.red("error: program must have a name."));
return;
}
const pattern = program.name() + "-*.cmd.js";
let cwd = commandsDir || __dirname;
fg(pattern, { cwd }).forEach((f) => {
try {
const fn = require(path.resolve(cwd, f));
if (typeof fn === "function") {
/** @type {Command} command */
const command = fn(program);
result.add(command.name());
}
} catch (e) {
console.error(C.red(`error: unable to install '${f}'. `) + e.message);
}
});
return result;
}
/**
* Expose the root command.
*/
exports = module.exports = new Command();
/**
* Expose `Command`.
*/
exports.Command = Command;
/**
* Expose `installCommands`.
*/
exports.installCommands = installCommands;
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment