Skip to content

Commit 1238524

Browse files
committed
fix: Strip HTML formatting tags from Parse Server CLI --help output
Closes #8434
1 parent 7e9d53a commit 1238524

2 files changed

Lines changed: 68 additions & 2 deletions

File tree

spec/CLI.spec.js

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -191,6 +191,55 @@ describe('definitions', () => {
191191
});
192192
});
193193

194+
describe('CLI help formatting (#8434)', () => {
195+
const loadInto = defs => {
196+
const command = require('../lib/cli/utils/commander').default;
197+
const program = new command.constructor();
198+
program.storeOptionsAsProperties();
199+
program.allowExcessArguments();
200+
program.loadDefinitions(defs);
201+
return program;
202+
};
203+
const htmlTag = /<br\s*\/?>|<\/?(?:b|ul|li)>|<a\b|<\/a>/i;
204+
205+
it('strips HTML formatting tags from every option description', () => {
206+
for (const defs of [definitions, liveQueryDefinitions]) {
207+
loadInto(defs).options.forEach(option => {
208+
expect(option.description).not.toMatch(htmlTag);
209+
});
210+
}
211+
});
212+
213+
it('converts <br> paragraph breaks to newlines', () => {
214+
const description = loadInto(definitions).options.find(o => o.attributeName() === 'directAccess')
215+
.description;
216+
expect(description).toContain('\n');
217+
expect(description).not.toContain('<br>');
218+
});
219+
220+
it('converts <a href> links to "text (url)"', () => {
221+
const description = loadInto(definitions).options.find(o => o.attributeName() === 'trustProxy')
222+
.description;
223+
expect(description).toContain(
224+
'express trust proxy settings (https://expressjs.com/en/guide/behind-proxies.html)'
225+
);
226+
});
227+
228+
it('preserves angle-bracket literals that are not HTML tags', () => {
229+
// `_auth_data_<provider>` and `<= \`20\`` are literal content, not markup - a blanket strip would eat them.
230+
const program = loadInto({
231+
myOption: {
232+
env: 'PARSE_MY_OPTION',
233+
help: 'Field `_auth_data_<provider>`.<br>Valid values are >= `0` and <= `20`.',
234+
},
235+
});
236+
const description = program.options.find(o => o.attributeName() === 'myOption').description;
237+
expect(description).toContain('_auth_data_<provider>');
238+
expect(description).toContain('<= `20`');
239+
expect(description).not.toContain('<br>');
240+
});
241+
});
242+
194243
describe('LiveQuery definitions', () => {
195244
it('should have valid types', () => {
196245
for (const key in liveQueryDefinitions) {

src/cli/utils/commander.js

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,23 @@ let _definitions;
77
let _reverseDefinitions;
88
let _defaults;
99

10+
// Option help is authored as HTML in the JSDoc comments of `src/Options/index.js` so it renders on the
11+
// docs site. Commander only word-wraps the text, so convert the HTML to plain text for the terminal here.
12+
function cleanHelpText(help) {
13+
if (!help) {
14+
return help;
15+
}
16+
return help
17+
.replace(/<a\b[^>]*\bhref="([^"]*)"[^>]*>(.*?)<\/a>/gi, '$2 ($1)')
18+
.replace(/<\/li>/gi, '')
19+
.replace(/<li>/gi, '\n- ')
20+
.replace(/<br\s*\/?>/gi, '\n')
21+
.replace(/<\/?(?:ul|b)>/gi, '')
22+
.replace(/[ \t]+\n/g, '\n')
23+
.replace(/\n{3,}/g, '\n\n')
24+
.trim();
25+
}
26+
1027
Command.prototype.loadDefinitions = function (definitions) {
1128
_definitions = definitions;
1229

@@ -16,13 +33,13 @@ Command.prototype.loadDefinitions = function (definitions) {
1633
if (additionalOptions.required === true) {
1734
return program.option(
1835
`--${opt} <${opt}>`,
19-
additionalOptions.help,
36+
cleanHelpText(additionalOptions.help),
2037
additionalOptions.action
2138
);
2239
} else {
2340
return program.option(
2441
`--${opt} [${opt}]`,
25-
additionalOptions.help,
42+
cleanHelpText(additionalOptions.help),
2643
additionalOptions.action
2744
);
2845
}

0 commit comments

Comments
 (0)