From 2a03012e2cb60ea03402ccf81f206ab1f27e5dab Mon Sep 17 00:00:00 2001
From: Peter Jaszkowiak
Date: Sat, 27 Mar 2021 10:14:27 -0600
Subject: [PATCH] fix: ./nodebb help with commander@7 (#9434)
hopefully this one last as long as the last one did
---
src/cli/colors.js | 220 ++++++++++++++++++++++++++--------------------
src/cli/index.js | 4 +-
2 files changed, 126 insertions(+), 98 deletions(-)
diff --git a/src/cli/colors.js b/src/cli/colors.js
index bb4ea2887e..35f30376ae 100644
--- a/src/cli/colors.js
+++ b/src/cli/colors.js
@@ -1,58 +1,25 @@
'use strict';
-
-// override commander functions
+// override commander help formatting functions
// to include color styling in the output
// so the CLI looks nice
-const { Command } = require('commander');
+const { Command, Help } = require('commander');
-const commandColor = 'yellow';
-const optionColor = 'cyan';
-const argColor = 'magenta';
-const subCommandColor = 'green';
-const subOptionColor = 'blue';
-const subArgColor = 'red';
-
-Command.prototype.helpInformation = function () {
- let desc = [];
- if (this._description) {
- desc = [
- ` ${this._description}`,
- '',
- ];
- }
-
- let cmdName = this._name;
- if (this._alias) {
- cmdName = `${cmdName} | ${this._alias}`;
- }
- const usage = [
- '',
- ` Usage: ${cmdName[commandColor]}${' '.reset}${this.usage()}`,
- '',
- ];
-
- let cmds = [];
- const commandHelp = this.commandHelp();
- if (commandHelp) {
- cmds = [commandHelp];
- }
-
- const options = [
- '',
- ' Options:',
- '',
- `${this.optionHelp().replace(/^/gm, ' ')}`,
- '',
- ];
-
- return usage
- .concat(desc)
- .concat(options)
- .concat(cmds)
- .join('\n'.reset);
-};
+const colors = [
+ // depth = 0, top-level command
+ {
+ command: 'yellow',
+ option: 'cyan',
+ arg: 'magenta',
+ },
+ // depth = 1, second-level commands
+ {
+ command: 'green',
+ option: 'blue',
+ arg: 'red',
+ },
+];
function humanReadableArgName(arg) {
const nameOutput = arg.name + (arg.variadic === true ? '...' : '');
@@ -60,58 +27,119 @@ function humanReadableArgName(arg) {
return arg.required ? `<${nameOutput}>` : `[${nameOutput}]`;
}
-Command.prototype.usage = function () {
- const args = this._args.map(arg => humanReadableArgName(arg));
+// get depth of command
+// 0 = top, 1 = subcommand of top, etc
+Command.prototype.depth = function () {
+ if (this._depth === undefined) {
+ let depth = 0;
+ let { parent } = this;
+ while (parent) { depth += 1; parent = parent.parent; }
- const usage = '[options]'[optionColor] +
- (this.commands.length ? ' [command]' : '')[subCommandColor] +
- (this._args.length ? ` ${args.join(' ')}` : '')[argColor];
-
- return usage;
-};
-
-function pad(str, width) {
- const len = Math.max(0, width - str.length);
- return str + Array(len + 1).join(' ');
-}
-
-Command.prototype.commandHelp = function () {
- if (!this.commands.length) {
- return '';
+ this._depth = depth;
}
+ return this._depth;
+};
- const commands = this.commands.filter(cmd => !cmd._noHelp).map((cmd) => {
+module.exports = {
+ commandUsage(cmd) {
+ const depth = cmd.depth();
+
+ // Usage
+ let cmdName = cmd._name;
+ if (cmd._aliases[0]) {
+ cmdName = `${cmdName}|${cmd._aliases[0]}`;
+ }
+ let parentCmdNames = '';
+ let parentCmd = cmd.parent;
+ let parentDepth = depth - 1;
+ while (parentCmd) {
+ parentCmdNames = `${parentCmd.name()[colors[parentDepth].command]} ${parentCmdNames}`;
+
+ parentCmd = parentCmd.parent;
+ parentDepth -= 1;
+ }
+
+ // from Command.prototype.usage()
+ const args = cmd._args.map(arg => humanReadableArgName(arg)[colors[depth].arg]);
+ const cmdUsage = [].concat(
+ (cmd.options.length || cmd._hasHelpOption ? '[options]'[colors[depth].option] : []),
+ (cmd.commands.length ? '[command]'[colors[depth + 1].command] : []),
+ (cmd._args.length ? args : [])
+ ).join(' ');
+
+ return `${parentCmdNames}${cmdName[colors[depth].command]} ${cmdUsage}`;
+ },
+ subcommandTerm(cmd) {
+ const depth = cmd.depth();
+
+ // Legacy. Ignores custom usage string, and nested commands.
const args = cmd._args.map(arg => humanReadableArgName(arg)).join(' ');
+ return (cmd._name + (
+ cmd._aliases[0] ? `|${cmd._aliases[0]}` : ''
+ ))[colors[depth].command] +
+ (cmd.options.length ? ' [options]' : '')[colors[depth].option] + // simplistic check for non-help option
+ (args ? ` ${args}` : '')[colors[depth].arg];
+ },
+ longestOptionTermLength(cmd, helper) {
+ return Help.prototype.longestOptionTermLength.call(this, cmd, helper) + ''.red.length;
+ },
+ longestArgumentTermLength(cmd, helper) {
+ return Help.prototype.longestArgumentTermLength.call(this, cmd, helper) + ''.red.length;
+ },
+ formatHelp(cmd, helper) {
+ const depth = cmd.depth();
- return [
- `${cmd._name[subCommandColor] +
- (cmd._alias ? ` | ${cmd._alias}` : '')[subCommandColor] +
- (cmd.options.length ? ' [options]' : '')[subOptionColor]
- } ${args[subArgColor]}`,
- cmd._description,
- ];
- });
+ const termWidth = helper.padWidth(cmd, helper);
+ const helpWidth = helper.helpWidth || 80;
+ const itemIndentWidth = 2;
+ const itemSeparatorWidth = 2; // between term and description
+ function formatItem(term, description) {
+ if (description) {
+ const fullText = `${term.padEnd(termWidth + itemSeparatorWidth)}${description}`;
+ return helper.wrap(fullText, helpWidth - itemIndentWidth, termWidth + itemSeparatorWidth);
+ }
+ return term;
+ }
+ function formatList(textArray) {
+ return textArray.join('\n').replace(/^/gm, ' '.repeat(itemIndentWidth));
+ }
- const width = commands.reduce((max, command) => Math.max(max, command[0].length), 0);
+ // Usage
+ let output = [`Usage: ${helper.commandUsage(cmd)}`, ''];
- return [
- '',
- ' Commands:',
- '',
- commands.map((cmd) => {
- const desc = cmd[1] ? ` ${cmd[1]}` : '';
- return pad(cmd[0], width) + desc;
- }).join('\n').replace(/^/gm, ' '),
- '',
- ].join('\n');
-};
-
-Command.prototype.optionHelp = function () {
- const width = this.largestOptionLength();
-
- // Append the help information
- return this.options
- .map(option => `${pad(option.flags, width)[optionColor]} ${option.description}`)
- .concat([`${pad('-h, --help', width)[optionColor]} output usage information`])
- .join('\n');
+ // Description
+ const commandDescription = helper.commandDescription(cmd);
+ if (commandDescription.length > 0) {
+ output = output.concat([commandDescription, '']);
+ }
+
+ // Arguments
+ const argumentList = helper.visibleArguments(cmd).map(argument => formatItem(
+ argument.term[colors[depth].arg],
+ argument.description
+ ));
+ if (argumentList.length > 0) {
+ output = output.concat(['Arguments:', formatList(argumentList), '']);
+ }
+
+ // Options
+ const optionList = helper.visibleOptions(cmd).map(option => formatItem(
+ helper.optionTerm(option)[colors[depth].option],
+ helper.optionDescription(option)
+ ));
+ if (optionList.length > 0) {
+ output = output.concat(['Options:', formatList(optionList), '']);
+ }
+
+ // Commands
+ const commandList = helper.visibleCommands(cmd).map(cmd => formatItem(
+ helper.subcommandTerm(cmd),
+ helper.subcommandDescription(cmd)
+ ));
+ if (commandList.length > 0) {
+ output = output.concat(['Commands:', formatList(commandList), '']);
+ }
+
+ return output.join('\n');
+ },
};
diff --git a/src/cli/index.js b/src/cli/index.js
index fafc4857ad..e69910cf1f 100644
--- a/src/cli/index.js
+++ b/src/cli/index.js
@@ -77,6 +77,8 @@ const pkg = require('../../package.json');
const file = require('../file');
const prestart = require('../prestart');
+program.configureHelp(require('./colors'));
+
program
.name('./nodebb')
.description('Welcome to NodeBB')
@@ -305,8 +307,6 @@ program
}
});
-require('./colors');
-
if (process.argv.length === 2) {
program.help();
}