diff --git a/install/package.json b/install/package.json index 0dd831ae81..3ef0ffded1 100644 --- a/install/package.json +++ b/install/package.json @@ -54,7 +54,7 @@ "cli-graph": "3.2.2", "clipboard": "2.0.11", "colors": "1.4.0", - "commander": "12.1.0", + "commander": "13.1.0", "compare-versions": "6.1.1", "compression": "1.8.0", "connect-flash": "0.1.1", diff --git a/src/cli/colors.js b/src/cli/colors.js index 7ddd188898..9757a70903 100644 --- a/src/cli/colors.js +++ b/src/cli/colors.js @@ -1,160 +1,15 @@ 'use strict'; -// override commander help formatting functions -// to include color styling in the output -// so the CLI looks nice - -const { Command } = require('commander'); +// todo: replace with styleText from node:util in node 20+ const chalk = require('chalk'); -const colors = [ - // depth = 0, top-level command - { command: 'yellow', option: 'cyan', arg: 'magenta' }, - // depth = 1, second-level commands - { command: 'green', option: 'blue', arg: 'red' }, - // depth = 2, third-level commands - { command: 'yellow', option: 'cyan', arg: 'magenta' }, - // depth = 3 fourth-level commands - { command: 'green', option: 'blue', arg: 'red' }, -]; - -function humanReadableArgName(arg) { - const nameOutput = arg.name() + (arg.variadic === true ? '...' : ''); - - return arg.required ? `<${nameOutput}>` : `[${nameOutput}]`; -} - -function getControlCharacterSpaces(term) { - const matches = term.match(/.\[\d+m/g); - return matches ? matches.length * 5 : 0; -} - -// 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; } - - this._depth = depth; - } - return this._depth; -}; - +// https://github.com/tj/commander.js/blob/master/examples/color-help.mjs 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 = `${chalk[colors[parentDepth].command](parentCmd.name())} ${parentCmdNames}`; - - parentCmd = parentCmd.parent; - parentDepth -= 1; - } - - // from Command.prototype.usage() - const args = cmd._args.map(arg => chalk[colors[depth].arg](humanReadableArgName(arg))); - const cmdUsage = [].concat( - (cmd.options.length || cmd._hasHelpOption ? chalk[colors[depth].option]('[options]') : []), - (cmd.commands.length ? chalk[colors[depth + 1].command]('[command]') : []), - (cmd._args.length ? args : []) - ).join(' '); - - return `${parentCmdNames}${chalk[colors[depth].command](cmdName)} ${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 chalk[colors[depth].command](cmd._name + ( - cmd._aliases[0] ? `|${cmd._aliases[0]}` : '' - )) + - chalk[colors[depth].option](cmd.options.length ? ' [options]' : '') + // simplistic check for non-help option - chalk[colors[depth].arg](args ? ` ${args}` : ''); - }, - longestOptionTermLength(cmd, helper) { - return helper.visibleOptions(cmd).reduce((max, option) => Math.max( - max, - helper.optionTerm(option).length - getControlCharacterSpaces(helper.optionTerm(option)) - ), 0); - }, - longestSubcommandTermLength(cmd, helper) { - return helper.visibleCommands(cmd).reduce((max, command) => Math.max( - max, - helper.subcommandTerm(command).length - getControlCharacterSpaces(helper.subcommandTerm(command)) - ), 0); - }, - longestArgumentTermLength(cmd, helper) { - return helper.visibleArguments(cmd).reduce((max, argument) => Math.max( - max, - helper.argumentTerm(argument).length - getControlCharacterSpaces(helper.argumentTerm(argument)) - ), 0); - }, - 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) { - const padding = ' '.repeat((termWidth + itemSeparatorWidth) - (term.length - getControlCharacterSpaces(term))); - if (description) { - const fullText = `${term}${padding}${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)}`, '']; - - // Description - const commandDescription = helper.commandDescription(cmd); - if (commandDescription.length > 0) { - output = output.concat([commandDescription, '']); - } - - // Arguments - const argumentList = helper.visibleArguments(cmd).map(argument => formatItem( - chalk[colors[depth].arg](argument.term), - argument.description - )); - if (argumentList.length > 0) { - output = output.concat(['Arguments:', formatList(argumentList), '']); - } - - // Options - const optionList = helper.visibleOptions(cmd).map(option => formatItem( - chalk[colors[depth].option](helper.optionTerm(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'); - }, + styleTitle: str => chalk.bold(str), + styleCommandText: str => chalk.cyan(str), + styleCommandDescription: str => chalk.magenta(str), + styleDescriptionText: str => chalk.italic(str), + styleOptionText: str => chalk.green(str), + styleArgumentText: str => chalk.yellow(str), + styleSubcommandText: str => chalk.blue(str), };