fix: ./nodebb help with commander@7 (#9434)
hopefully this one last as long as the last one didv1.18.x
parent
dfdb005099
commit
2a03012e2c
@ -1,117 +1,145 @@
|
||||
'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 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}`,
|
||||
'',
|
||||
const { Command, Help } = require('commander');
|
||||
|
||||
const colors = [
|
||||
// depth = 0, top-level command
|
||||
{
|
||||
command: 'yellow',
|
||||
option: 'cyan',
|
||||
arg: 'magenta',
|
||||
},
|
||||
// depth = 1, second-level commands
|
||||
{
|
||||
command: 'green',
|
||||
option: 'blue',
|
||||
arg: 'red',
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
let cmdName = this._name;
|
||||
if (this._alias) {
|
||||
cmdName = `${cmdName} | ${this._alias}`;
|
||||
}
|
||||
const usage = [
|
||||
'',
|
||||
` Usage: ${cmdName[commandColor]}${' '.reset}${this.usage()}`,
|
||||
'',
|
||||
];
|
||||
function humanReadableArgName(arg) {
|
||||
const nameOutput = arg.name + (arg.variadic === true ? '...' : '');
|
||||
|
||||
let cmds = [];
|
||||
const commandHelp = this.commandHelp();
|
||||
if (commandHelp) {
|
||||
cmds = [commandHelp];
|
||||
return arg.required ? `<${nameOutput}>` : `[${nameOutput}]`;
|
||||
}
|
||||
|
||||
const options = [
|
||||
'',
|
||||
' Options:',
|
||||
'',
|
||||
`${this.optionHelp().replace(/^/gm, ' ')}`,
|
||||
'',
|
||||
];
|
||||
// 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; }
|
||||
|
||||
return usage
|
||||
.concat(desc)
|
||||
.concat(options)
|
||||
.concat(cmds)
|
||||
.join('\n'.reset);
|
||||
this._depth = depth;
|
||||
}
|
||||
return this._depth;
|
||||
};
|
||||
|
||||
function humanReadableArgName(arg) {
|
||||
const nameOutput = arg.name + (arg.variadic === true ? '...' : '');
|
||||
module.exports = {
|
||||
commandUsage(cmd) {
|
||||
const depth = cmd.depth();
|
||||
|
||||
return arg.required ? `<${nameOutput}>` : `[${nameOutput}]`;
|
||||
// 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;
|
||||
}
|
||||
|
||||
Command.prototype.usage = function () {
|
||||
const args = this._args.map(arg => humanReadableArgName(arg));
|
||||
|
||||
const usage = '[options]'[optionColor] +
|
||||
(this.commands.length ? ' [command]' : '')[subCommandColor] +
|
||||
(this._args.length ? ` ${args.join(' ')}` : '')[argColor];
|
||||
// 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 usage;
|
||||
};
|
||||
return `${parentCmdNames}${cmdName[colors[depth].command]} ${cmdUsage}`;
|
||||
},
|
||||
subcommandTerm(cmd) {
|
||||
const depth = cmd.depth();
|
||||
|
||||
function pad(str, width) {
|
||||
const len = Math.max(0, width - str.length);
|
||||
return str + Array(len + 1).join(' ');
|
||||
// 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();
|
||||
|
||||
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));
|
||||
}
|
||||
|
||||
// Usage
|
||||
let output = [`Usage: ${helper.commandUsage(cmd)}`, ''];
|
||||
|
||||
Command.prototype.commandHelp = function () {
|
||||
if (!this.commands.length) {
|
||||
return '';
|
||||
// Description
|
||||
const commandDescription = helper.commandDescription(cmd);
|
||||
if (commandDescription.length > 0) {
|
||||
output = output.concat([commandDescription, '']);
|
||||
}
|
||||
|
||||
const commands = this.commands.filter(cmd => !cmd._noHelp).map((cmd) => {
|
||||
const args = cmd._args.map(arg => humanReadableArgName(arg)).join(' ');
|
||||
// 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), '']);
|
||||
}
|
||||
|
||||
return [
|
||||
`${cmd._name[subCommandColor] +
|
||||
(cmd._alias ? ` | ${cmd._alias}` : '')[subCommandColor] +
|
||||
(cmd.options.length ? ' [options]' : '')[subOptionColor]
|
||||
} ${args[subArgColor]}`,
|
||||
cmd._description,
|
||||
];
|
||||
});
|
||||
|
||||
const width = commands.reduce((max, command) => Math.max(max, command[0].length), 0);
|
||||
|
||||
return [
|
||||
'',
|
||||
' Commands:',
|
||||
'',
|
||||
commands.map((cmd) => {
|
||||
const desc = cmd[1] ? ` ${cmd[1]}` : '';
|
||||
return pad(cmd[0], width) + desc;
|
||||
}).join('\n').replace(/^/gm, ' '),
|
||||
'',
|
||||
].join('\n');
|
||||
};
|
||||
// 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), '']);
|
||||
}
|
||||
|
||||
Command.prototype.optionHelp = function () {
|
||||
const width = this.largestOptionLength();
|
||||
// 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), '']);
|
||||
}
|
||||
|
||||
// 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');
|
||||
return output.join('\n');
|
||||
},
|
||||
};
|
||||
|
Loading…
Reference in New Issue