Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[WIP] chore: migrate to commander #1347

Closed
wants to merge 15 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
40 changes: 25 additions & 15 deletions packages/serve/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { devServer } from "webpack-dev-server/bin/cli-flags";
import WebpackCLI from "webpack-cli";
import WebpackCLI from "../../webpack-cli";
import startDevServer from "./startDevServer";
import argsToCamelCase from "./args-to-camel-case";

Expand All @@ -10,23 +10,33 @@ import argsToCamelCase from "./args-to-camel-case";
* @param {String[]} args - args processed from the CLI
* @returns {Function} invokes the devServer API
*/
export default function serve(...args): void {
export default function serve(): void {
const cli = new WebpackCLI();
const core = cli.getCoreFlags();
// partial parsing usage: https://github.com/75lb/command-line-args/wiki/Partial-parsing

// since the webpack flags have the 'entry' option set as it's default option,
// we need to parse the dev server args first. Otherwise, the webpack parsing could snatch
// one of the dev server's options and set it to this 'entry' option.
// see: https://github.com/75lb/command-line-args/blob/master/doc/option-definition.md#optiondefaultoption--boolean
const devServerArgs = cli.commandLineArgs(devServer, { argv: args, partial: true });
const webpackArgs = cli.commandLineArgs(core, { argv: devServerArgs._unknown || [], stopAtFirstUnknown: false });
const finalArgs = argsToCamelCase(devServerArgs._all || {});
// pass along the 'hot' argument to the dev server if it exists
if (webpackArgs && webpackArgs._all && typeof webpackArgs._all.hot !== 'undefined') {
finalArgs['hot'] = webpackArgs._all.hot;
}
cli.getCompiler(webpackArgs, core).then((compiler): void => {
const filteredArgs = process.argv.filter(arg => (arg != "serve"));
const devServerArgs = cli.argParser(devServer, filteredArgs);
const webpackArgs = cli.argParser(core, filteredArgs, process.title, cli.runHelp, cli.runVersion);
const finalArgs = argsToCamelCase(devServerArgs.opts() || {});


// pass along the 'hot' argument to the dev server if it exists
if (webpackArgs && webpackArgs.opts() && webpackArgs.opts().hot !== undefined) {
finalArgs['hot'] = webpackArgs.opts().hot;
}

Object.keys(finalArgs).forEach(arg => {
if (finalArgs[arg] === undefined) {
delete finalArgs[arg];
}
});

if (webpackArgs.args.length > 0) {
process.stderr.write(`Unknown option: ${webpackArgs.args}`);
return;
}

cli.getCompiler(webpackArgs.opts(), core).then((compiler): void => {
startDevServer(compiler, finalArgs);
});
}
34 changes: 16 additions & 18 deletions packages/webpack-cli/lib/bootstrap.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ const WebpackCLI = require('./webpack-cli');
const { core, commands } = require('./utils/cli-flags');
const logger = require('./utils/logger');
const cliExecuter = require('./utils/cli-executer');

const argParser = require('./utils/arg-parser');
require('./utils/process-log');

process.title = 'webpack-cli';
Expand All @@ -21,7 +21,7 @@ const isCommandUsed = commands =>
});

const resolveNegatedArgs = args => {
args._unknown.forEach((arg, idx) => {
args.forEach((arg, idx) => {
if (arg.includes('--') || arg.includes('--no')) {
const argPair = arg.split('=');
const optName = arg.includes('--no') ? argPair[0].slice(5) : argPair[0].slice(2);
Expand All @@ -36,23 +36,23 @@ const resolveNegatedArgs = args => {
if (cliFlag) {
args[cliFlag.group][optName] = argValue;
args._all[optName] = argValue;
args._unknown[idx] = null;
args.args[idx] = null;
}
}
});
};

async function runCLI(cli, commandIsUsed) {
let args;
const helpFlagExists = isFlagPresent(process.argv, 'help');
const versionFlagExists = isFlagPresent(process.argv, 'version');
const parsedArgs = argParser(core, process.argv, process.title, cli.runHelp, cli.runVersion);

if (helpFlagExists) {
if (parsedArgs.args.includes('help')) {
cli.runHelp(process.argv);
return;
} else if (versionFlagExists) {
process.exit(0);
}

if (parsedArgs.args.includes('version')) {
cli.runVersion();
return;
process.exit(0);
}

if (commandIsUsed) {
Expand All @@ -61,18 +61,17 @@ async function runCLI(cli, commandIsUsed) {
return await cli.runCommand(commandIsUsed, ...args);
} else {
try {
args = cli.commandLineArgs(core, { stopAtFirstUnknown: false, partial: true });
if (args._unknown) {
resolveNegatedArgs(args);
args._unknown
if (parsedArgs.args.length > 0) {
resolveNegatedArgs(parsedArgs.args);
parsedArgs.args
.filter(e => e)
.forEach(unknown => {
logger.warn('Unknown argument:', unknown);
});
cliExecuter();
return;
}
const result = await cli.run(args, core);
const result = await cli.run(parsedArgs.opts(), core);
if (!result) {
return;
}
Expand All @@ -99,9 +98,8 @@ async function runCLI(cli, commandIsUsed) {
const newArgKeys = Object.keys(argsMap).filter(arg => !keysToDelete.includes(argsMap[arg].pos));
// eslint-disable-next-line require-atomic-updates
process.argv = newArgKeys;
args = cli.commandLineArgs(core, { stopAtFirstUnknown: false, partial: true });

await cli.run(args, core);
args = argParser("", core, process.argv);
await cli.run(args.opts(), core);
process.stdout.write('\n');
logger.warn('Duplicate flags found, defaulting to last set value');
} else {
Expand Down
53 changes: 0 additions & 53 deletions packages/webpack-cli/lib/groups/AdvancedGroup.js
Original file line number Diff line number Diff line change
Expand Up @@ -77,59 +77,6 @@ class AdvancedGroup extends GroupHelper {
if (args.target) {
options.target = args.target;
}

if (args.global) {
const globalArrLen = args.global.length;
if (!globalArrLen) {
logger.warn('Argument to global flag is none');
return;
}
if (globalArrLen === 1) {
logger.warn('Argument to global flag expected a key/value pair');
return;
}

const providePluginObject = {};
args.global.forEach((arg, idx) => {
const isKey = idx % 2 === 0;
const isConcatArg = arg.includes('=');
if (isKey && isConcatArg) {
const splitIdx = arg.indexOf('=');
const argVal = arg.substr(splitIdx + 1);
const argKey = arg.substr(0, splitIdx);
if (!argVal.length) {
logger.warn(`Found unmatching value for global flag key '${argKey}'`);
return;
}
// eslint-disable-next-line no-prototype-builtins
if (providePluginObject.hasOwnProperty(argKey)) {
logger.warn(`Overriding key '${argKey}' for global flag`);
}
providePluginObject[argKey] = argVal;
return;
}
if (isKey) {
const nextArg = args.global[idx + 1];
// eslint-disable-next-line no-prototype-builtins
if (providePluginObject.hasOwnProperty(arg)) {
logger.warn(`Overriding key '${arg}' for global flag`);
}
if (!nextArg) {
logger.warn(`Found unmatching value for global flag key '${arg}'`);
return;
}
providePluginObject[arg] = nextArg;
}
});

const { ProvidePlugin } = require('webpack');
const globalVal = new ProvidePlugin(providePluginObject);
if (options && options.plugins) {
options.plugins.unshift(globalVal);
} else {
options.plugins = [globalVal];
}
}
}
run() {
this.resolveOptions();
Expand Down
5 changes: 4 additions & 1 deletion packages/webpack-cli/lib/groups/OutputGroup.js
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,10 @@ class OutputGroup extends GroupHelper {
const { args } = this;
if (args) {
const { output } = args;
if (!output) {
// TODO: Remove comment before merge
// We need to show warning when empty output flag is supplied
// which is set to boolean true by commander
if (!output || output === true) {
return;
}
const outputInfo = path.parse(output);
Expand Down
3 changes: 3 additions & 0 deletions packages/webpack-cli/lib/groups/StatsGroup.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,9 @@ class StatsGroup extends GroupHelper {
this.opts.option.stats = {
verbose: true,
};
} else if (!StatsGroup.validOptions().includes(this.args.stats)) {
logger.warn(`'${this.args.stats}' is invalid value for stats. Using 'normal' option for stats`);
this.opts.options.stats = 'normal';
} else {
this.opts.options.stats = this.args.stats;
}
Expand Down
12 changes: 6 additions & 6 deletions packages/webpack-cli/lib/groups/ZeroConfigGroup.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ class ZeroConfigGroup extends GroupHelper {
if (process.env.NODE_ENV && (process.env.NODE_ENV === PRODUCTION || process.env.NODE_ENV === DEVELOPMENT)) {
return process.env.NODE_ENV;
} else {
if ((this.args.mode || this.args.noMode) && (this.args.dev || this.args.prod)) {
if ((this.args.mode !== undefined ) && (this.args.dev || this.args.prod)) {
logger.warn(
`You provided both ${this.args.mode ? 'mode' : 'no-mode'} and ${
this.args.prod ? '--prod' : '--dev'
Expand All @@ -32,12 +32,12 @@ class ZeroConfigGroup extends GroupHelper {
return NONE ;
}
}
if (this.args.noMode && this.args.mode) {
logger.warn(
'You Provided both mode and no-mode arguments. You Should Provide just one. "mode" will be used.'
)
}

if (this.args.mode) {
if (this.args.mode !== PRODUCTION && this.args.mode !== DEVELOPMENT && this.args.mode !== NONE) {
logger.warn('You provided an invalid value for "mode" option.');
return PRODUCTION;
}
return this.args.mode;
}
if (this.args.prod) {
Expand Down
45 changes: 45 additions & 0 deletions packages/webpack-cli/lib/utils/arg-parser.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
const commander = require("commander");

/**
* Creates Argument parser corresponding to the supplied options
* parse the args and return the result
*
* @param {object[]} options Array of objects with details about flags
* @param {string[]} args process.argv or it's subset
*/
function argParser(options, args, name = "", helpFunction = undefined, versionFunction = undefined) {
const parser = new commander.Command();
// Set parser name
parser.name(name);

// Use customized version output if available
if (versionFunction) {
parser.on('option:version', () => {
versionFunction(args);
process.exit(0);
});
}

// Use customised help output is avaliable
if (helpFunction) {
parser.on('option:help', () => {
helpFunction(args);
process.exit(0);
});
}

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

// Register options on the parser
options.reduce((parserInstance, option) => {
const flags = option.alias ? `-${option.alias}, --${option.name}` : `--${option.name}`;
const flagsWithType = option.type !== Boolean ? flags + ' [type]' : flags;
parserInstance.option(flagsWithType, option.description, option.defaultValue);
return parserInstance;
}, parser);

return parser.parse(args);
}

module.exports = argParser;
18 changes: 1 addition & 17 deletions packages/webpack-cli/lib/utils/cli-flags.js
Original file line number Diff line number Diff line change
Expand Up @@ -197,15 +197,6 @@ module.exports = {
description: 'Load a given plugin',
link: 'https://webpack.js.org/plugins/',
},
{
name: 'global',
usage: '--global myVar ./global.js',
alias: 'g',
type: String,
multiple: true,
group: ADVANCED_GROUP,
description: 'Declares and exposes a global variable',
},
{
name: 'target',
alias: 't',
Expand Down Expand Up @@ -281,14 +272,7 @@ module.exports = {
{
name: 'mode',
usage: '--mode <development | production>',
type: (value) => {
if (value === 'development' || value === 'production' || value === 'none') {
return value ;
} else {
logger.warn('You provided an invalid value for "mode" option.');
return 'production' ;
}
},
type: String,
group: ZERO_CONFIG_GROUP,
description: 'Defines the mode to pass to webpack',
link: 'https://webpack.js.org/concepts/#mode'
Expand Down
10 changes: 10 additions & 0 deletions packages/webpack-cli/lib/utils/helpers.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
/**
* Convert camelCase to kebab-case
* @param {string} str input string in camelCase
* @returns {string} output string in kebab-case
*/
function toKebabCase(str) {
return str.replace(/([a-z0-9])([A-Z])/g, '$1-$2').toLowerCase()
}

module.exports = { toKebabCase };
19 changes: 12 additions & 7 deletions packages/webpack-cli/lib/webpack-cli.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,8 @@ const GroupHelper = require('./utils/GroupHelper');
const { Compiler } = require('./utils/Compiler');
const { groups, core } = require('./utils/cli-flags');
const webpackMerge = require('webpack-merge');
const commandArgs = require('command-line-args');
const { toKebabCase } = require('./utils/helpers');
const argParser = require('./utils/arg-parser');

const defaultCommands = {
init: 'init',
Expand Down Expand Up @@ -36,12 +37,12 @@ class WebpackCLI extends GroupHelper {
this.outputConfiguration = {};
}
setMappedGroups(args, inlineOptions) {
const { _all } = args;
Object.keys(_all).forEach(key => {
this.setGroupMap(key, _all[key], inlineOptions);
Object.keys(args).forEach(key => {
this.setGroupMap(toKebabCase(key), args[key], inlineOptions);
});
}
setGroupMap(key, val, inlineOptions) {
if (val === undefined) return;
const opt = inlineOptions.find(opt => opt.name === key);
const groupName = opt.group;
if (this.groupMap.has(groupName)) {
Expand Down Expand Up @@ -82,15 +83,19 @@ class WebpackCLI extends GroupHelper {
}

/**
* It exposes "command-line-args" function
* Expose commander argParser
* @param {...any} args args for argParser
*/
commandLineArgs(...args) {
return commandArgs(...args);
argParser(...args) {
return argParser(...args);
}


getCoreFlags() {
return core;
}


/**
* Based on the parsed keys, the function will import and create
* a group that handles respective values
Expand Down
2 changes: 1 addition & 1 deletion packages/webpack-cli/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,8 @@
"@webpack-cli/package-utils": "^1.0.1-alpha.4",
"ansi-escapes": "^4.2.1",
"chalk": "^3.0.0",
"command-line-args": "^5.1.1",
"command-line-usage": "^6.1.0",
"commander": "^5.0.0",
"enquirer": "^2.3.4",
"execa": "^3.2.0",
"import-local": "^3.0.2",
Expand Down
Loading