Skip to content

Commit

Permalink
Merge branch 'next' into hotfix/migrate
Browse files Browse the repository at this point in the history
  • Loading branch information
rishabh3112 committed Apr 25, 2020
2 parents 6bdb1bd + 8892926 commit e29d4db
Show file tree
Hide file tree
Showing 29 changed files with 506 additions and 275 deletions.
2 changes: 1 addition & 1 deletion packages/info/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ const DEFAULT_DETAILS: Information = {

export default async function info(...args): Promise<string[]> {
const cli = new WebpackCLI();
const infoArgs = cli.commandLineArgs(options, { argv: args, stopAtFirstUnknown: false });
const infoArgs = cli.argParser(options, args, true).opts;
const envinfoConfig = {};

if (infoArgs._unknown && infoArgs._unknown.length > 0) {
Expand Down
1 change: 1 addition & 0 deletions packages/info/src/options.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
export default [
{
name: 'output',
usage: '--output <json | markdown>',
type: String,
description: 'To get the output in specified format (accept json or markdown)',
},
Expand Down
33 changes: 20 additions & 13 deletions packages/serve/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { devServer } from 'webpack-dev-server/bin/cli-flags';
import WebpackCLI from 'webpack-cli';
import logger from 'webpack-cli/lib/utils/logger';
import startDevServer from './startDevServer';
import argsToCamelCase from './args-to-camel-case';

/**
*
Expand All @@ -10,23 +10,30 @@ 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(...args: string[]): 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 || {});
const parsedDevServerArgs = cli.argParser(devServer, args, true);
const devServerArgs = parsedDevServerArgs.opts;
const parsedWebpackArgs = cli.argParser(core, parsedDevServerArgs.unknownArgs, true, process.title);
const webpackArgs = parsedWebpackArgs.opts;

// 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;
if (webpackArgs && webpackArgs.hot !== undefined) {
devServerArgs['hot'] = webpackArgs.hot;
}

if (parsedWebpackArgs.unknownArgs.length > 0) {
parsedWebpackArgs.unknownArgs
.filter((e) => e)
.forEach((unknown) => {
logger.warn('Unknown argument:', unknown);
});
return;
}

cli.getCompiler(webpackArgs, core).then((compiler): void => {
startDevServer(compiler, finalArgs);
startDevServer(compiler, devServerArgs);
});
}
201 changes: 201 additions & 0 deletions packages/webpack-cli/__tests__/arg-parser.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,201 @@
const warnMock = jest.fn();
jest.mock('../lib/utils/logger', () => {
return {
warn: warnMock,
};
});
jest.spyOn(process, 'exit').mockImplementation(() => {});

const argParser = require('../lib/utils/arg-parser');
const { core } = require('../lib/utils/cli-flags');

const basicOptions = [
{
name: 'bool-flag',
alias: 'b',
usage: '--bool-flag',
type: Boolean,
description: 'boolean flag',
},
{
name: 'string-flag',
usage: '--string-flag <value>',
type: String,
description: 'string flag',
},
{
name: 'string-flag-with-default',
usage: '--string-flag-with-default <value>',
type: String,
description: 'string flag',
defaultValue: 'default-value',
},
{
name: 'custom-type-flag',
usage: '--custom-type-flag <value>',
type: (val) => {
return val.split(',');
},
description: 'custom type flag',
},
];

const helpAndVersionOptions = basicOptions.slice(0);
helpAndVersionOptions.push(
{
name: 'help',
usage: '--help',
type: Boolean,
description: 'help',
},
{
name: 'version',
alias: 'v',
usage: '--version',
type: Boolean,
description: 'version',
},
);

describe('arg-parser', () => {
beforeEach(() => {
warnMock.mockClear();
});

it('parses no flags', () => {
const res = argParser(basicOptions, [], true);
expect(res.unknownArgs.length).toEqual(0);
expect(res.opts).toEqual({
stringFlagWithDefault: 'default-value',
});
expect(warnMock.mock.calls.length).toEqual(0);
});

it('parses basic flags', () => {
const res = argParser(basicOptions, ['--bool-flag', '--string-flag', 'val'], true);
expect(res.unknownArgs.length).toEqual(0);
expect(res.opts).toEqual({
boolFlag: true,
stringFlag: 'val',
stringFlagWithDefault: 'default-value',
});
expect(warnMock.mock.calls.length).toEqual(0);
});

it('parses negated boolean flags', () => {
const res = argParser(basicOptions, ['--no-bool-flag'], true);
expect(res.unknownArgs.length).toEqual(0);
expect(res.opts).toEqual({
boolFlag: false,
stringFlagWithDefault: 'default-value',
});
expect(warnMock.mock.calls.length).toEqual(0);
});

it('parses boolean flag alias', () => {
const res = argParser(basicOptions, ['-b'], true);
expect(res.unknownArgs.length).toEqual(0);
expect(res.opts).toEqual({
boolFlag: true,
stringFlagWithDefault: 'default-value',
});
expect(warnMock.mock.calls.length).toEqual(0);
});

it('warns on usage of both flag and same negated flag, setting it to false', () => {
const res = argParser(basicOptions, ['--bool-flag', '--no-bool-flag'], true);
expect(res.unknownArgs.length).toEqual(0);
expect(res.opts).toEqual({
boolFlag: false,
stringFlagWithDefault: 'default-value',
});
expect(warnMock.mock.calls.length).toEqual(1);
expect(warnMock.mock.calls[0][0]).toContain('You provided both --bool-flag and --no-bool-flag');
});

it('warns on usage of both flag and same negated flag, setting it to true', () => {
const res = argParser(basicOptions, ['--no-bool-flag', '--bool-flag'], true);
expect(res.unknownArgs.length).toEqual(0);
expect(res.opts).toEqual({
boolFlag: true,
stringFlagWithDefault: 'default-value',
});
expect(warnMock.mock.calls.length).toEqual(1);
expect(warnMock.mock.calls[0][0]).toContain('You provided both --bool-flag and --no-bool-flag');
});

it('warns on usage of both flag alias and same negated flag, setting it to true', () => {
const res = argParser(basicOptions, ['--no-bool-flag', '-b'], true);
expect(res.unknownArgs.length).toEqual(0);
expect(res.opts).toEqual({
boolFlag: true,
stringFlagWithDefault: 'default-value',
});
expect(warnMock.mock.calls.length).toEqual(1);
expect(warnMock.mock.calls[0][0]).toContain('You provided both -b and --no-bool-flag');
});

it('parses string flag using equals sign', () => {
const res = argParser(basicOptions, ['--string-flag=val'], true);
expect(res.unknownArgs.length).toEqual(0);
expect(res.opts).toEqual({
stringFlag: 'val',
stringFlagWithDefault: 'default-value',
});
expect(warnMock.mock.calls.length).toEqual(0);
});

it('handles additional node args from argv', () => {
const res = argParser(basicOptions, ['node', 'index.js', '--bool-flag', '--string-flag', 'val'], false);
expect(res.unknownArgs.length).toEqual(0);
expect(res.opts).toEqual({
boolFlag: true,
stringFlag: 'val',
stringFlagWithDefault: 'default-value',
});
expect(warnMock.mock.calls.length).toEqual(0);
});

it('handles unknown args', () => {
const res = argParser(basicOptions, ['--unknown-arg', '-b', 'no-leading-dashes'], true);
expect(res.unknownArgs).toEqual(['--unknown-arg', 'no-leading-dashes']);
expect(res.opts).toEqual({
boolFlag: true,
stringFlagWithDefault: 'default-value',
});
expect(warnMock.mock.calls.length).toEqual(0);
});

it('handles custom type args', () => {
const res = argParser(basicOptions, ['--custom-type-flag', 'val1,val2,val3'], true);
expect(res.unknownArgs.length).toEqual(0);
expect(res.opts).toEqual({
customTypeFlag: ['val1', 'val2', 'val3'],
stringFlagWithDefault: 'default-value',
});
expect(warnMock.mock.calls.length).toEqual(0);
});

it('calls help callback on --help', () => {
const helpCb = jest.fn();
argParser(helpAndVersionOptions, ['--help'], true, '', helpCb);
expect(helpCb.mock.calls.length).toEqual(1);
expect(helpCb.mock.calls[0][0]).toEqual(['--help']);
});

it('calls version callback on --version', () => {
const versionCb = jest.fn();
argParser(helpAndVersionOptions, ['--version'], true, '', () => {}, versionCb);
expect(versionCb.mock.calls.length).toEqual(1);
});

it('parses webpack args', () => {
const res = argParser(core, ['--entry', 'test.js', '--hot', '-o', './dist/', '--no-watch'], true);
expect(res.unknownArgs.length).toEqual(0);
expect(res.opts.entry).toEqual('test.js');
expect(res.opts.hot).toBeTruthy();
expect(res.opts.output).toEqual('./dist/');
expect(res.opts.watch).toBeFalsy();
expect(warnMock.mock.calls.length).toEqual(0);
});
});
4 changes: 2 additions & 2 deletions packages/webpack-cli/bin/cli.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
'use strict';
require('v8-compile-cache');
const importLocal = require('import-local');
const parseArgs = require('../lib/utils/parse-args');
const parseNodeArgs = require('../lib/utils/parse-node-args');
const runner = require('../lib/runner');

// Prefer the local installation of webpack-cli
Expand All @@ -13,6 +13,6 @@ if (importLocal(__filename)) {
process.title = 'webpack';

const [, , ...rawArgs] = process.argv;
const { cliArgs, nodeArgs } = parseArgs(rawArgs);
const { cliArgs, nodeArgs } = parseNodeArgs(rawArgs);

runner(nodeArgs, cliArgs);
69 changes: 30 additions & 39 deletions packages/webpack-cli/lib/bootstrap.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,12 @@ 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';

const isFlagPresent = (args, flag) => args.find((arg) => [flag, `--${flag}`].includes(arg));
// const isFlagPresent = (args, flag) => args.find((arg) => [flag, `--${flag}`].includes(arg));
const isArgCommandName = (arg, cmd) => arg === cmd.name || arg === cmd.alias;
const removeCmdFromArgs = (args, cmd) => args.filter((arg) => !isArgCommandName(arg, cmd));
const normalizeFlags = (args, cmd) => {
Expand All @@ -20,39 +20,21 @@ const isCommandUsed = (commands) =>
return process.argv.includes(cmd.name) || process.argv.includes(cmd.alias);
});

const resolveNegatedArgs = (args) => {
args._unknown.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);

let argValue = arg.includes('--no') ? 'false' : argPair[1];
if (argValue === 'false') {
argValue = false;
} else if (argValue === 'true') {
argValue = true;
}
const cliFlag = core.find((opt) => opt.name === optName);
if (cliFlag) {
args[cliFlag.group][optName] = argValue;
args._all[optName] = argValue;
args._unknown[idx] = null;
}
}
});
};

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

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

if (parsedArgs.unknownArgs.includes('version')) {
runVersion();
process.exit(0);
}

if (commandIsUsed) {
Expand All @@ -61,18 +43,28 @@ 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
// handle the default webpack entry CLI argument, where instead
// of doing 'webpack-cli --entry ./index.js' you can simply do
// 'webpack-cli ./index.js'
// if the unknown arg starts with a '-', it will be considered
// an unknown flag rather than an entry
let entry;
if (parsedArgs.unknownArgs.length === 1 && !parsedArgs.unknownArgs[0].startsWith('-')) {
entry = parsedArgs.unknownArgs[0];
} else if (parsedArgs.unknownArgs.length > 0) {
parsedArgs.unknownArgs
.filter((e) => e)
.forEach((unknown) => {
logger.warn('Unknown argument:', unknown);
});
cliExecuter();
return;
}
const result = await cli.run(args, core);
const parsedArgsOpts = parsedArgs.opts;
if (entry) {
parsedArgsOpts.entry = entry;
}
const result = await cli.run(parsedArgsOpts, core);
if (!result) {
return;
}
Expand All @@ -99,9 +91,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
Loading

0 comments on commit e29d4db

Please sign in to comment.