From b2df3cc0c8cba17c1995d3038e4a2caaca2f47f5 Mon Sep 17 00:00:00 2001 From: Mariusz Nowak Date: Thu, 23 Sep 2021 16:24:04 +0200 Subject: [PATCH] feat(CLI): Modern help output --- lib/cli/commands-schema/aws-service.js | 6 ++ lib/cli/render-help/command.js | 9 +- lib/cli/render-help/general.js | 91 ++++++++++++++++--- lib/cli/render-help/generate-command-usage.js | 12 ++- lib/cli/render-help/interactive-setup.js | 12 ++- lib/cli/render-help/options.js | 41 +++++++-- 6 files changed, 140 insertions(+), 31 deletions(-) diff --git a/lib/cli/commands-schema/aws-service.js b/lib/cli/commands-schema/aws-service.js index 2cabb27b5..520452ad3 100644 --- a/lib/cli/commands-schema/aws-service.js +++ b/lib/cli/commands-schema/aws-service.js @@ -6,6 +6,7 @@ const serviceCommands = require('./service'); const commands = (module.exports = new Map()); commands.set('deploy', { + groupName: 'main', usage: 'Deploy a Serverless service', options: { 'conceal': { @@ -48,6 +49,7 @@ commands.set('deploy', { }); commands.set('deploy function', { + groupName: 'main', usage: 'Deploy a single function from the service', options: { 'function': { @@ -78,6 +80,7 @@ commands.set('deploy list functions', { }); commands.set('info', { + groupName: 'main', usage: 'Display information about the service', options: { conceal: { @@ -94,6 +97,7 @@ commands.set('info', { }); commands.set('invoke', { + groupName: 'main', usage: 'Invoke a deployed function', options: { function: { @@ -137,6 +141,7 @@ commands.set('invoke', { }); commands.set('invoke local', { + groupName: 'main', usage: 'Invoke function locally', options: { 'function': { @@ -177,6 +182,7 @@ commands.set('invoke local', { }); commands.set('logs', { + groupName: 'main', usage: 'Output the logs of a deployed function', options: { function: { diff --git a/lib/cli/render-help/command.js b/lib/cli/render-help/command.js index 7c92ccdde..50e5cc31e 100644 --- a/lib/cli/render-help/command.js +++ b/lib/cli/render-help/command.js @@ -1,5 +1,6 @@ 'use strict'; +const { legacy, writeText } = require('@serverless/utils/log'); const resolveInput = require('../resolve-input'); const renderOptionsHelp = require('./options'); @@ -9,10 +10,14 @@ module.exports = (commandName) => { const { commandsSchema } = resolveInput(); const commandSchema = commandsSchema.get(commandName); - if (commandSchema) process.stdout.write(`${generateCommandUsage(commandName, commandSchema)}\n`); + if (commandSchema) { + legacy.write(`${generateCommandUsage(commandName, commandSchema)}\n`); + writeText(generateCommandUsage(commandName, commandSchema, { isModern: true })); + } for (const [subCommandName, subCommandSchema] of commandsSchema) { if (!subCommandName.startsWith(`${commandName} `)) continue; - process.stdout.write(`${generateCommandUsage(subCommandName, subCommandSchema)}\n`); + legacy.write(`${generateCommandUsage(subCommandName, subCommandSchema)}\n`); + writeText(generateCommandUsage(subCommandName, subCommandSchema, { isModern: true })); } if (commandSchema) renderOptionsHelp(Object.assign({}, commandSchema.options)); diff --git a/lib/cli/render-help/general.js b/lib/cli/render-help/general.js index c86c2acbf..74e5b34c5 100644 --- a/lib/cli/render-help/general.js +++ b/lib/cli/render-help/general.js @@ -1,12 +1,86 @@ 'use strict'; const chalk = require('chalk'); +const { legacy, writeText, style } = require('@serverless/utils/log'); +const { version } = require('../../../package'); +const globalOptions = require('../commands-schema/common-options/global'); const resolveInput = require('../resolve-input'); const generateCommandUsage = require('./generate-command-usage'); +const renderOptions = require('./options'); module.exports = (loadedPlugins) => { const { commandsSchema } = resolveInput(); + writeText( + `Serverless Framework v${version}`, + null, + style.aside('Usage'), + 'serverless ', + 'sls ', + null, + style.aside('Get started'), + `Run ${style.error('serverless')} to interactively setup a project.`, + 'Use --help-interactive to display the interactive setup help.', + null, + style.aside('Monitoring'), + 'Enable performance and error monitoring with the Serverless Dashboard.', + `Learn more: ${style.link('https://serverless.com/monitoring')}`, + null, + style.aside('Plugins'), + 'Extend the Serverless Framework with plugins.', + `Explore plugins: ${style.link('https://serverless.com/plugins')}`, + null, + style.aside('Options') + ); + + renderOptions(globalOptions, { shouldWriteModernOnly: true }); + + const allCommands = new Map( + Array.from(commandsSchema).filter( + ([commandName, { isHidden, lifecycleEvents }]) => commandName && !isHidden && lifecycleEvents + ) + ); + const mainCommands = new Map( + Array.from(allCommands).filter(([, { groupName }]) => groupName === 'main') + ); + + if (mainCommands.size) { + writeText(null, style.aside('Main commands')); + for (const [commandName, commandSchema] of mainCommands) { + writeText(generateCommandUsage(commandName, commandSchema, { isModern: true })); + } + writeText(null, style.aside('Other commands')); + } else { + writeText(null, style.aside('All commands')); + } + + const extensionCommandsSchema = new Map(); + + for (const [commandName, commandSchema] of allCommands) { + if (commandSchema.isExtension) { + if (!extensionCommandsSchema.has(commandSchema.sourcePlugin)) { + extensionCommandsSchema.set(commandSchema.sourcePlugin, new Map()); + } + extensionCommandsSchema.get(commandSchema.sourcePlugin).set(commandName, commandSchema); + continue; + } + if (commandSchema.groupName === 'main') continue; + + writeText(generateCommandUsage(commandName, commandSchema, { isModern: true })); + } + + if (loadedPlugins.size) { + if (extensionCommandsSchema.size) { + for (const [plugin, pluginCommandsSchema] of extensionCommandsSchema) { + writeText(null, style.aside(plugin.constructor.name)); + for (const [commandName, commandSchema] of pluginCommandsSchema) { + writeText(generateCommandUsage(commandName, commandSchema, { isModern: true })); + } + } + } + } + writeText(); + const lines = [ '', chalk.yellow.underline('Commands'), @@ -42,19 +116,8 @@ module.exports = (loadedPlugins) => { '', ]; - const extensionCommandsSchema = new Map(); - - for (const [commandName, commandSchema] of commandsSchema) { - if (!commandName) continue; - if (commandSchema.isHidden) continue; - if (!commandSchema.lifecycleEvents) continue; - if (commandSchema.isExtension) { - if (!extensionCommandsSchema.has(commandSchema.sourcePlugin)) { - extensionCommandsSchema.set(commandSchema.sourcePlugin, new Map()); - } - extensionCommandsSchema.get(commandSchema.sourcePlugin).set(commandName, commandSchema); - continue; - } + for (const [commandName, commandSchema] of allCommands) { + if (commandSchema.isExtension) continue; lines.push(generateCommandUsage(commandName, commandSchema)); } @@ -80,5 +143,5 @@ module.exports = (loadedPlugins) => { } } - process.stdout.write(`${lines.join('\n')}\n`); + legacy.write(`${lines.join('\n')}\n`); }; diff --git a/lib/cli/render-help/generate-command-usage.js b/lib/cli/render-help/generate-command-usage.js index 252e63066..abc568ec7 100644 --- a/lib/cli/render-help/generate-command-usage.js +++ b/lib/cli/render-help/generate-command-usage.js @@ -1,11 +1,17 @@ 'use strict'; const chalk = require('chalk'); +const { style } = require('@serverless/utils/log'); -module.exports = (commandName, commandSchema) => { - const dotsLength = 30; +module.exports = (commandName, commandSchema, options = {}) => { + const indentFillLength = 30; const usage = commandSchema.usage; - const dots = '.'.repeat(Math.max(dotsLength - commandName.length, 0)); + if (options.isModern) { + return `${commandName} ${' '.repeat( + Math.max(indentFillLength - commandName.length, 0) + )} ${style.aside(usage)}`; + } + const dots = '.'.repeat(Math.max(indentFillLength - commandName.length, 0)); return `${chalk.yellow(commandName)} ${chalk.dim(dots)} ${usage}`; }; diff --git a/lib/cli/render-help/interactive-setup.js b/lib/cli/render-help/interactive-setup.js index 528de1d12..d828e2832 100644 --- a/lib/cli/render-help/interactive-setup.js +++ b/lib/cli/render-help/interactive-setup.js @@ -1,12 +1,20 @@ 'use strict'; const chalk = require('chalk'); +const { legacy, writeText, style } = require('@serverless/utils/log'); const commmandSchema = require('../commands-schema/no-service').get(''); const renderOptions = require('./options'); module.exports = () => { - process.stdout.write(`${chalk.yellow.underline('Interactive CLI')}\n`); - process.stdout.write( + writeText( + style.aside('Interactive CLI'), + `Run ${style.error('serverless')} to interactively setup a project.`, + null, + style.aside('Options') + ); + + legacy.write(`${chalk.yellow.underline('Interactive CLI')}\n`); + legacy.write( `${chalk.yellow( `Run serverless (or shortcut sls) a subcommand to initialize an interactive setup of functionalities related to given service or current environment` diff --git a/lib/cli/render-help/options.js b/lib/cli/render-help/options.js index 35c38da04..2aef88733 100644 --- a/lib/cli/render-help/options.js +++ b/lib/cli/render-help/options.js @@ -1,32 +1,53 @@ 'use strict'; const chalk = require('chalk'); +const { legacy, writeText, style } = require('@serverless/utils/log'); -module.exports = (commandOptions) => { - const dotsLength = 40; +module.exports = (commandOptions, options = {}) => { + const indentFillLength = 40; for (const [option, optionsObject] of Object.entries(commandOptions)) { - let optionsDots = '.'.repeat(Math.max(dotsLength - option.length, 0)); + let legacyOptionsIndentFill = '.'.repeat(Math.max(indentFillLength - option.length, 0)); + let optionsIndentFill = ' '.repeat(Math.max(indentFillLength - option.length - 4, 0)); if (optionsObject.required) { - optionsDots = optionsDots.slice(0, optionsDots.length - 18); + legacyOptionsIndentFill = legacyOptionsIndentFill.slice( + 0, + legacyOptionsIndentFill.length - 18 + ); + optionsIndentFill = optionsIndentFill.slice(0, optionsIndentFill.length - 18); } else { - optionsDots = optionsDots.slice(0, optionsDots.length - 7); + legacyOptionsIndentFill = legacyOptionsIndentFill.slice( + 0, + legacyOptionsIndentFill.length - 7 + ); + optionsIndentFill = optionsIndentFill.slice(0, optionsIndentFill.length - 7); } if (optionsObject.shortcut) { - optionsDots = optionsDots.slice(0, optionsDots.length - 5); + legacyOptionsIndentFill = legacyOptionsIndentFill.slice( + 0, + legacyOptionsIndentFill.length - 5 + ); + optionsIndentFill = optionsIndentFill.slice(0, optionsIndentFill.length - 5); } - const optionInfo = ` --${option}`; let shortcutInfo = ''; let requiredInfo = ''; if (optionsObject.shortcut) shortcutInfo = ` / -${optionsObject.shortcut}`; if (optionsObject.required) requiredInfo = ' (required)'; - const optionsUsage = optionsObject.usage ? chalk.dim(optionsDots) + optionsObject.usage : ''; - const output = `${optionInfo}${shortcutInfo}${requiredInfo} ${optionsUsage}`; + if (!options.shouldWriteModernOnly) { + const legacyOptionsUsage = optionsObject.usage + ? chalk.dim(legacyOptionsIndentFill) + optionsObject.usage + : ''; + const legacyOutput = ` --${option}${shortcutInfo}${requiredInfo} ${legacyOptionsUsage}`; + legacy.write(`${chalk.yellow(legacyOutput)}\n`); + } - process.stdout.write(`${chalk.yellow(output)}\n`); + const optionsUsage = optionsObject.usage + ? optionsIndentFill + style.aside(optionsObject.usage) + : ''; + writeText(`--${option}${shortcutInfo}${requiredInfo} ${optionsUsage}`); } };