From 69fadd30b6990be6385d74348fd6c73b035a4868 Mon Sep 17 00:00:00 2001 From: Jon Ursenbach Date: Thu, 2 Dec 2021 12:10:47 -0800 Subject: [PATCH] feat: overhauling the help screen generator so it's consistent everywhere (#397) * feat: swapping our colors for chalk * chore: reverting some unnnecessary changes * feat: overhauling the help screen generator so it's consistent everywhere * feat: simplify command listings * ci: fixing chalk to always run in ci * fix: reverting a command description change * test: updating snapshots * fix: "check out" instead of "checkout" --- .github/dependabot.yml | 4 - .github/workflows/ci.yml | 2 +- README.md | 2 +- __tests__/__snapshots__/index.test.js.snap | 234 +++++++++++++++++++++ __tests__/cmds/logout.test.js | 2 +- __tests__/index.test.js | 122 +++++------ __tests__/lib/commands.test.js | 3 +- package-lock.json | 13 +- package.json | 1 - src/cmds/logout.js | 2 +- src/cmds/oas.js | 2 +- src/cmds/swagger.js | 2 +- src/lib/commands.js | 2 +- src/lib/help.js | 108 ++++------ 14 files changed, 346 insertions(+), 153 deletions(-) create mode 100644 __tests__/__snapshots__/index.test.js.snap diff --git a/.github/dependabot.yml b/.github/dependabot.yml index 8771d3cbd..bd6339673 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -33,7 +33,3 @@ updates: - dependency-name: node-fetch versions: - ">= 3" - # table-layout is now an ESM package and can't be used here without a rewrite. - - dependency-name: table-layout - versions: - - ">= 3" diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index c02252b98..0c391e37a 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -26,6 +26,6 @@ jobs: - name: Run tests run: npm test env: - # `chalk` has troubles with color detection while on CI and also in how it's used within our tests. + # `chalk` has troubles with color detection while on CI. # https://github.com/chalk/supports-color/issues/106 FORCE_COLOR: 1 diff --git a/README.md b/README.md index ebe4245a6..6e3b4b550 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# `rdme` - ReadMe's CLI +# 📖 rdme [![](https://d3vv6lp55qjaqc.cloudfront.net/items/1M3C3j0I0s0j3T362344/Untitled-2.png)](https://readme.com) diff --git a/__tests__/__snapshots__/index.test.js.snap b/__tests__/__snapshots__/index.test.js.snap new file mode 100644 index 000000000..17eaeda95 --- /dev/null +++ b/__tests__/__snapshots__/index.test.js.snap @@ -0,0 +1,234 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`cli --help should not surface args that are designated as hidden 1`] = ` +" +Alias for \`rdme openapi\`. [deprecated] + +Usage + + rdme swagger [file] [options] + +Options + + --key string Project API key + --id string Unique identifier for your API definition. Use this if you're resyncing an + existing API definition + --version string Project version + -h, --help Display this usage guide + +Related commands + + $ rdme openapi Upload, or resync, your OpenAPI/Swagger definition to ReadMe. +" +`; + +exports[`cli --help should print help 1`] = ` +" + 📖 rdme + + a utlity for interacting with ReadMe + . + .\\\\ /. + ’ ‘ ‘ ‘ + ( nn\\\\ . . / ) + (nnnnn,.MM. AM .nn. + .nnnndMM----_______--M.’’ / + |nnn/nnnnnnnnnnnnnnnnn\\\\’mmm/ + /nnnn...nnnnnnnnnnn...mmmmm\\\\ + /nn ‘qnnnP’ ‘mmm\\\\ + /n’ .XXX. \\\\nnn/ .XX. \\\\mmb + An (XOXX) nnn (XOXX) mm\\\\ + /nn ‘XXXX’.~”~. ‘XXX”’ .mmmb + dnnn. ( )n. .mmmmb + .nnnnnn....n.\\\\ ./nnnnnnnnnmmmmmm\\\\ + (READnnnnnnnnnnn’Y’nnnnnnnnnnmmmmmmME) + REinnnnnnnnnnnnnnnnnnnnnnnnnmmmmmmmAD/ + /MEEnnnnnnnnnnnnnnnnnnnnnnnnnmmmmmmm)E'. + READnnnnnnn*’ ‘*mmmmmmmm)MEE. +(READ|nnnn’ \\\\../ \\\\.../ ‘mmmmmM)EEE) + READ(nn*’ ‘mmm.MEEE) + ‘READn’ \\\\._./ \\\\__./ \\\\.../ ‘MEE*’ + * /* + +Usage + + rdme [arguments] + +Options + + -h, --help Display this usage guide + -v, --version Show the current rdme version + +Administration + + $ rdme login Login to a ReadMe project. + $ rdme logout Logs the currently authenticated user out of ReadMe. + $ rdme whoami Displays the current user and project authenticated with ReadMe. + +Upload OpenAPI/Swagger definitions + + $ rdme openapi Upload, or resync, your OpenAPI/Swagger definition to ReadMe. + $ rdme swagger Alias for \`rdme openapi\`. [deprecated] + +Documentation + + $ rdme docs Sync a folder of markdown files to your ReadMe project. + $ rdme docs:edit Edit a single file from your ReadMe project without saving locally. + +Versions + + $ rdme versions List versions available in your project or get a version by SemVer + (https://semver.org/). + $ rdme versions:create Create a new version for your project. + $ rdme versions:update Update an existing version for your project. + $ rdme versions:delete Delete a version associated with your ReadMe project. + +Other useful commands + + $ rdme oas Helpful OpenAPI generation tooling. + $ rdme open Open your current ReadMe project in the browser. + +Run rdme help  for help with a specific command. + +To get more help with ReadMe, check out our docs at https://docs.readme.com. +" +`; + +exports[`cli --help should print help for the \`-H\` alias 1`] = ` +" + 📖 rdme + + a utlity for interacting with ReadMe + . + .\\\\ /. + ’ ‘ ‘ ‘ + ( nn\\\\ . . / ) + (nnnnn,.MM. AM .nn. + .nnnndMM----_______--M.’’ / + |nnn/nnnnnnnnnnnnnnnnn\\\\’mmm/ + /nnnn...nnnnnnnnnnn...mmmmm\\\\ + /nn ‘qnnnP’ ‘mmm\\\\ + /n’ .XXX. \\\\nnn/ .XX. \\\\mmb + An (XOXX) nnn (XOXX) mm\\\\ + /nn ‘XXXX’.~”~. ‘XXX”’ .mmmb + dnnn. ( )n. .mmmmb + .nnnnnn....n.\\\\ ./nnnnnnnnnmmmmmm\\\\ + (READnnnnnnnnnnn’Y’nnnnnnnnnnmmmmmmME) + REinnnnnnnnnnnnnnnnnnnnnnnnnmmmmmmmAD/ + /MEEnnnnnnnnnnnnnnnnnnnnnnnnnmmmmmmm)E'. + READnnnnnnn*’ ‘*mmmmmmmm)MEE. +(READ|nnnn’ \\\\../ \\\\.../ ‘mmmmmM)EEE) + READ(nn*’ ‘mmm.MEEE) + ‘READn’ \\\\._./ \\\\__./ \\\\.../ ‘MEE*’ + * /* + +Usage + + rdme [arguments] + +Options + + -h, --help Display this usage guide + -v, --version Show the current rdme version + +Administration + + $ rdme login Login to a ReadMe project. + $ rdme logout Logs the currently authenticated user out of ReadMe. + $ rdme whoami Displays the current user and project authenticated with ReadMe. + +Upload OpenAPI/Swagger definitions + + $ rdme openapi Upload, or resync, your OpenAPI/Swagger definition to ReadMe. + $ rdme swagger Alias for \`rdme openapi\`. [deprecated] + +Documentation + + $ rdme docs Sync a folder of markdown files to your ReadMe project. + $ rdme docs:edit Edit a single file from your ReadMe project without saving locally. + +Versions + + $ rdme versions List versions available in your project or get a version by SemVer + (https://semver.org/). + $ rdme versions:create Create a new version for your project. + $ rdme versions:update Update an existing version for your project. + $ rdme versions:delete Delete a version associated with your ReadMe project. + +Other useful commands + + $ rdme oas Helpful OpenAPI generation tooling. + $ rdme open Open your current ReadMe project in the browser. + +Run rdme help  for help with a specific command. + +To get more help with ReadMe, check out our docs at https://docs.readme.com. +" +`; + +exports[`cli --help should print usage for a given command 1`] = ` +" +Alias for \`rdme openapi\`. [deprecated] + +Usage + + rdme swagger [file] [options] + +Options + + --key string Project API key + --id string Unique identifier for your API definition. Use this if you're resyncing an + existing API definition + --version string Project version + -h, --help Display this usage guide + +Related commands + + $ rdme openapi Upload, or resync, your OpenAPI/Swagger definition to ReadMe. +" +`; + +exports[`cli --help should print usage for a given command if supplied as \`help \` 1`] = ` +" +Alias for \`rdme openapi\`. [deprecated] + +Usage + + rdme swagger [file] [options] + +Options + + --key string Project API key + --id string Unique identifier for your API definition. Use this if you're resyncing an + existing API definition + --version string Project version + -h, --help Display this usage guide + +Related commands + + $ rdme openapi Upload, or resync, your OpenAPI/Swagger definition to ReadMe. +" +`; + +exports[`cli --help should show related commands for a subcommands help menu 1`] = ` +" +List versions available in your project or get a version by SemVer (https://semver.org/). + +Usage + + rdme versions [options] + +Options + + --key string Project API key + --version string A specific project version to view + --raw Return raw output from the API instead of in a \\"pretty\\" format. + -h, --help Display this usage guide + +Related commands + + $ rdme versions:create Create a new version for your project. + $ rdme versions:update Update an existing version for your project. + $ rdme versions:delete Delete a version associated with your ReadMe project. +" +`; diff --git a/__tests__/cmds/logout.test.js b/__tests__/cmds/logout.test.js index 0c7b46325..91f7ced6d 100644 --- a/__tests__/cmds/logout.test.js +++ b/__tests__/cmds/logout.test.js @@ -10,7 +10,7 @@ describe('rdme logout', () => { return cmd.run({}).then(msg => { expect(msg).toBe( - `You have logged out of Readme. Please use \`${config.cli} ${loginCmd.command}\` to login again.` + `You have logged out of ReadMe. Please use \`${config.cli} ${loginCmd.command}\` to login again.` ); }); }); diff --git a/__tests__/index.test.js b/__tests__/index.test.js index 57b190c21..12018f670 100644 --- a/__tests__/index.test.js +++ b/__tests__/index.test.js @@ -2,39 +2,37 @@ const nock = require('nock'); const cli = require('../src'); const { version } = require('../package.json'); const conf = require('../src/lib/configstore'); -const swaggerCmd = require('../src/cmds/swagger'); describe('cli', () => { - it('command not found', () => { - expect.assertions(1); - return cli('notARealCommand').catch(e => { - expect(e.message).toContain('Command not found'); - }); + it('command not found', async () => { + await expect(cli('notARealCommand')).rejects.toThrow('Command not found'); }); describe('--version', () => { - it('should return version from package.json', () => cli(['--version']).then(v => expect(v).toBe(version))); + it('should return version from package.json', async () => { + await expect(cli(['--version'])).resolves.toBe(version); + }); - it('should return version if the `-V` alias is supplied', () => cli(['-V']).then(v => expect(v).toBe(version))); + it('should return version if the `-V` alias is supplied', async () => { + await expect(cli(['-V'])).resolves.toBe(version); + }); - it('should return version from package.json for help command', () => - cli(['help', '--version']).then(v => expect(v).toBe(version))); + it('should return version from package.json for help command', async () => { + await expect(cli(['help', '--version'])).resolves.toBe(version); + }); // This is necessary because we use --version for other commands like `docs` - it('should only return version if no command', () => { - expect.assertions(1); - - return expect(cli(['no-such-command', '--version'])).rejects.toThrow( + it('should only return version if no command', async () => { + await expect(cli(['no-such-command', '--version'])).rejects.toThrow( // This can be ignored as it's just going to be a command not found error 'Command not found.' ); }); - it('should not be returned if `--version` is being used on a subcommand', () => { - expect.assertions(1); + it('should not be returned if `--version` is being used on a subcommand', async () => { nock.disableNetConnect(); - return expect(cli(['docs:edit', 'getting-started', '--version', '1.0.0', '--key=abcdef'])).rejects.not.toThrow( + await expect(cli(['docs:edit', 'getting-started', '--version', '1.0.0', '--key=abcdef'])).rejects.not.toThrow( // We're testing that the docs:edit command does NOT return an error about `--version` not // being here because if it throws that error, then that means that `--version` wasn't // passed in as expected. @@ -44,79 +42,73 @@ describe('cli', () => { }); describe('--help', () => { - it('should print help', () => - cli(['--help']).then(output => { - expect(output).not.toBe(version); - })); - - it('should print help for the `-H` alias', () => - cli(['-H']).then(output => { - expect(output).not.toBe(version); - })); - - it('should print usage for a given command', () => { - return cli(['swagger', '--help']).then(output => { - expect(output).toContain(swaggerCmd.description); - expect(output).toContain(swaggerCmd.usage); - expect(output).toContain('--key'); - expect(output).toContain('--help'); - }); + beforeEach(() => { + // The `table-layout` dependency within `command-line-usage` has a quirk in the 1.x family where if it's being + // run within a Jest environment it won't always use the same amount of columns for tables, resulting in cases + // where Jest snapshots will randomly return back different results. + // + // This issue has been fixed in later versions of `table-layout` but `command-line-usage` is still targeting the + // 1.x series. + // + // See https://github.com/75lb/table-layout/issues/9 for more information. + process.stdout.columns = 100; + process.stderr.columns = 100; + }); + + afterEach(() => { + process.stdout.columns = undefined; + process.stderr.columns = undefined; }); - it('should print usage for a given command if supplied as `help `', () => { - return cli(['help', 'swagger']).then(output => { - expect(output).toContain(swaggerCmd.description); - expect(output).toContain(swaggerCmd.usage); - expect(output).toContain('--key'); - expect(output).toContain('--help'); - }); + it('should print help', async () => { + await expect(cli(['--help'])).resolves.toMatchSnapshot(); }); - it('should not surface args that are designated as hidden', () => { - return cli(['swagger', '--help']).then(output => { - expect(output).not.toContain('---token'); - expect(output).not.toContain('---spec'); - }); + it('should print help for the `-H` alias', async () => { + await expect(cli(['-H'])).resolves.toMatchSnapshot(); }); - it('should show related commands for a subcommands help menu', () => { - return cli(['versions', '--help']).then(output => { - expect(output).toContain('Related commands'); - expect(output).toContain('versions:create'); - }); + it('should print usage for a given command', async () => { + await expect(cli(['swagger', '--help'])).resolves.toMatchSnapshot(); }); - it.todo('should not show related commands on commands that have none'); + it('should print usage for a given command if supplied as `help `', async () => { + await expect(cli(['help', 'swagger'])).resolves.toMatchSnapshot(); + }); + + it('should not surface args that are designated as hidden', async () => { + await expect(cli(['swagger', '--help'])).resolves.toMatchSnapshot(); + }); + + it('should show related commands for a subcommands help menu', async () => { + await expect(cli(['versions', '--help'])).resolves.toMatchSnapshot(); + }); }); describe('subcommands', () => { // docs:edit will make a backend connection beforeAll(() => nock.disableNetConnect()); - it('should load subcommands from the folder', () => { - expect.assertions(1); - return expect(cli(['docs:edit', 'getting-started', '--version=1.0.0', '--key=abcdef'])).rejects.not.toThrow( + it('should load subcommands from the folder', async () => { + await expect(cli(['docs:edit', 'getting-started', '--version=1.0.0', '--key=abcdef'])).rejects.not.toThrow( 'Command not found.' ); }); }); - it('should not error with undefined cmd', () => { - return cli([]).then(resp => { - expect(resp).toContain('Available commands'); - }); + it('should not error with undefined cmd', async () => { + await expect(cli([])).resolves.toContain('Upload OpenAPI/Swagger definitions'); }); - it('should add stored apiKey to opts', () => { + it('should add stored apiKey to opts', async () => { expect.assertions(1); conf.set('apiKey', '123456'); - return cli(['docs']).catch(err => { - conf.delete('apiKey'); - expect(err.message).toBe('No folder provided. Usage `rdme docs [options]`.'); - }); + + await expect(cli(['docs'])).rejects.toThrow('No folder provided. Usage `rdme docs [options]`.'); + conf.delete('apiKey'); }); - it('should not error with oas arguments passed in', () => { + it('should not error with `rdme oas` arguments passed in', () => { expect(() => { return cli(['oas', 'endpoint']); }).not.toThrow(); diff --git a/__tests__/lib/commands.test.js b/__tests__/lib/commands.test.js index 660ed2e68..1a683dd41 100644 --- a/__tests__/lib/commands.test.js +++ b/__tests__/lib/commands.test.js @@ -34,7 +34,8 @@ describe('utils', () => { commands.list().forEach(c => { const cmd = c.command; - expect(cmd.description[cmd.description.length - 1]).toBe('.'); + // Command descriptions should end with punctuation. + expect(cmd.description[cmd.description.length - 1]).toMatch(/(.|])/); cmd.args.forEach(arg => { if (arg.name === 'key') { diff --git a/package-lock.json b/package-lock.json index 380b53be1..6da307679 100644 --- a/package-lock.json +++ b/package-lock.json @@ -27,7 +27,6 @@ "parse-link-header": "^1.0.1", "read": "^1.0.7", "semver": "^7.0.0", - "table-layout": "^1.0.0", "tmp-promise": "^3.0.2" }, "bin": { @@ -9131,9 +9130,9 @@ "dev": true }, "node_modules/table-layout": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/table-layout/-/table-layout-1.0.1.tgz", - "integrity": "sha512-dEquqYNJiGwY7iPfZ3wbXDI944iqanTSchrACLL2nOB+1r+h1Nzu2eH+DuPPvWvm5Ry7iAPeFlgEtP5bIp5U7Q==", + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/table-layout/-/table-layout-1.0.2.tgz", + "integrity": "sha512-qd/R7n5rQTRFi+Zf2sk5XVVd9UQl6ZkduPFC3S7WEGJAmetDTjY3qPN50eSKzwuzEyQKy5TN2TiZdkIjos2L6A==", "dependencies": { "array-back": "^4.0.1", "deep-extend": "~0.6.0", @@ -16794,9 +16793,9 @@ "dev": true }, "table-layout": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/table-layout/-/table-layout-1.0.1.tgz", - "integrity": "sha512-dEquqYNJiGwY7iPfZ3wbXDI944iqanTSchrACLL2nOB+1r+h1Nzu2eH+DuPPvWvm5Ry7iAPeFlgEtP5bIp5U7Q==", + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/table-layout/-/table-layout-1.0.2.tgz", + "integrity": "sha512-qd/R7n5rQTRFi+Zf2sk5XVVd9UQl6ZkduPFC3S7WEGJAmetDTjY3qPN50eSKzwuzEyQKy5TN2TiZdkIjos2L6A==", "requires": { "array-back": "^4.0.1", "deep-extend": "~0.6.0", diff --git a/package.json b/package.json index 09541b2fa..44bb6789d 100644 --- a/package.json +++ b/package.json @@ -51,7 +51,6 @@ "parse-link-header": "^1.0.1", "read": "^1.0.7", "semver": "^7.0.0", - "table-layout": "^1.0.0", "tmp-promise": "^3.0.2" }, "devDependencies": { diff --git a/src/cmds/logout.js b/src/cmds/logout.js index 95b05ab9b..49d472be0 100644 --- a/src/cmds/logout.js +++ b/src/cmds/logout.js @@ -16,6 +16,6 @@ exports.run = async () => { } return Promise.resolve( - `You have logged out of Readme. Please use \`${config.cli} ${loginCmd.command}\` to login again.` + `You have logged out of ReadMe. Please use \`${config.cli} ${loginCmd.command}\` to login again.` ); }; diff --git a/src/cmds/oas.js b/src/cmds/oas.js index 67d07bd8b..051a227de 100644 --- a/src/cmds/oas.js +++ b/src/cmds/oas.js @@ -3,7 +3,7 @@ const path = require('path'); exports.command = 'oas'; exports.usage = 'oas'; -exports.description = 'OAS related tasks. See https://npm.im/oas for more information.'; +exports.description = 'Helpful OpenAPI generation tooling.'; exports.category = 'utilities'; exports.position = 1; diff --git a/src/cmds/swagger.js b/src/cmds/swagger.js index 8fd066761..19fc5f7e3 100644 --- a/src/cmds/swagger.js +++ b/src/cmds/swagger.js @@ -2,7 +2,7 @@ const openapi = require('./openapi'); exports.command = 'swagger'; exports.usage = 'swagger [file] [options]'; -exports.description = 'Alias for `rdme openapi` [deprecated].'; +exports.description = 'Alias for `rdme openapi`. [deprecated]'; exports.category = openapi.category; exports.position = openapi.position + 1; diff --git a/src/lib/commands.js b/src/lib/commands.js index 1ba62f976..65e8f416f 100644 --- a/src/lib/commands.js +++ b/src/lib/commands.js @@ -72,7 +72,7 @@ exports.getCategories = () => { commands: [], }, apis: { - description: 'Sync Swagger/OpenAPI files', + description: 'Upload OpenAPI/Swagger definitions', commands: [], }, docs: { diff --git a/src/lib/help.js b/src/lib/help.js index 075fed106..c9b10f5bd 100644 --- a/src/lib/help.js +++ b/src/lib/help.js @@ -1,16 +1,22 @@ const chalk = require('chalk'); const config = require('config'); -const Table = require('table-layout'); const usage = require('command-line-usage'); const commands = require('./commands'); -function styleCommand(command) { - return `${chalk.grey('$')} ${config.cli} ${command.name}`; +function formatCommands(cmds) { + return cmds + .sort((a, b) => (a.position > b.position ? 1 : -1)) + .map(command => { + return { + name: `${chalk.grey('$')} ${config.cli} ${command.name}`, + summary: command.description, + }; + }); } const owlbert = () => { // http://asciiart.club - return ` ${chalk.blue.bold(config.cli)} + return ` 📖 ${chalk.blue.bold(config.cli)} ${chalk.bold(`a utlity for interacting with ReadMe`)} . @@ -38,13 +44,6 @@ const owlbert = () => { }; exports.commandUsage = cmd => { - cmd.args.push({ - name: 'help', - alias: 'h', - type: Boolean, - description: 'Display this usage guide', - }); - const helpContent = [ { content: cmd.description, @@ -56,24 +55,23 @@ exports.commandUsage = cmd => { }, { header: 'Options', - optionList: cmd.args, + optionList: [...cmd.args].concat([ + { + name: 'help', + alias: 'h', + type: Boolean, + description: 'Display this usage guide', + }, + ]), hide: cmd.hiddenArgs || [], }, ]; const similarCommands = commands.getSimilar(cmd.category, cmd.command); if (similarCommands.length) { - const related = []; - similarCommands.forEach(similar => { - related.push({ - name: styleCommand(similar), - summary: similar.description, - }); - }); - helpContent.push({ header: 'Related commands', - content: related, + content: formatCommands(similarCommands), }); } @@ -87,63 +85,37 @@ exports.globalUsage = async args => { raw: true, }, { - content: `Usage: ${config.cli} [arguments]`, - raw: true, + header: 'Usage', + content: `${config.cli} [arguments]`, + }, + { + header: 'Options', + optionList: args, + hide: ['command'], }, ]; const categories = commands.listByCategory(); - let availableCommands = []; Object.keys(categories).forEach(key => { const category = categories[key]; - const commandCategory = { - header: category.description, - content: [], - }; - category.commands - .sort((a, b) => (a.position > b.position ? 1 : -1)) - .forEach(command => { - commandCategory.content.push({ - command: styleCommand(command), - description: command.description, - }); - }); - - availableCommands.push(` ${chalk.bold(commandCategory.header)}`); - availableCommands = availableCommands.concat( - new Table(commandCategory.content, { - columns: [ - { name: 'command', noWrap: true, width: 28, padding: { left: ' ', right: '' } }, - { name: 'description', maxWidth: 65 }, - ], - }).renderLines() - ); - - availableCommands.push(''); - }); - - // Pop off the last whitespace row in the availableCommands table so we don't have two line breaks - // between it and the options list. - delete availableCommands[availableCommands.length - 1]; - - helpContent.push({ - header: 'Available commands', - content: availableCommands, - raw: true, - }); - - helpContent.push({ - header: 'Options', - optionList: args, - hide: ['command'], + helpContent.push({ + header: category.description, + content: formatCommands(category.commands), + }); }); - helpContent.push({ - content: `Run ${chalk.dim(`${config.cli} help `)} for help with a specific command.`, - raw: true, - }); + helpContent.push( + { + content: `Run ${chalk.dim(`${config.cli} help `)} for help with a specific command.`, + raw: true, + }, + { + content: `To get more help with ReadMe, check out our docs at https://docs.readme.com.`, + raw: true, + } + ); return usage(helpContent); };