Last active
August 27, 2019 05:52
-
-
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`
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
"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" |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
"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); |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/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'`); | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
"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