feat(CLI): Modern help output

This commit is contained in:
Mariusz Nowak 2021-09-23 16:24:04 +02:00 committed by Mariusz Nowak
parent 8c9bd4a6ed
commit b2df3cc0c8
6 changed files with 140 additions and 31 deletions

View File

@ -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: {

View File

@ -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));

View File

@ -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 <command> <options>',
'sls <command> <options>',
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`);
};

View File

@ -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}`;
};

View File

@ -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`

View File

@ -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}`);
}
};