feat: remove colors in favour of chalk (#10142)

* feat: remove colors in favour of chalk

* fix: bad conversion from colors to chalk in src/cli/index.js

* fix: padWidth calculation to account for control characters

* fix: termWidth calculation, but swapped one problem for another

* fix: formatItem, implement my own padRight to take control characters into account
This commit is contained in:
Julian Lam
2022-02-01 21:43:09 -05:00
committed by GitHub
parent c7a5643932
commit cf8f62aed9
15 changed files with 106 additions and 78 deletions

View File

@@ -4,7 +4,8 @@
// to include color styling in the output
// so the CLI looks nice
const { Command, Help } = require('commander');
const { Command } = require('commander');
const chalk = require('chalk');
const colors = [
// depth = 0, top-level command
@@ -23,6 +24,11 @@ function humanReadableArgName(arg) {
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 () {
@@ -49,38 +55,50 @@ module.exports = {
let parentCmd = cmd.parent;
let parentDepth = depth - 1;
while (parentCmd) {
parentCmdNames = `${parentCmd.name()[colors[parentDepth].command]} ${parentCmdNames}`;
parentCmdNames = `${chalk[colors[parentDepth].command](parentCmd.name())} ${parentCmdNames}`;
parentCmd = parentCmd.parent;
parentDepth -= 1;
}
// from Command.prototype.usage()
const args = cmd._args.map(arg => humanReadableArgName(arg)[colors[depth].arg]);
const args = cmd._args.map(arg => chalk[colors[depth].arg](humanReadableArgName(arg)));
const cmdUsage = [].concat(
(cmd.options.length || cmd._hasHelpOption ? '[options]'[colors[depth].option] : []),
(cmd.commands.length ? '[command]'[colors[depth + 1].command] : []),
(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}${cmdName[colors[depth].command]} ${cmdUsage}`;
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 (cmd._name + (
return chalk[colors[depth].command](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];
)) +
chalk[colors[depth].option](cmd.options.length ? ' [options]' : '') + // simplistic check for non-help option
chalk[colors[depth].arg](args ? ` ${args}` : '');
},
longestOptionTermLength(cmd, helper) {
return Help.prototype.longestOptionTermLength.call(this, cmd, helper) + ''.red.length;
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 Help.prototype.longestArgumentTermLength.call(this, cmd, helper) + ''.red.length;
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();
@@ -90,8 +108,9 @@ module.exports = {
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.padEnd(termWidth + itemSeparatorWidth)}${description}`;
const fullText = `${term}${padding}${description}`;
return helper.wrap(fullText, helpWidth - itemIndentWidth, termWidth + itemSeparatorWidth);
}
return term;
@@ -111,7 +130,7 @@ module.exports = {
// Arguments
const argumentList = helper.visibleArguments(cmd).map(argument => formatItem(
argument.term[colors[depth].arg],
chalk[colors[depth].arg](argument.term),
argument.description
));
if (argumentList.length > 0) {
@@ -120,7 +139,7 @@ module.exports = {
// Options
const optionList = helper.visibleOptions(cmd).map(option => formatItem(
helper.optionTerm(option)[colors[depth].option],
chalk[colors[depth].option](helper.optionTerm(option)),
helper.optionDescription(option)
));
if (optionList.length > 0) {

View File

@@ -22,10 +22,10 @@ try {
packageInstall.preserveExtraneousPlugins();
try {
fs.accessSync(path.join(paths.nodeModules, 'colors/package.json'), fs.constants.R_OK);
fs.accessSync(path.join(paths.nodeModules, 'chalk/package.json'), fs.constants.R_OK);
require('colors');
console.log('OK'.green);
const chalk = require('chalk');
console.log(chalk.green('OK'));
} catch (e) {
console.log('OK');
}
@@ -52,7 +52,7 @@ try {
checkVersion('nconf');
checkVersion('async');
checkVersion('commander');
checkVersion('colors');
checkVersion('chalk');
} catch (e) {
if (['ENOENT', 'DEP_WRONG_VERSION', 'MODULE_NOT_FOUND'].includes(e.code)) {
console.warn('Dependencies outdated or not yet installed.');
@@ -61,14 +61,14 @@ try {
packageInstall.updatePackageFile();
packageInstall.installAll();
require('colors');
console.log('OK'.green + '\n'.reset);
const chalk = require('chalk');
console.log(`${chalk.green('OK')}\n`);
} else {
throw e;
}
}
require('colors');
const chalk = require('chalk');
const nconf = require('nconf');
const { program } = require('commander');
const yargs = require('yargs');
@@ -173,7 +173,7 @@ program
try {
initConfig = JSON.parse(initConfig);
} catch (e) {
console.warn('Invalid JSON passed as initial config value.'.red);
console.warn(chalk.red('Invalid JSON passed as initial config value.'));
console.log('If you meant to pass in an initial config value, please try again.\n');
throw e;
@@ -190,7 +190,7 @@ program
});
program
.command('build [targets...]')
.description(`Compile static assets ${'(JS, CSS, templates, languages)'.red}`)
.description(`Compile static assets ${chalk.red('(JS, CSS, templates, languages)')}`)
.option('-s, --series', 'Run builds in series without extra processes')
.action((targets, options) => {
if (program.opts().dev) {
@@ -240,7 +240,7 @@ resetCommand
.action((options) => {
const valid = ['theme', 'plugin', 'widgets', 'settings', 'all'].some(x => options[x]);
if (!valid) {
console.warn('\n No valid options passed in, so nothing was reset.'.red);
console.warn(`\n${chalk.red('No valid options passed in, so nothing was reset.')}`);
resetCommand.help();
}
@@ -270,8 +270,8 @@ program
console.log(`\n${[
'When running particular upgrade scripts, options are ignored.',
'By default all options are enabled. Passing any options disables that default.',
`Only package and dependency updates: ${'./nodebb upgrade -mi'.yellow}`,
`Only database update: ${'./nodebb upgrade -s'.yellow}`,
`Only package and dependency updates: ${chalk.yellow('./nodebb upgrade -mi')}`,
`Only database update: ${chalk.yellow('./nodebb upgrade -s')}`,
].join('\n')}`);
})
.action((scripts, options) => {
@@ -289,7 +289,7 @@ program
if (err) {
throw err;
}
console.log('OK'.green);
console.log(chalk.green('OK'));
process.exit();
});
});

View File

@@ -3,6 +3,7 @@
const winston = require('winston');
const childProcess = require('child_process');
const CliGraph = require('cli-graph');
const chalk = require('chalk');
const build = require('../meta/build');
const db = require('../database');
@@ -76,9 +77,9 @@ async function listPlugins() {
process.stdout.write('Active plugins:\n');
combined.forEach((plugin) => {
process.stdout.write(`\t* ${plugin.id}${plugin.version ? `@${plugin.version}` : ''} (`);
process.stdout.write(plugin.installed ? 'installed'.green : 'not installed'.red);
process.stdout.write(plugin.installed ? chalk.green('installed') : chalk.red('not installed'));
process.stdout.write(', ');
process.stdout.write(plugin.active ? 'enabled'.green : 'disabled'.yellow);
process.stdout.write(plugin.active ? chalk.green('enabled') : chalk.yellow('disabled'));
process.stdout.write(')\n');
});
@@ -88,9 +89,9 @@ async function listPlugins() {
async function listEvents(count = 10) {
await db.init();
const eventData = await events.getEvents('', 0, count - 1);
console.log((`\nDisplaying last ${count} administrative events...`).bold);
console.log(chalk.bold(`\nDisplaying last ${count} administrative events...`));
eventData.forEach((event) => {
console.log(` * ${String(event.timestampISO).green} ${String(event.type).yellow}${event.text ? ` ${event.text}` : ''}${' (uid: '.reset}${event.uid ? event.uid : 0})`);
console.log(` * ${chalk.green(String(event.timestampISO))} ${chalk.yellow(String(event.type))}${event.text ? ` ${event.text}` : ''} (uid: ${event.uid ? event.uid : 0})`);
});
process.exit();
}

View File

@@ -1,9 +1,9 @@
'use strict';
require('colors');
const path = require('path');
const winston = require('winston');
const fs = require('fs');
const chalk = require('chalk');
const db = require('../database');
const events = require('../events');
@@ -57,8 +57,8 @@ exports.reset = async function (options) {
if (!tasks.length) {
console.log([
'No arguments passed in, so nothing was reset.\n'.yellow,
`Use ./nodebb reset ${'{-t|-p|-w|-s|-a}'.red}`,
chalk.yellow('No arguments passed in, so nothing was reset.\n'),
`Use ./nodebb reset ${chalk.red('{-t|-p|-w|-s|-a}')}`,
' -t\tthemes',
' -p\tplugins',
' -w\twidgets',

View File

@@ -2,6 +2,7 @@
const fs = require('fs');
const childProcess = require('child_process');
const chalk = require('chalk');
const fork = require('../meta/debugFork');
const { paths } = require('../constants');
@@ -39,17 +40,17 @@ function start(options) {
}
if (options.log) {
console.log(`\n${[
'Starting NodeBB with logging output'.bold,
'Hit '.red + 'Ctrl-C '.bold + 'to exit'.red,
chalk.bold('Starting NodeBB with logging output'),
chalk.red('Hit ') + chalk.bold('Ctrl-C ') + chalk.red('to exit'),
'The NodeBB process will continue to run in the background',
`Use "${'./nodebb stop'.yellow}" to stop the NodeBB server`,
`Use "${chalk.yellow('./nodebb stop')}" to stop the NodeBB server`,
].join('\n')}`);
} else if (!options.silent) {
console.log(`\n${[
'Starting NodeBB'.bold,
` "${'./nodebb stop'.yellow}" to stop the NodeBB server`,
` "${'./nodebb log'.yellow}" to view server output`,
` "${'./nodebb help'.yellow}${'" for more commands\n'.reset}`,
chalk.bold('Starting NodeBB'),
` "${chalk.yellow('./nodebb stop')}" to stop the NodeBB server`,
` "${chalk.yellow('./nodebb log')}" to view server output`,
` "${chalk.yellow('./nodebb help')}" for more commands\n`,
].join('\n')}`);
}
@@ -82,7 +83,7 @@ function stop() {
function restart(options) {
getRunningPid((err, pid) => {
if (!err) {
console.log('\nRestarting NodeBB'.bold);
console.log(chalk.bold('\nRestarting NodeBB'));
process.kill(pid, 'SIGTERM');
options.silent = true;
@@ -97,20 +98,20 @@ function status() {
getRunningPid((err, pid) => {
if (!err) {
console.log(`\n${[
'NodeBB Running '.bold + (`(pid ${pid.toString()})`).cyan,
`\t"${'./nodebb stop'.yellow}" to stop the NodeBB server`,
`\t"${'./nodebb log'.yellow}" to view server output`,
`\t"${'./nodebb restart'.yellow}" to restart NodeBB\n`,
chalk.bold('NodeBB Running ') + chalk.cyan(`(pid ${pid.toString()})`),
`\t"${chalk.yellow('./nodebb stop')}" to stop the NodeBB server`,
`\t"${chalk.yellow('./nodebb log')}" to view server output`,
`\t"${chalk.yellow('./nodebb restart')}" to restart NodeBB\n`,
].join('\n')}`);
} else {
console.log('\nNodeBB is not running'.bold);
console.log(`\t"${'./nodebb start'.yellow}${'" to launch the NodeBB server\n'.reset}`);
console.log(chalk.bold('\nNodeBB is not running'));
console.log(`\t"${chalk.yellow('./nodebb start')}" to launch the NodeBB server\n`);
}
});
}
function log() {
console.log('\nHit '.red + 'Ctrl-C '.bold + 'to exit\n'.red + '\n'.reset);
console.log(`${chalk.red('\nHit ') + chalk.bold('Ctrl-C ') + chalk.red('to exit\n')}\n`);
childProcess.spawn('tail', ['-F', './logs/output.log'], {
stdio: 'inherit',
cwd,

View File

@@ -7,6 +7,7 @@ const semver = require('semver');
const fs = require('fs');
const path = require('path');
const nconf = require('nconf');
const chalk = require('chalk');
const { paths, pluginNamePattern } = require('../constants');
@@ -96,11 +97,11 @@ async function checkPlugins() {
const toCheck = Object.keys(plugins);
if (!toCheck.length) {
process.stdout.write(' OK'.green + ''.reset);
process.stdout.write(chalk.green(' OK'));
return []; // no extraneous plugins installed
}
const suggestedModules = await getSuggestedModules(nbbVersion, toCheck);
process.stdout.write(' OK'.green + ''.reset);
process.stdout.write(chalk.green(' OK'));
let current;
let suggested;
@@ -125,12 +126,12 @@ async function upgradePlugins() {
try {
const found = await checkPlugins();
if (found && found.length) {
process.stdout.write(`\n\nA total of ${String(found.length).bold} package(s) can be upgraded:\n\n`);
process.stdout.write(`\n\nA total of ${chalk.bold(String(found.length))} package(s) can be upgraded:\n\n`);
found.forEach((suggestObj) => {
process.stdout.write(`${' * '.yellow + suggestObj.name.reset} (${suggestObj.current.yellow}${' -> '.reset}${suggestObj.suggested.green}${')\n'.reset}`);
process.stdout.write(`${chalk.yellow(' * ') + suggestObj.name} (${chalk.yellow(suggestObj.current)}' -> '${chalk.green(suggestObj.suggested)}')\n'`);
});
} else {
console.log('\nAll packages up-to-date!'.green + ''.reset);
console.log(chalk.green('\nAll packages up-to-date!'));
return;
}
@@ -140,7 +141,7 @@ async function upgradePlugins() {
prompt.start();
const result = await prompt.get({
name: 'upgrade',
description: '\nProceed with upgrade (y|n)?'.reset,
description: '\nProceed with upgrade (y|n)?',
type: 'string',
});
@@ -150,10 +151,10 @@ async function upgradePlugins() {
cproc.execFileSync(packageManagerExecutable, args, { stdio: 'ignore' });
} else {
console.log('Package upgrades skipped'.yellow + '. Check for upgrades at any time by running "'.reset + './nodebb upgrade -p'.green + '".'.reset);
console.log(`${chalk.yellow('Package upgrades skipped')}. Check for upgrades at any time by running "${chalk.green('./nodebb upgrade -p')}".`);
}
} catch (err) {
console.log('Warning'.yellow + ': An unexpected error occured when attempting to verify plugin upgradability'.reset);
console.log(`${chalk.yellow('Warning')}: An unexpected error occured when attempting to verify plugin upgradability`);
throw err;
}
}

View File

@@ -1,6 +1,7 @@
'use strict';
const nconf = require('nconf');
const chalk = require('chalk');
const packageInstall = require('./package-install');
const { upgradePlugins } = require('./upgrade-plugins');
@@ -11,13 +12,13 @@ const steps = {
handler: function () {
packageInstall.updatePackageFile();
packageInstall.preserveExtraneousPlugins();
process.stdout.write(' OK\n'.green);
process.stdout.write(chalk.green(' OK\n'));
},
},
install: {
message: 'Bringing base dependencies up to date...',
handler: function () {
process.stdout.write(' started\n'.green);
process.stdout.write(chalk.green(' started\n'));
packageInstall.installAll();
},
},
@@ -49,7 +50,7 @@ async function runSteps(tasks) {
for (let i = 0; i < tasks.length; i++) {
const step = steps[tasks[i]];
if (step && step.message && step.handler) {
process.stdout.write(`\n${(`${i + 1}. `).bold}${step.message.yellow}`);
process.stdout.write(`\n${chalk.bold(`${i + 1}. `)}${chalk.yellow(step.message)}`);
/* eslint-disable-next-line */
await step.handler();
}
@@ -60,7 +61,7 @@ async function runSteps(tasks) {
const { columns } = process.stdout;
const spaces = columns ? new Array(Math.floor(columns / 2) - (message.length / 2) + 1).join(' ') : ' ';
console.log(`\n\n${spaces}${message.green.bold}${'\n'.reset}`);
console.log(`\n\n${spaces}${chalk.green.bold(message)}\n`);
process.exit();
} catch (err) {
@@ -70,7 +71,7 @@ async function runSteps(tasks) {
}
async function runUpgrade(upgrades, options) {
console.log('\nUpdating NodeBB...'.cyan);
console.log(chalk.cyan('\nUpdating NodeBB...'));
options = options || {};
// disable mongo timeouts during upgrade
nconf.set('mongo:options:socketTimeoutMS', 0);