diff --git a/spec/CLI.spec.js b/spec/CLI.spec.js index 39061b8083..a7017dde18 100644 --- a/spec/CLI.spec.js +++ b/spec/CLI.spec.js @@ -191,6 +191,55 @@ describe('definitions', () => { }); }); +describe('CLI help formatting (#8434)', () => { + const loadInto = defs => { + const command = require('../lib/cli/utils/commander').default; + const program = new command.constructor(); + program.storeOptionsAsProperties(); + program.allowExcessArguments(); + program.loadDefinitions(defs); + return program; + }; + const htmlTag = /|<\/?(?:b|ul|li)>|/i; + + it('strips HTML formatting tags from every option description', () => { + for (const defs of [definitions, liveQueryDefinitions]) { + loadInto(defs).options.forEach(option => { + expect(option.description).not.toMatch(htmlTag); + }); + } + }); + + it('converts
paragraph breaks to newlines', () => { + const description = loadInto(definitions).options.find(o => o.attributeName() === 'directAccess') + .description; + expect(description).toContain('\n'); + expect(description).not.toContain('
'); + }); + + it('converts links to "text (url)"', () => { + const description = loadInto(definitions).options.find(o => o.attributeName() === 'trustProxy') + .description; + expect(description).toContain( + 'express trust proxy settings (https://expressjs.com/en/guide/behind-proxies.html)' + ); + }); + + it('preserves angle-bracket literals that are not HTML tags', () => { + // `_auth_data_` and `<= \`20\`` are literal content, not markup - a blanket strip would eat them. + const program = loadInto({ + myOption: { + env: 'PARSE_MY_OPTION', + help: 'Field `_auth_data_`.
Valid values are >= `0` and <= `20`.', + }, + }); + const description = program.options.find(o => o.attributeName() === 'myOption').description; + expect(description).toContain('_auth_data_'); + expect(description).toContain('<= `20`'); + expect(description).not.toContain('
'); + }); +}); + describe('LiveQuery definitions', () => { it('should have valid types', () => { for (const key in liveQueryDefinitions) { diff --git a/src/cli/utils/commander.js b/src/cli/utils/commander.js index e2e06e0550..85f4983f10 100644 --- a/src/cli/utils/commander.js +++ b/src/cli/utils/commander.js @@ -7,6 +7,23 @@ let _definitions; let _reverseDefinitions; let _defaults; +// Option help is authored as HTML in the JSDoc comments of `src/Options/index.js` so it renders on the +// docs site. Commander only word-wraps the text, so convert the HTML to plain text for the terminal here. +function cleanHelpText(help) { + if (!help) { + return help; + } + return help + .replace(/]*\bhref="([^"]*)"[^>]*>(.*?)<\/a>/gi, '$2 ($1)') + .replace(/<\/li>/gi, '') + .replace(/
  • /gi, '\n- ') + .replace(//gi, '\n') + .replace(/<\/?(?:ul|b)>/gi, '') + .replace(/[ \t]+\n/g, '\n') + .replace(/\n{3,}/g, '\n\n') + .trim(); +} + Command.prototype.loadDefinitions = function (definitions) { _definitions = definitions; @@ -16,13 +33,13 @@ Command.prototype.loadDefinitions = function (definitions) { if (additionalOptions.required === true) { return program.option( `--${opt} <${opt}>`, - additionalOptions.help, + cleanHelpText(additionalOptions.help), additionalOptions.action ); } else { return program.option( `--${opt} [${opt}]`, - additionalOptions.help, + cleanHelpText(additionalOptions.help), additionalOptions.action ); }