diff --git a/bin/webpack.js b/bin/webpack.js index 67f98e38f14..0c1ccad747a 100755 --- a/bin/webpack.js +++ b/bin/webpack.js @@ -16,11 +16,16 @@ const NON_COMPILATION_ARGS = [ "add", "remove", "update", + "serve", "generate-loader", "generate-plugin" ]; const NON_COMPILATION_CMD = process.argv.find(arg => { + if (arg === "serve") { + global.process.argv = global.process.argv.filter(a => a !== "serve"); + process.argv = global.process.argv; + } return NON_COMPILATION_ARGS.find(a => a === arg); }); diff --git a/lib/commands/serve.js b/lib/commands/serve.js new file mode 100644 index 00000000000..afb7c0b8091 --- /dev/null +++ b/lib/commands/serve.js @@ -0,0 +1,174 @@ +"use strict"; + +const inquirer = require("inquirer"); +const path = require("path"); +const glob = require("glob-all"); +const chalk = require("chalk"); +const spawn = require("cross-spawn"); +const List = require("webpack-addons").List; +const processPromise = require("../utils/resolve-packages").processPromise; + +/** + * + * Installs WDS using NPM with --save --dev etc + * + * @param {Object} cmd - arg to spawn with + * @returns {Void} + */ + +const spawnNPMWithArg = cmd => + spawn.sync("npm", ["install", "webpack-dev-server", cmd], { + stdio: "inherit" + }); + +/** + * + * Installs WDS using Yarn with add etc + * + * @param {Object} cmd - arg to spawn with + * @returns {Void} + */ + +const spawnYarnWithArg = cmd => + spawn.sync("yarn", ["add", "webpack-dev-server", cmd], { + stdio: "inherit" + }); + +/** + * + * Find the path of a given module + * + * @param {Object} dep - dependency to find + * @returns {String} string with given path + */ + +const getRootPathModule = dep => + glob + .sync([ + path.resolve(process.cwd(), dep), + path.join(process.cwd(), "..", dep), + path.join(process.cwd(), "..", "..", dep), + path.join(process.cwd(), "..", "..", "..", dep) + ]) + .pop(); + +/** + * + * Prompts for installing the devServer and running it + * + * @param {Object} args - args processed from the CLI + * @returns {Function} invokes the devServer API + */ +module.exports = function serve() { + let packageJSONPath = getRootPathModule("package.json"); + if (!packageJSONPath) { + console.log( + "\n", + chalk.red("✖ Could not find your package.json file"), + "\n" + ); + process.exit(1); + } + let packageJSON = require(packageJSONPath); + /* + * We gotta do this, cause some configs might not have devdep, + * dep or optional dep, so we'd need sanity checks for each + */ + let hasDevServerDep = packageJSON + ? Object.keys(packageJSON).filter(p => packageJSON[p]["webpack-dev-server"]) + : []; + + if (hasDevServerDep.length) { + let WDSPath = getRootPathModule( + "node_modules/webpack-dev-server/bin/webpack-dev-server.js" + ); + if (!WDSPath) { + console.log( + "\n", + chalk.red( + "✖ Could not find the webpack-dev-server dependency in node_modules root path" + ) + ); + console.log( + chalk.bold.green(" ✔︎"), + "Try this command:", + chalk.bold.green("rm -rf node_modules && npm install") + ); + process.exit(1); + } + return require(WDSPath); + } else { + process.stdout.write( + "\n" + + chalk.bold( + "✖ We didn't find any webpack-dev-server dependency in your project," + ) + + "\n" + + chalk.bold.green(" 'webpack serve'") + + " " + + chalk.bold("requires you to have it installed ") + + "\n\n" + ); + return inquirer + .prompt([ + { + type: "confirm", + name: "confirmDevserver", + message: "Do you want to install it? (default: Y)", + default: "Y" + } + ]) + .then(answer => { + if (answer["confirmDevserver"]) { + return inquirer + .prompt( + List( + "confirmDepType", + "What kind of dependency do you want it to be under? (default: devDependency)", + ["devDependency", "optionalDependency", "dependency"] + ) + ) + .then(depTypeAns => { + const packager = getRootPathModule("package-lock.json") + ? "npm" + : "yarn"; + let spawnAction; + if (depTypeAns["confirmDepType"] === "devDependency") { + if (packager === "yarn") { + spawnAction = () => spawnYarnWithArg("--dev"); + } else { + spawnAction = () => spawnNPMWithArg("--save-dev"); + } + } + if (depTypeAns["confirmDepType"] === "dependency") { + if (packager === "yarn") { + spawnAction = () => spawnYarnWithArg(" "); + } else { + spawnAction = () => spawnNPMWithArg("--save"); + } + } + if (depTypeAns["confirmDepType"] === "optionalDependency") { + if (packager === "yarn") { + spawnAction = () => spawnYarnWithArg("--optional"); + } else { + spawnNPMWithArg("--save-optional"); + } + } + return processPromise(spawnAction()).then(() => { + // Recursion doesn't work well with require call being cached + delete require.cache[require.resolve(packageJSONPath)]; + return serve(); + }); + }); + } else { + console.log(chalk.bold.red("✖ Serve aborted due cancelling")); + process.exitCode = 1; + } + }) + .catch(err => { + console.log(chalk.red("✖ Serve aborted due to some errors")); + console.error(err); + process.exitCode = 1; + }); + } +}; diff --git a/lib/index.js b/lib/index.js index 5923ec0d9be..464fa7527b6 100644 --- a/lib/index.js +++ b/lib/index.js @@ -14,7 +14,7 @@ const path = require("path"); */ module.exports = function initialize(command, args) { - const popArgs = args.slice(2).pop(); + const popArgs = args ? args.slice(2).pop() : null; switch (command) { case "init": { const initPkgs = args.slice(2).length === 1 ? [] : [popArgs]; @@ -41,6 +41,9 @@ module.exports = function initialize(command, args) { case "update": { return require("./commands/update")(); } + case "serve": { + return require("./commands/serve")(); + } case "generate-loader": { return require("./generate-loader/index.js")(); } diff --git a/lib/utils/npm-packages-exists.js b/lib/utils/npm-packages-exists.js index 36e962fc324..65c38e80eaa 100644 --- a/lib/utils/npm-packages-exists.js +++ b/lib/utils/npm-packages-exists.js @@ -1,7 +1,7 @@ "use strict"; const chalk = require("chalk"); const npmExists = require("./npm-exists"); -const resolvePackages = require("./resolve-packages"); +const resolvePackages = require("./resolve-packages").resolvePackages; /** * diff --git a/lib/utils/resolve-packages.js b/lib/utils/resolve-packages.js index 9c9be0fb940..5f4f2308706 100644 --- a/lib/utils/resolve-packages.js +++ b/lib/utils/resolve-packages.js @@ -36,7 +36,7 @@ function processPromise(child) { * a webpack configuration through yeoman or throws an error */ -module.exports = function resolvePackages(pkg) { +function resolvePackages(pkg) { Error.stackTraceLimit = 30; let packageLocations = []; @@ -65,4 +65,9 @@ module.exports = function resolvePackages(pkg) { return creator(packageLocations); }); }); +} + +module.exports = { + resolvePackages, + processPromise };