diff --git a/packages/create-react-app/createReactApp.js b/packages/create-react-app/createReactApp.js index 16acace8fa2..1bf3bb0649d 100755 --- a/packages/create-react-app/createReactApp.js +++ b/packages/create-react-app/createReactApp.js @@ -49,7 +49,7 @@ const url = require('url'); const hyperquest = require('hyperquest'); const envinfo = require('envinfo'); const os = require('os'); - +const findMonorepo = require('react-dev-utils/workspaceUtils').findMonorepo; const packageJson = require('./package.json'); // These files should be allowed to remain on a failed install, @@ -185,7 +185,7 @@ function createApp(name, verbose, version, useNpm, template) { JSON.stringify(packageJson, null, 2) + os.EOL ); - const useYarn = useNpm ? false : shouldUseYarn(); + const useYarn = useNpm ? false : shouldUseYarn(root); const originalDirectory = process.cwd(); process.chdir(root); if (!useYarn && !checkThatNpmCanReadCwd()) { @@ -221,7 +221,7 @@ function createApp(name, verbose, version, useNpm, template) { run(root, appName, version, verbose, originalDirectory, template, useYarn); } -function shouldUseYarn() { +function isYarnAvailable() { try { execSync('yarnpkg --version', { stdio: 'ignore' }); return true; @@ -230,6 +230,11 @@ function shouldUseYarn() { } } +function shouldUseYarn(appDir) { + const mono = findMonorepo(appDir); + return (mono.isYarnWs && mono.isAppIncluded) || isYarnAvailable(); +} + function install(root, useYarn, dependencies, verbose, isOnline) { return new Promise((resolve, reject) => { let command; diff --git a/packages/create-react-app/package.json b/packages/create-react-app/package.json index 1a66c6a7f3c..86bd258f736 100644 --- a/packages/create-react-app/package.json +++ b/packages/create-react-app/package.json @@ -27,6 +27,7 @@ "envinfo": "3.4.2", "fs-extra": "^5.0.0", "hyperquest": "^2.1.2", + "react-dev-utils": "^5.0.0", "semver": "^5.0.3", "tar-pack": "^3.4.0", "tmp": "0.0.33", diff --git a/packages/react-dev-utils/package.json b/packages/react-dev-utils/package.json index 05bddedae05..d66e92c9daf 100644 --- a/packages/react-dev-utils/package.json +++ b/packages/react-dev-utils/package.json @@ -34,7 +34,8 @@ "printHostingInstructions.js", "WatchMissingNodeModulesPlugin.js", "WebpackDevServerUtils.js", - "webpackHotDevClient.js" + "webpackHotDevClient.js", + "workspaceUtils.js" ], "dependencies": { "@babel/code-frame": "7.0.0-beta.38", @@ -45,7 +46,9 @@ "detect-port-alt": "1.1.5", "escape-string-regexp": "1.0.5", "filesize": "3.5.11", + "find-pkg": "1.0.0", "global-modules": "1.0.0", + "globby": "7.1.1", "gzip-size": "4.1.0", "inquirer": "5.0.0", "is-root": "1.0.0", diff --git a/packages/react-dev-utils/workspaceUtils.js b/packages/react-dev-utils/workspaceUtils.js new file mode 100644 index 00000000000..159580e6066 --- /dev/null +++ b/packages/react-dev-utils/workspaceUtils.js @@ -0,0 +1,53 @@ +/** + * Copyright (c) 2018-present, Facebook, Inc. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +'use strict'; +const fs = require('fs'); +const path = require('path'); +const findPkg = require('find-pkg'); +const globby = require('globby'); + +const findPkgs = (rootPath, globPatterns) => { + if (!globPatterns) { + return []; + } + const globOpts = { + cwd: rootPath, + strict: true, + absolute: true, + }; + return globPatterns + .reduce( + (pkgs, pattern) => + pkgs.concat(globby.sync(path.join(pattern, 'package.json'), globOpts)), + [] + ) + .map(f => path.dirname(path.normalize(f))); +}; + +const findMonorepo = appDir => { + const monoPkgPath = findPkg.sync(path.resolve(appDir, '..')); + const monoPkg = monoPkgPath && require(monoPkgPath); + const patterns = monoPkg && monoPkg.workspaces; + const isYarnWs = Boolean(patterns); + const allPkgs = patterns && findPkgs(path.dirname(monoPkgPath), patterns); + const isIncluded = dir => allPkgs && allPkgs.indexOf(dir) !== -1; + const isAppIncluded = isIncluded(appDir); + const pkgs = allPkgs + ? allPkgs.filter(f => fs.realpathSync(f) !== appDir) + : []; + + return { + isAppIncluded, + isYarnWs, + pkgs, + }; +}; + +module.exports = { + findMonorepo, +}; diff --git a/packages/react-scripts/config/paths.js b/packages/react-scripts/config/paths.js index 5d8c7aaa51e..8cd5cf439d7 100644 --- a/packages/react-scripts/config/paths.js +++ b/packages/react-scripts/config/paths.js @@ -11,8 +11,7 @@ const path = require('path'); const fs = require('fs'); const url = require('url'); -const findPkg = require('find-pkg'); -const globby = require('globby'); +const findMonorepo = require('react-dev-utils/workspaceUtils').findMonorepo; // Make sure any symlinks in the project folder are resolved: // https://github.com/facebook/create-react-app/issues/637 @@ -58,7 +57,6 @@ module.exports = { appIndexJs: resolveApp('src/index.js'), appPackageJson: resolveApp('package.json'), appSrc: resolveApp('src'), - yarnLockFile: resolveApp('yarn.lock'), testsSetup: resolveApp('src/setupTests.js'), appNodeModules: resolveApp('node_modules'), publicUrl: getPublicUrl(resolveApp('package.json')), @@ -80,7 +78,6 @@ module.exports = { appIndexJs: resolveApp('src/index.js'), appPackageJson: resolveApp('package.json'), appSrc: resolveApp('src'), - yarnLockFile: resolveApp('yarn.lock'), testsSetup: resolveApp('src/setupTests.js'), appNodeModules: resolveApp('node_modules'), publicUrl: getPublicUrl(resolveApp('package.json')), @@ -106,7 +103,6 @@ if (useTemplate) { appIndexJs: resolveOwn('template/src/index.js'), appPackageJson: resolveOwn('package.json'), appSrc: resolveOwn('template/src'), - yarnLockFile: resolveOwn('template/yarn.lock'), testsSetup: resolveOwn('template/src/setupTests.js'), appNodeModules: resolveOwn('node_modules'), publicUrl: getPublicUrl(resolveOwn('package.json')), @@ -120,40 +116,16 @@ if (useTemplate) { module.exports.srcPaths = [module.exports.appSrc]; -const findPkgs = (rootPath, globPatterns) => { - const globOpts = { - cwd: rootPath, - strict: true, - absolute: true, - }; - return globPatterns - .reduce( - (pkgs, pattern) => - pkgs.concat(globby.sync(path.join(pattern, 'package.json'), globOpts)), - [] - ) - .map(f => path.dirname(path.normalize(f))); -}; - -const getMonorepoPkgPaths = () => { - const monoPkgPath = findPkg.sync(path.resolve(appDirectory, '..')); - if (monoPkgPath) { - // get monorepo config from yarn workspace - const pkgPatterns = require(monoPkgPath).workspaces; - if (pkgPatterns == null) { - return []; - } - const pkgPaths = findPkgs(path.dirname(monoPkgPath), pkgPatterns); - // only include monorepo pkgs if app itself is included in monorepo - if (pkgPaths.indexOf(appDirectory) !== -1) { - return pkgPaths.filter(f => fs.realpathSync(f) !== appDirectory); - } - } - return []; -}; +module.exports.useYarn = fs.existsSync( + path.join(module.exports.appPath, 'yarn.lock') +); if (checkForMonorepo) { // if app is in a monorepo (lerna or yarn workspace), treat other packages in // the monorepo as if they are app source - Array.prototype.push.apply(module.exports.srcPaths, getMonorepoPkgPaths()); + const mono = findMonorepo(appDirectory); + if (mono.isAppIncluded) { + Array.prototype.push.apply(module.exports.srcPaths, mono.pkgs); + } + module.exports.useYarn = module.exports.useYarn || mono.isYarnWs; } diff --git a/packages/react-scripts/package.json b/packages/react-scripts/package.json index 3c7014e51cc..65329b74e2c 100644 --- a/packages/react-scripts/package.json +++ b/packages/react-scripts/package.json @@ -44,9 +44,7 @@ "eslint-plugin-react": "7.5.1", "extract-text-webpack-plugin": "3.0.2", "file-loader": "1.1.6", - "find-pkg": "1.0.0", "fs-extra": "5.0.0", - "globby": "7.1.1", "graphql": "0.12.3", "graphql-tag": "2.6.1", "html-webpack-plugin": "2.30.1", diff --git a/packages/react-scripts/scripts/build.js b/packages/react-scripts/scripts/build.js index cca5ff28313..f69400dcf63 100644 --- a/packages/react-scripts/scripts/build.js +++ b/packages/react-scripts/scripts/build.js @@ -45,7 +45,6 @@ const { printBrowsers } = require('react-dev-utils/browsersHelper'); const measureFileSizesBeforeBuild = FileSizeReporter.measureFileSizesBeforeBuild; const printFileSizesAfterBuild = FileSizeReporter.printFileSizesAfterBuild; -const useYarn = fs.existsSync(paths.yarnLockFile); // These sizes are pretty large. We'll warn for bundles exceeding them. const WARN_AFTER_BUNDLE_GZIP_SIZE = 512 * 1024; @@ -112,7 +111,7 @@ checkBrowsers(paths.appPath) publicUrl, publicPath, buildFolder, - useYarn + paths.useYarn ); printBrowsers(paths.appPath); }, diff --git a/packages/react-scripts/scripts/eject.js b/packages/react-scripts/scripts/eject.js index e8c64a77853..98863b915f2 100644 --- a/packages/react-scripts/scripts/eject.js +++ b/packages/react-scripts/scripts/eject.js @@ -224,7 +224,7 @@ inquirer } } - if (fs.existsSync(paths.yarnLockFile)) { + if (paths.useYarn) { const windowsCmdFilePath = path.join( appPath, 'node_modules', diff --git a/packages/react-scripts/scripts/start.js b/packages/react-scripts/scripts/start.js index 83e4bb630ed..95502bc2a24 100644 --- a/packages/react-scripts/scripts/start.js +++ b/packages/react-scripts/scripts/start.js @@ -29,7 +29,6 @@ if (process.env.SKIP_PREFLIGHT_CHECK !== 'true') { } // @remove-on-eject-end -const fs = require('fs'); const chalk = require('chalk'); const webpack = require('webpack'); const WebpackDevServer = require('webpack-dev-server'); @@ -46,7 +45,6 @@ const paths = require('../config/paths'); const config = require('../config/webpack.config.dev'); const createDevServerConfig = require('../config/webpackDevServer.config'); -const useYarn = fs.existsSync(paths.yarnLockFile); const isInteractive = process.stdout.isTTY; // Warn and crash if required files are missing @@ -69,7 +67,9 @@ if (process.env.HOST) { console.log( `If this was unintentional, check that you haven't mistakenly set it in your shell.` ); - console.log(`Learn more here: ${chalk.yellow('http://bit.ly/CRA-advanced-config')}`); + console.log( + `Learn more here: ${chalk.yellow('http://bit.ly/CRA-advanced-config')}` + ); console.log(); } @@ -91,7 +91,13 @@ checkBrowsers(paths.appPath) const appName = require(paths.appPackageJson).name; const urls = prepareUrls(protocol, HOST, port); // Create a webpack compiler that is configured with custom messages. - const compiler = createCompiler(webpack, config, appName, urls, useYarn); + const compiler = createCompiler( + webpack, + config, + appName, + urls, + paths.useYarn + ); // Load proxy config const proxySetting = require(paths.appPackageJson).proxy; const proxyConfig = prepareProxy(proxySetting, paths.appPublic);