Skip to content

Commit ef59cfc

Browse files
authored
refactor: migrate help group (#1875)
* refactor: migrate help group
1 parent e606d88 commit ef59cfc

File tree

8 files changed

+136
-132
lines changed

8 files changed

+136
-132
lines changed

packages/webpack-cli/__tests__/arg-parser.test.js

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,14 @@
11
const warnMock = jest.fn();
2+
const rawMock = jest.fn();
23
jest.mock('../lib/utils/logger', () => {
34
return {
45
warn: warnMock,
6+
raw: rawMock,
57
};
68
});
9+
10+
const helpMock = jest.fn();
11+
jest.mock('../lib/groups/runHelp', () => helpMock);
712
const processExitSpy = jest.spyOn(process, 'exit').mockImplementation(() => {});
813
const consoleErrorSpy = jest.spyOn(console, 'error').mockImplementation(() => {});
914

@@ -351,10 +356,9 @@ describe('arg-parser', () => {
351356
});
352357

353358
it('calls help callback on --help', () => {
354-
const helpCb = jest.fn();
355-
argParser(helpAndVersionOptions, ['--help'], true, '', helpCb);
356-
expect(helpCb.mock.calls.length).toEqual(1);
357-
expect(helpCb.mock.calls[0][0]).toEqual(['--help']);
359+
argParser(helpAndVersionOptions, ['--help'], true, '');
360+
expect(helpMock.mock.calls.length).toEqual(1);
361+
expect(helpMock.mock.calls[0][0]).toEqual(['--help']);
358362
});
359363

360364
it('parses webpack args', () => {

packages/webpack-cli/lib/bootstrap.js

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ const { options } = require('colorette');
22
const WebpackCLI = require('./webpack-cli');
33
const { core } = require('./utils/cli-flags');
44
const versionRunner = require('./groups/runVersion');
5+
const helpRunner = require('./groups/runHelp');
56
const logger = require('./utils/logger');
67
const { isCommandUsed } = require('./utils/arg-utils');
78
const cliExecuter = require('./utils/cli-executer');
@@ -16,10 +17,10 @@ async function runCLI(cliArgs) {
1617
let args;
1718

1819
const commandIsUsed = isCommandUsed(cliArgs);
19-
const parsedArgs = argParser(core, cliArgs, true, process.title, cli.runHelp);
20+
const parsedArgs = argParser(core, cliArgs, true, process.title);
2021
if (parsedArgs.unknownArgs.includes('help') || parsedArgs.opts.help) {
2122
options.enabled = !cliArgs.includes('--no-color');
22-
cli.runHelp(cliArgs);
23+
helpRunner(cliArgs);
2324
process.exit(0);
2425
}
2526

packages/webpack-cli/lib/groups/HelpGroup.js

Lines changed: 0 additions & 103 deletions
This file was deleted.
Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,116 @@
1+
const { yellow, bold, underline, options } = require('colorette');
2+
const commandLineUsage = require('command-line-usage');
3+
4+
const { core, commands } = require('../utils/cli-flags');
5+
const { hasUnknownArgs, allNames, commands: commandNames } = require('../utils/unknown-args');
6+
const logger = require('../utils/logger');
7+
8+
// This function prints a warning about invalid flag
9+
const printInvalidArgWarning = (args) => {
10+
const invalidArgs = hasUnknownArgs(args, allNames);
11+
if (invalidArgs.length > 0) {
12+
const argType = invalidArgs[0].startsWith('-') ? 'option' : 'command';
13+
logger.warn(`You provided an invalid ${argType} '${invalidArgs[0]}'.`);
14+
}
15+
};
16+
17+
// This function is responsible for printing command/flag scoped help
18+
const printSubHelp = (subject, isCommand) => {
19+
const info = isCommand ? commands : core;
20+
// Contains object with details about given subject
21+
const options = info.find((commandOrFlag) => {
22+
if (isCommand) {
23+
return commandOrFlag.name == subject || commandOrFlag.alias == subject;
24+
}
25+
return commandOrFlag.name === subject.slice(2) || commandOrFlag.alias === subject.slice(1);
26+
});
27+
28+
const header = (head) => bold(underline(head));
29+
const flagAlias = options.alias ? (isCommand ? ` ${options.alias} |` : ` -${options.alias},`) : '';
30+
const usage = yellow(`webpack${flagAlias} ${options.usage}`);
31+
const description = options.description;
32+
const link = options.link;
33+
34+
logger.raw(`${header('Usage')}: ${usage}`);
35+
logger.raw(`${header('Description')}: ${description}`);
36+
37+
if (link) {
38+
logger.raw(`${header('Documentation')}: ${link}`);
39+
}
40+
41+
if (options.flags) {
42+
const flags = commandLineUsage({
43+
header: 'Options',
44+
optionList: options.flags,
45+
});
46+
logger.raw(flags);
47+
}
48+
};
49+
50+
const printHelp = () => {
51+
const o = (s) => yellow(s);
52+
const options = require('../utils/cli-flags');
53+
const negatedFlags = options.core
54+
.filter((flag) => flag.negative)
55+
.reduce((allFlags, flag) => {
56+
return [...allFlags, { name: `no-${flag.name}`, description: `Negates ${flag.name}`, type: Boolean }];
57+
}, []);
58+
const title = bold('⬡ ') + underline('webpack') + bold(' ⬡');
59+
const desc = 'The build tool for modern web applications';
60+
const websitelink = ' ' + underline('https://webpack.js.org');
61+
62+
const usage = bold('Usage') + ': ' + '`' + o('webpack [...options] | <command>') + '`';
63+
const examples = bold('Example') + ': ' + '`' + o('webpack help --flag | <command>') + '`';
64+
65+
const hh = ` ${title}\n
66+
${websitelink}\n
67+
${desc}\n
68+
${usage}\n
69+
${examples}\n
70+
`;
71+
return commandLineUsage([
72+
{
73+
content: hh,
74+
raw: true,
75+
},
76+
{
77+
header: 'Available Commands',
78+
content: options.commands.map((cmd) => {
79+
return { name: `${cmd.name} | ${cmd.alias}`, summary: cmd.description };
80+
}),
81+
},
82+
{
83+
header: 'Options',
84+
optionList: options.core
85+
.map((e) => {
86+
if (e.type.length > 1) e.type = e.type[0];
87+
// Here we replace special characters with chalk's escape
88+
// syntax (`\$&`) to avoid chalk trying to re-process our input.
89+
// This is needed because chalk supports a form of `{var}`
90+
// interpolation.
91+
e.description = e.description.replace(/[{}\\]/g, '\\$&');
92+
return e;
93+
})
94+
.concat(negatedFlags),
95+
},
96+
]);
97+
};
98+
99+
const outputHelp = (cliArgs) => {
100+
options.enabled = !cliArgs.includes('--no-color');
101+
printInvalidArgWarning(cliArgs);
102+
const flagOrCommandUsed = allNames.filter((name) => {
103+
return cliArgs.includes(name);
104+
})[0];
105+
const isCommand = commandNames.includes(flagOrCommandUsed);
106+
107+
// Print full help when no flag or command is supplied with help
108+
if (flagOrCommandUsed) {
109+
printSubHelp(flagOrCommandUsed, isCommand);
110+
} else {
111+
logger.raw(printHelp());
112+
}
113+
logger.raw('\n Made with ♥️ by the webpack team');
114+
};
115+
116+
module.exports = outputHelp;

packages/webpack-cli/lib/groups/runVersion.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ const outputVersion = (args) => {
99

1010
// The command with which version is invoked
1111
const commandUsed = isCommandUsed(args);
12-
const invalidArgs = hasUnknownArgs(args, ...allNames);
12+
const invalidArgs = hasUnknownArgs(args, allNames);
1313
if (commandsUsed && commandsUsed.length === 1 && invalidArgs.length === 0) {
1414
try {
1515
if ([commandUsed.alias, commandUsed.name].some((pkg) => commandsUsed.includes(pkg))) {

packages/webpack-cli/lib/utils/arg-parser.js

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
const commander = require('commander');
22
const logger = require('./logger');
33
const { commands } = require('./cli-flags');
4+
const runHelp = require('../groups/runHelp');
45
const { defaultCommands } = require('./commands');
56

67
/**
@@ -12,7 +13,7 @@ const { defaultCommands } = require('./commands');
1213
* @param {boolean} argsOnly false if all of process.argv has been provided, true if
1314
* args is only a subset of process.argv that removes the first couple elements
1415
*/
15-
function argParser(options, args, argsOnly = false, name = '', helpFunction) {
16+
function argParser(options, args, argsOnly = false, name = '') {
1617
const parser = new commander.Command();
1718
// Set parser name
1819
parser.name(name);
@@ -36,12 +37,10 @@ function argParser(options, args, argsOnly = false, name = '', helpFunction) {
3637
parser.on('command:*', () => {});
3738

3839
// Use customized help output if available
39-
if (helpFunction) {
40-
parser.on('option:help', () => {
41-
helpFunction(args);
42-
process.exit(0);
43-
});
44-
}
40+
parser.on('option:help', () => {
41+
runHelp(args);
42+
process.exit(0);
43+
});
4544

4645
// Allow execution if unknown arguments are present
4746
parser.allowUnknownOption(true);

packages/webpack-cli/lib/utils/unknown-args.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,6 @@ module.exports = {
55
commands: [...commandNames],
66
flags: [...flagNames],
77
allNames: [...commandNames, ...flagNames],
8-
hasUnknownArgs: (args, ...names) =>
8+
hasUnknownArgs: (args, names) =>
99
args.filter((e) => !names.includes(e) && !e.includes('color') && e !== 'version' && e !== '-v' && !e.includes('help')),
1010
};

packages/webpack-cli/lib/webpack-cli.js

Lines changed: 1 addition & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
const webpackMerge = require('webpack-merge');
2-
const { options } = require('colorette');
32
const GroupHelper = require('./utils/GroupHelper');
43
const { Compiler } = require('./utils/Compiler');
54
const { groups, core } = require('./utils/cli-flags');
@@ -88,7 +87,7 @@ class WebpackCLI extends GroupHelper {
8887
for (const [key] of this.groupMap.entries()) {
8988
switch (key) {
9089
case groups.HELP_GROUP: {
91-
const HelpGroup = require('./groups/HelpGroup');
90+
const HelpGroup = require('./groups/runHelp');
9291
this.helpGroup = new HelpGroup();
9392
break;
9493
}
@@ -223,18 +222,6 @@ class WebpackCLI extends GroupHelper {
223222
});
224223
return webpack;
225224
}
226-
227-
runHelp(args) {
228-
const HelpGroup = require('./groups/HelpGroup');
229-
const { commands, allNames, hasUnknownArgs } = require('./utils/unknown-args');
230-
const subject = allNames.filter((name) => {
231-
return args.includes(name);
232-
})[0];
233-
const invalidArgs = hasUnknownArgs(args, ...allNames);
234-
const isCommand = commands.includes(subject);
235-
options.enabled = !args.includes('--no-color');
236-
return new HelpGroup().outputHelp(isCommand, subject, invalidArgs);
237-
}
238225
}
239226

240227
module.exports = WebpackCLI;

0 commit comments

Comments
 (0)