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

fix(add): add handling of merge option #797

Merged
merged 38 commits into from
Jun 6, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
38 commits
Select commit Hold shift + click to select a range
ce51a0a
fix(add): add handling of merge option
rishabh3112 Mar 20, 2019
d901d49
chore(refactor): add generator
rishabh3112 Mar 20, 2019
eb43443
fix(add): add handling of merge option
rishabh3112 Mar 20, 2019
66bde9f
chore(refactor): add generator
rishabh3112 Mar 20, 2019
ff67423
Merge branch 'fix/add' of https://github.com/rishabh3112/webpack-cli …
rishabh3112 Mar 20, 2019
e5c7f67
chore(refactor): update package list
rishabh3112 Mar 21, 2019
2299848
chore(refactor): move schema to utils
rishabh3112 Mar 22, 2019
ccf0dce
fix(add): apply suggestions
rishabh3112 Mar 26, 2019
e839614
chore: merge master
rishabh3112 May 28, 2019
915c4ab
chore(refactor): move questions to utils
rishabh3112 May 28, 2019
5778bdf
chore: lint
rishabh3112 May 28, 2019
0782944
chore: lint
rishabh3112 May 28, 2019
cb5a15f
chore: lint
rishabh3112 May 28, 2019
e023d23
chore: add JSDoc descriptions
rishabh3112 May 28, 2019
248b9cc
feat: add mergeHandler
rishabh3112 May 29, 2019
1323bbf
chore: update variable name
rishabh3112 May 29, 2019
a2c49e2
chore: update types of the config
rishabh3112 May 29, 2019
88eec7c
chore: made condition strict
rishabh3112 May 29, 2019
cf85535
chore: update parseMerge
rishabh3112 May 30, 2019
445ab31
chore: make config const
rishabh3112 May 30, 2019
b6a438d
chore: update parseMerge
rishabh3112 May 31, 2019
b7ef7a4
chore: merge with master
rishabh3112 May 31, 2019
55d237b
chore: update prop name
rishabh3112 May 31, 2019
6a7e662
chore: use replaceWith
rishabh3112 May 31, 2019
a89645a
chore: create isImportPresent
rishabh3112 May 31, 2019
5e23da2
chore: remove trivial type
rishabh3112 May 31, 2019
27c6198
chore: add errors for invalid params
rishabh3112 Jun 4, 2019
8b88980
chore: add types to import functions
rishabh3112 Jun 4, 2019
cf8e3c9
chore: merge master
rishabh3112 Jun 5, 2019
7481974
chore: create questions.ts
rishabh3112 Jun 5, 2019
8609b2b
chore: update error message
rishabh3112 Jun 5, 2019
8e3f4ae
chore: update variable name
rishabh3112 Jun 5, 2019
5ee4169
chore: use import instead of require
rishabh3112 Jun 5, 2019
d72ac08
chore: remove eslint disable comments
rishabh3112 Jun 5, 2019
57b47c3
chore: reorder imports
rishabh3112 Jun 6, 2019
8a66c21
chore: reorder imports
rishabh3112 Jun 6, 2019
0e0ba8a
chore: reorder imports
rishabh3112 Jun 6, 2019
7fe04e9
chore: group imports
rishabh3112 Jun 6, 2019
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
103 changes: 38 additions & 65 deletions packages/generators/add-generator.ts
Original file line number Diff line number Diff line change
@@ -1,57 +1,25 @@
import path, { resolve } from "path";
import glob from "glob-all";
import * as AutoComplete from "inquirer-autocomplete-prompt";
import * as Generator from "yeoman-generator";

import * as glob from "glob-all";
import * as autoComplete from "inquirer-autocomplete-prompt";
import * as path from "path";

import npmExists from "@webpack-cli/utils/npm-exists";
import { getPackageManager } from "@webpack-cli/utils/package-manager";
import PROP_TYPES from "@webpack-cli/utils/prop-types";
import { AutoComplete, Confirm, Input, List } from "@webpack-cli/webpack-scaffold";
import { Input, List } from "@webpack-cli/webpack-scaffold";
import webpackDevServerSchema from "webpack-dev-server/lib/options.json";

import {
actionTypeQuestion,
entryTypeQuestion,
manualOrListInput,
mergeFileQuestion,
topScopeQuestion
} from "./utils/add/questions";
import { traverseAndGetProperties } from "./utils/add";
import { SchemaProperties, WebpackOptions } from "./types";
import { entryQuestions, generatePluginName } from "./utils";
import * as webpackDevServerSchema from "webpack-dev-server/lib/options.json";
import * as webpackSchema from "./utils/optionsSchema.json";

const PROPS: string[] = Array.from(PROP_TYPES.keys());

/**
*
* Checks if the given array has a given property
*
* @param {Array} arr - array to check
* @param {String} prop - property to check existence of
*
* @returns {Boolean} hasProp - Boolean indicating if the property
* is present
*/
const traverseAndGetProperties = (arr: object[], prop: string): boolean => {
let hasProp = false;
arr.forEach(
(p: object): void => {
if (p[prop]) {
hasProp = true;
}
}
);
return hasProp;
};

/**
*
* Search config properties
*
* @param {Object} answers Prompt answers object
* @param {String} input Input search string
*
* @returns {Promise} Returns promise which resolves to filtered props
*
*/
const searchProps = (answers: object, input: string): Promise<string[]> => {
input = input || "";
return Promise.resolve(PROPS.filter((prop: string): boolean => prop.toLowerCase().includes(input.toLowerCase())));
};
import entryQuestions from "./utils/entry";
import { generatePluginName } from "./utils/plugins";
import webpackSchema from "../optionsSchema.json";

/**
*
Expand All @@ -69,6 +37,7 @@ export default class AddGenerator extends Generator {
configName?: string;
topScope?: string[];
item?: string;
merge?: string | string[];
webpackOptions?: WebpackOptions;
};
};
Expand All @@ -83,16 +52,13 @@ export default class AddGenerator extends Generator {
}
};
const { registerPrompt } = this.env.adapter.promptModule;
registerPrompt("autocomplete", autoComplete);
registerPrompt("autocomplete", AutoComplete);
}

public prompting(): Promise<void | {}> {
const done: () => {} = this.async();
let action: string;
const self: this = this;
const manualOrListInput: (promptAction: string) => Generator.Question = (
promptAction: string
): Generator.Question => Input("actionAnswer", `What do you want to add to ${promptAction}?`);
let inputPrompt: Generator.Question;

// first index indicates if it has a deep prop, 2nd indicates what kind of
Expand All @@ -101,13 +67,7 @@ export default class AddGenerator extends Generator {
// eslint-disable-next-line
const isDeepProp: any[] = [false, false];

return this.prompt([
AutoComplete("actionType", "What property do you want to add to?", {
pageSize: 7,
source: searchProps,
suggestOnly: false
})
])
return this.prompt(actionTypeQuestion)
.then(
(actionTypeAnswer: { actionType: string }): void => {
// Set initial prop, like devtool
Expand All @@ -119,9 +79,7 @@ export default class AddGenerator extends Generator {
.then(
(): Promise<void | {}> => {
if (action === "entry") {
return this.prompt([
Confirm("entryType", "Will your application have multiple bundles?", false)
])
return this.prompt(entryTypeQuestion)
.then(
(entryTypeAnswer: { entryType: boolean }): Promise<void | {}> => {
// Ask different questions for entry points
Expand All @@ -136,13 +94,22 @@ export default class AddGenerator extends Generator {
);
} else {
if (action === "topScope") {
return this.prompt([Input("topScope", "What do you want to add to topScope?")]).then(
return this.prompt(topScopeQuestion).then(
(topScopeAnswer: { topScope: string }): void => {
this.configuration.config.topScope.push(topScopeAnswer.topScope);
done();
}
);
}
if (action === "merge") {
return this.prompt(mergeFileQuestion).then(
(mergeFileAnswer: { mergeFile: string; mergeConfigName: string }): void => {
const resolvedPath = resolve(process.cwd(), mergeFileAnswer.mergeFile);
this.configuration.config[action] = [mergeFileAnswer.mergeConfigName, resolvedPath];
done();
}
);
}
}
const temp = action;
if (action === "resolveLoader") {
Expand Down Expand Up @@ -308,7 +275,11 @@ export default class AddGenerator extends Generator {
.pop()
.replace(".js", "")
)
.find((p: string): boolean => p.toLowerCase().indexOf(answeredPluginName) >= 0 || p.indexOf(answeredPluginName) >= 0);
.find(
(p: string): boolean =>
p.toLowerCase().indexOf(answeredPluginName) >= 0 ||
p.indexOf(answeredPluginName) >= 0
);

if (pluginExist) {
this.configuration.config.item = pluginExist;
Expand Down Expand Up @@ -416,7 +387,9 @@ export default class AddGenerator extends Generator {
(action === "devtool" || action === "watch" || action === "mode")
) {
this.configuration.config.item = action;
this.configuration.config.webpackOptions[action] = answerToAction.actionAnswer;
(this.configuration.config.webpackOptions[
action
] as string) = answerToAction.actionAnswer;
done();
return;
}
Expand Down
56 changes: 56 additions & 0 deletions packages/generators/utils/add/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
import PROP_TYPES from "@webpack-cli/utils/prop-types";
evenstensberg marked this conversation as resolved.
Show resolved Hide resolved

export const PROPS: string[] = Array.from(PROP_TYPES.keys());

/**
*
* Replaces the string with a substring at the given index
* https://gist.github.com/efenacigiray/9367920
*
* @param {String} str - string to be modified
* @param {Number} index - index to replace from
* @param {String} replace - string to replace starting from index
*
* @returns {String} string - The newly mutated string
*
*/
export function replaceAt(str: string, index: number, replace: string): string {
return str.substring(0, index) + replace + str.substring(index + 1);
}

/**
*
* Checks if the given array has a given property
*
* @param {Array} arr - array to check
* @param {String} prop - property to check existence of
*
* @returns {Boolean} hasProp - Boolean indicating if the property
* is present
*/
export const traverseAndGetProperties = (arr: object[], prop: string): boolean => {
let hasProp = false;
arr.forEach(
(p: object): void => {
if (p[prop]) {
hasProp = true;
}
}
);
return hasProp;
};

/**
*
* Search config properties
*
* @param {Object} answers Prompt answers object
* @param {String} input Input search string
*
* @returns {Promise} Returns promise which resolves to filtered props
*
*/
export const searchProps = (answers: object, input: string): Promise<string[]> => {
input = input || "";
return Promise.resolve(PROPS.filter((prop: string): boolean => prop.toLowerCase().includes(input.toLowerCase())));
};
47 changes: 47 additions & 0 deletions packages/generators/utils/add/questions.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import { existsSync } from "fs";
evenstensberg marked this conversation as resolved.
Show resolved Hide resolved
import { resolve } from "path";
import { Question } from "inquirer";
import { AutoComplete, Confirm, Input, InputValidate } from "@webpack-cli/webpack-scaffold";

evenstensberg marked this conversation as resolved.
Show resolved Hide resolved
import { searchProps } from "./index";

/**
* Returns Inquirer question for given action
* @param {string} action action for which question has to be prompted
* @returns {Question} Question for given action
*/
export const manualOrListInput = (action: string): Question => {
const actionQuestion = `What do you want to add to ${action}?`;
return Input("actionAnswer", actionQuestion);
};

export const actionTypeQuestion = AutoComplete("actionType", "What property do you want to add to?", {
pageSize: 7,
source: searchProps,
suggestOnly: false
});

export const entryTypeQuestion: Question = Confirm("entryType", "Will your application have multiple bundles?", false);

export const topScopeQuestion: Question = Input("topScope", "What do you want to add to topScope?");

const mergeFileQuestionsFunction = (): Question[] => {
const mergePathQuestion =
"What is the location of webpack configuration with which you want to merge current configuration?";
const mergePathValidator = (path: string): boolean | string => {
const resolvedPath = resolve(process.cwd(), path);
if (existsSync(resolvedPath)) {
if (/\.js$/.test(path)) {
return true;
}
return "Path doesn't correspond to a javascript file";
}
return "Invalid path provided";
};
const mergeConfigNameQuestion = "What is the name by which you want to denote above configuration?";
return [
InputValidate("mergeFile", mergePathQuestion, mergePathValidator),
Input("mergeConfigName", mergeConfigNameQuestion)
];
};
export const mergeFileQuestion: Question[] = mergeFileQuestionsFunction();
10 changes: 5 additions & 5 deletions packages/generators/utils/index.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import entryQuestions from './entry'
import entryQuestions from "./entry";
import langQuestionHandler, { LangType, getBabelLoader, getTypescriptLoader } from "./languageSupport";
import plugins , { replaceAt, generatePluginName } from './plugins'
import styleQuestionHandler, { StylingType, LoaderName, StyleRegex, Loader } from './styleSupport'
import plugins, { replaceAt, generatePluginName } from "./plugins";
import styleQuestionHandler, { StylingType, LoaderName, StyleRegex, Loader } from "./styleSupport";
import tooltip from "./tooltip";
import validate from './validate'
import { getDefaultOptimization } from './webpackConfig';
import validate from "./validate";
import { getDefaultOptimization } from "./webpackConfig";

export {
entryQuestions,
Expand Down
12 changes: 6 additions & 6 deletions packages/serve/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,11 +24,11 @@ import { List } from "@webpack-cli/webpack-scaffold";
*/

interface ConfigType {
installCmd: string,
dependency: string,
devDependency: string,
optionalDependency: string
};
installCmd: string;
dependency: string;
devDependency: string;
optionalDependency: string;
}

const npmConfig: ConfigType = {
installCmd: "install",
Expand All @@ -47,7 +47,7 @@ const yarnConfig: ConfigType = {
const spawnWithArg = (pm: string, cmd: string): SpawnSyncReturns<Buffer> => {
const pmConfig: ConfigType = pm === "npm" ? npmConfig : yarnConfig;
const options: string[] = [pmConfig.installCmd, "webpack-dev-server", pmConfig[cmd]];
return spawn.sync(pm, options, {stdio: "inherit"});
return spawn.sync(pm, options, { stdio: "inherit" });
};

/**
Expand Down
49 changes: 45 additions & 4 deletions packages/utils/ast-utils.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,24 @@
import { JSCodeshift, Node, valueType } from "./types/NodePath";
import * as validateIdentifier from "./validate-identifier";

function isImportPresent(j: JSCodeshift, ast: Node, path: string): boolean {
if (typeof path !== "string") {
throw new Error(`path parameter should be string, recieved ${typeof path}`);
}
let importExists = false;
ast.find(j.CallExpression).forEach(
(callExp: Node): void => {
if (
(callExp.value as Node).callee.name === "require" &&
(callExp.value as Node).arguments[0].value === path
) {
importExists = true;
}
}
);
return importExists;
}

/**
*
* Traverse safely over a path object for array for paths
Expand Down Expand Up @@ -605,8 +623,8 @@ function parseTopScope(j: JSCodeshift, ast: Node, value: string[], action: strin
*/

// eslint-disable-next-line @typescript-eslint/no-unused-vars
function parseMerge(j: JSCodeshift, ast: Node, value: string, action: string): boolean | Node {
function createMergeProperty(p: Node): boolean {
function parseMerge(j: JSCodeshift, ast: Node, value: string[], action: string): boolean | Node {
function createMergeProperty(p: Node, configIdentifier: string): boolean {
// FIXME Use j.callExp()
rishabh3112 marked this conversation as resolved.
Show resolved Hide resolved
const exportsDecl: Node[] = (p.value as Node).body.map(
(n: Node): Node => {
Expand All @@ -626,14 +644,37 @@ function parseMerge(j: JSCodeshift, ast: Node, value: string, action: string): b
type: "MemberExpression"
},
operator: "=",
right: j.callExpression(j.identifier("merge"), [j.identifier(value), exportsDecl.pop()]),
right: j.callExpression(j.identifier("merge"), [j.identifier(configIdentifier), exportsDecl.pop()]),
type: "AssignmentExpression"
};

(p.value as Node).body[bodyLength - 1] = newVal;
return false; // TODO: debug later
}

function addMergeImports(configIdentifier: string, configPath: string): void {
if (typeof configIdentifier !== "string" || typeof configPath !== "string") {
throw new Error(
`Both parameters should be strings. recieved ${typeof configIdentifier}, ${typeof configPath}`
);
}
ast.find(j.Program).forEach(
(p: Node): void => {
if (!isImportPresent(j, ast, "webpack-merge")) {
(p.value as Node).body.splice(-1, 0, `const merge = require('webpack-merge')`);
}

if (!isImportPresent(j, ast, configPath)) {
(p.value as Node).body.splice(-1, 0, `const ${configIdentifier} = require('${configPath}')`);
}
}
);
}

if (value) {
return ast.find(j.Program).filter((p: Node): boolean => createMergeProperty(p));
const [configIdentifier, configPath] = value;
rishabh3112 marked this conversation as resolved.
Show resolved Hide resolved
addMergeImports(configIdentifier, configPath);
return ast.find(j.Program).filter((p: Node): boolean => createMergeProperty(p, configIdentifier));
} else {
return ast;
}
Expand Down
Loading