diff --git a/lib/creator/transformations/devServer/devServer.test.js b/lib/creator/transformations/devServer/devServer.test.js index 4eb14cf79e9..14b3b7d11de 100644 --- a/lib/creator/transformations/devServer/devServer.test.js +++ b/lib/creator/transformations/devServer/devServer.test.js @@ -2,7 +2,7 @@ const defineTest = require("../../../transformations/defineTest"); -defineTest(__dirname, "devServer", "devServer-0",{ +defineTest(__dirname, "devServer", "devServer-0", { contentBase: "path.join(__dirname, 'dist')", compress: true, port: 9000 diff --git a/lib/creator/yeoman/webpack-generator.js b/lib/creator/yeoman/webpack-generator.js index f959dcc9ad6..0c3ffc1ddcf 100644 --- a/lib/creator/yeoman/webpack-generator.js +++ b/lib/creator/yeoman/webpack-generator.js @@ -10,6 +10,9 @@ const Input = require("webpack-addons").Input; const Confirm = require("webpack-addons").Confirm; const RawList = require("webpack-addons").RawList; +const getPackageManager = require("../../utils/package-manager") + .getPackageManager; + const entryQuestions = require("./utils/entry"); const getBabelPlugin = require("./utils/module"); const getDefaultPlugins = require("./utils/plugins"); @@ -19,7 +22,7 @@ module.exports = class WebpackGenerator extends Generator { constructor(args, opts) { super(args, opts); this.isProd = false; - this.npmInstalls = ["webpack", "uglifyjs-webpack-plugin"]; + this.dependencies = ["webpack", "uglifyjs-webpack-plugin"]; this.configuration = { config: { webpackOptions: {}, @@ -109,7 +112,7 @@ module.exports = class WebpackGenerator extends Generator { this.configuration.config.webpackOptions.module.rules.push( getBabelPlugin() ); - this.npmInstalls.push( + this.dependencies.push( "babel-loader", "babel-core", "babel-preset-es2015" @@ -130,7 +133,7 @@ module.exports = class WebpackGenerator extends Generator { } switch (stylingAnswer["stylingType"]) { case "SASS": - this.npmInstalls.push( + this.dependencies.push( "sass-loader", "node-sass", "style-loader", @@ -166,7 +169,7 @@ module.exports = class WebpackGenerator extends Generator { break; case "LESS": regExpForStyles = new RegExp(/\.(less|css)$/); - this.npmInstalls.push( + this.dependencies.push( "less-loader", "less", "style-loader", @@ -210,7 +213,7 @@ module.exports = class WebpackGenerator extends Generator { "const precss = require('precss');", "\n" ); - this.npmInstalls.push( + this.dependencies.push( "style-loader", "css-loader", "postcss-loader", @@ -267,7 +270,7 @@ module.exports = class WebpackGenerator extends Generator { } break; case "CSS": - this.npmInstalls.push( + this.dependencies.push( "style-loader", "css-loader" ); @@ -312,7 +315,7 @@ module.exports = class WebpackGenerator extends Generator { this.configuration.config.topScope.push( tooltip.cssPlugin() ); - this.npmInstalls.push( + this.dependencies.push( "extract-text-webpack-plugin" ); if ( @@ -390,7 +393,9 @@ module.exports = class WebpackGenerator extends Generator { }) .then(() => { asyncNamePrompt(); - this.npmInstall(this.npmInstalls, { "save-dev": true }); + this.runInstall(getPackageManager(), this.dependencies, { + "save-dev": true + }); }); } }; diff --git a/lib/utils/package-manager.js b/lib/utils/package-manager.js new file mode 100644 index 00000000000..57e335dfb8b --- /dev/null +++ b/lib/utils/package-manager.js @@ -0,0 +1,61 @@ +"use strict"; + +const path = require("path"); +const fs = require("fs"); +const spawn = require("cross-spawn"); +const globalPath = require("global-modules"); + +const SPAWN_FUNCTIONS = { + npm: spawnNPM, + yarn: spawnYarn +}; + +function spawnNPM(pkg, isNew) { + return spawn.sync("npm", [isNew ? "install" : "update", "-g", pkg], { + stdio: "inherit" + }); +} + +function spawnYarn(pkg, isNew) { + return spawn.sync("yarn", ["global", isNew ? "add" : "upgrade", pkg], { + stdio: "inherit" + }); +} +/* +* @function spawnChild +* +* Spawns a new process that installs the addon/dependency +* +* @param { String } pkg - The dependency to be installed +* @returns { } spawn - Installs the package +*/ + +function spawnChild(pkg) { + const pkgPath = path.resolve(globalPath, pkg); + const packageManager = getPackageManager(); + const isNew = !fs.existsSync(pkgPath); + + return SPAWN_FUNCTIONS[packageManager](pkg, isNew); +} + +/* +* @function getPackageManager +* +* Returns the name of package manager to use, +* preferring yarn over npm if available +* +* @returns { String } - The package manager name +*/ + +function getPackageManager() { + if (spawn.sync("yarn", [" --version"], { stdio: "ignore" }).error) { + return "npm"; + } + + return "yarn"; +} + +module.exports = { + getPackageManager, + spawnChild +}; diff --git a/lib/utils/package-manager.spec.js b/lib/utils/package-manager.spec.js new file mode 100644 index 00000000000..e45b920fb4a --- /dev/null +++ b/lib/utils/package-manager.spec.js @@ -0,0 +1,90 @@ +"use strict"; + +jest.mock("cross-spawn"); +jest.mock("fs"); + +describe("package-manager", () => { + const packageManager = require("./package-manager"); + const spawn = require("cross-spawn"); + const fs = require("fs"); + + const defaultSyncResult = { + pid: 1234, + output: [null, null, null], + stdout: null, + stderr: null, + signal: null, + status: 1, + error: null + }; + + function mockSpawnErrorOnce() { + spawn.sync.mockReturnValueOnce( + Object.assign({}, defaultSyncResult, { + status: null, + error: new Error() + }) + ); + } + + spawn.sync.mockReturnValue(defaultSyncResult); + + it("should return 'yarn' from getPackageManager if it's installed", () => { + expect(packageManager.getPackageManager()).toEqual("yarn"); + }); + + it("should return 'npm' from getPackageManager if yarn is not installed", () => { + mockSpawnErrorOnce(); + expect(packageManager.getPackageManager()).toEqual("npm"); + }); + + it("should spawn yarn add from spawnChild", () => { + const packageName = "some-pkg"; + + packageManager.spawnChild(packageName); + expect(spawn.sync).toHaveBeenLastCalledWith( + "yarn", + ["global", "add", packageName], + { stdio: "inherit" } + ); + }); + + it("should spawn yarn upgrade from spawnChild", () => { + const packageName = "some-pkg"; + + fs.existsSync.mockReturnValueOnce(true); + + packageManager.spawnChild(packageName); + expect(spawn.sync).toHaveBeenLastCalledWith( + "yarn", + ["global", "upgrade", packageName], + { stdio: "inherit" } + ); + }); + + it("should spawn npm install from spawnChild", () => { + const packageName = "some-pkg"; + + mockSpawnErrorOnce(); + packageManager.spawnChild(packageName); + expect(spawn.sync).toHaveBeenLastCalledWith( + "npm", + ["install", "-g", packageName], + { stdio: "inherit" } + ); + }); + + it("should spawn npm update from spawnChild", () => { + const packageName = "some-pkg"; + + mockSpawnErrorOnce(); + fs.existsSync.mockReturnValueOnce(true); + + packageManager.spawnChild(packageName); + expect(spawn.sync).toHaveBeenLastCalledWith( + "npm", + ["update", "-g", packageName], + { stdio: "inherit" } + ); + }); +}); diff --git a/lib/utils/resolve-packages.js b/lib/utils/resolve-packages.js index 98f8457722d..00f88bf35ed 100644 --- a/lib/utils/resolve-packages.js +++ b/lib/utils/resolve-packages.js @@ -1,11 +1,13 @@ "use strict"; const path = require("path"); -const fs = require("fs"); const chalk = require("chalk"); -const spawn = require("cross-spawn"); -const creator = require("../creator/index").creator; const globalPath = require("global-modules"); + +const creator = require("../creator/index").creator; + +const spawnChild = require("./package-manager").spawnChild; + /* * @function processPromise * @@ -26,28 +28,6 @@ function processPromise(child) { }); } -/* -* @function spawnChild -* -* Spawns a new process that installs the addon/dependency -* -* @param { String } pkg - The dependency to be installed -* @returns { } spawn - Installs the package -*/ - -function spawnChild(pkg) { - const pkgPath = path.resolve(globalPath, pkg); - let installType; - if (fs.existsSync(pkgPath)) { - installType = "update"; - } else { - installType = "install"; - } - return spawn.sync("npm", [installType, "-g", pkg], { - stdio: "inherit" - }); -} - /* * @function resolvePackages *