From a82896c3e39ada878f1cd41893e8304ad05b34d5 Mon Sep 17 00:00:00 2001
From: Jirat Ki <saakyz@gmail.com>
Date: Thu, 23 Feb 2017 10:55:41 +0700
Subject: [PATCH] Install react, react-dom, and react-scripts at the same time
 (#1253)
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

* Install react and react-dom along with react-scripts

- Install react, react-dom and react-script in a same time
- Move react-scripts to devDependencies.

* Check if react, react-dom has been already installed

- To backward compatibility with old CRA’s cli
- In case old CRA doesn’t install react, react-don along with
react-scripts

* Use packageName to find script dependency

- use packageName to find dependency
- fix pathExists.sync

* Check dependencies.react in package.json instead of actual files

* Process exit when dependencies not found

- Show error and exit when dependencies not found.
- Log install show custom package name

* Remove template string

* Install dependencies if template is preseted

* Remove dangling comma

Resolves #1239
---
 packages/create-react-app/index.js     | 53 +++++++++++---
 packages/react-scripts/scripts/init.js | 98 ++++++++++++++------------
 2 files changed, 97 insertions(+), 54 deletions(-)

diff --git a/packages/create-react-app/index.js b/packages/create-react-app/index.js
index 14fce0a95..d8d28496c 100755
--- a/packages/create-react-app/index.js
+++ b/packages/create-react-app/index.js
@@ -40,7 +40,7 @@
 
 var chalk = require('chalk');
 
-var currentNodeVersion = process.versions.node
+var currentNodeVersion = process.versions.node;
 if (currentNodeVersion.split('.')[0] < 4) {
   console.error(
     chalk.red(
@@ -124,7 +124,7 @@ function createApp(name, verbose, version, template) {
   var packageJson = {
     name: appName,
     version: '0.1.0',
-    private: true,
+    private: true
   };
   fs.writeFileSync(
     path.join(root, 'package.json'),
@@ -133,10 +133,6 @@ function createApp(name, verbose, version, template) {
   var originalDirectory = process.cwd();
   process.chdir(root);
 
-  console.log('Installing packages. This might take a couple minutes.');
-  console.log('Installing ' + chalk.cyan('react-scripts') + '...');
-  console.log();
-
   run(root, appName, version, verbose, originalDirectory, template);
 }
 
@@ -149,15 +145,15 @@ function shouldUseYarn() {
   }
 }
 
-function install(packageToInstall, verbose, callback) {
+function install(dependencies, verbose, callback) {
   var command;
   var args;
   if (shouldUseYarn()) {
     command = 'yarnpkg';
-    args = [ 'add', '--dev', '--exact', packageToInstall];
+    args = [ 'add', '--exact'].concat(dependencies);
   } else {
     command = 'npm';
-    args = ['install', '--save-dev', '--save-exact', packageToInstall];
+    args = ['install', '--save', '--save-exact'].concat(dependencies);
   }
 
   if (verbose) {
@@ -174,7 +170,13 @@ function run(root, appName, version, verbose, originalDirectory, template) {
   var packageToInstall = getInstallPackage(version);
   var packageName = getPackageName(packageToInstall);
 
-  install(packageToInstall, verbose, function(code, command, args) {
+  var allDependencies = ['react', 'react-dom', packageToInstall];
+
+  console.log('Installing packages. This might take a couple minutes.');
+  console.log('Installing ' + chalk.cyan('react, react-dom, ' + packageName) + '...');
+  console.log();
+
+  install(allDependencies, verbose, function(code, command, args) {
     if (code !== 0) {
       console.error(chalk.cyan(command + ' ' + args.join(' ')) + ' failed');
       process.exit(1);
@@ -182,6 +184,10 @@ function run(root, appName, version, verbose, originalDirectory, template) {
 
     checkNodeVersion(packageName);
 
+    // Since react-scripts has been installed with --save
+    // We need to move it into devDependencies and rewrite package.json
+    moveReactScriptsToDev(packageName);
+
     var scriptsPath = path.resolve(
       process.cwd(),
       'node_modules',
@@ -273,6 +279,33 @@ function checkAppName(appName) {
   }
 }
 
+function moveReactScriptsToDev(packageName) {
+  var packagePath = path.join(process.cwd(), 'package.json');
+  var packageJson = require(packagePath);
+
+  if (typeof packageJson.dependencies === 'undefined') {
+    console.error(
+      chalk.red('Missing dependencies in package.json')
+    );
+    process.exit(1);
+  }
+
+  var packageVersion = packageJson.dependencies[packageName];
+
+  if (typeof packageVersion === 'undefined') {
+    console.error(
+      chalk.red('Unable to find ' + packageName + ' in package.json')
+    );
+    process.exit(1);
+  }
+
+  packageJson.devDependencies = packageJson.devDependencies || {};
+  packageJson.devDependencies[packageName] = packageVersion;
+  delete packageJson.dependencies[packageName];
+
+  fs.writeFileSync(packagePath, JSON.stringify(packageJson, null, 2));
+}
+
 // If project only contains files generated by GH, it’s safe.
 // We also special case IJ-based products .idea because it integrates with CRA:
 // https://github.com/facebookincubator/create-react-app/pull/368#issuecomment-243446094
diff --git a/packages/react-scripts/scripts/init.js b/packages/react-scripts/scripts/init.js
index 864005eca..aa62265cc 100644
--- a/packages/react-scripts/scripts/init.js
+++ b/packages/react-scripts/scripts/init.js
@@ -64,8 +64,6 @@ module.exports = function(appPath, appName, verbose, originalDirectory, template
     }
   });
 
-  // Run yarn or npm for react and react-dom
-  // TODO: having to do two npm/yarn installs is bad, can we avoid it?
   var command;
   var args;
 
@@ -92,53 +90,65 @@ module.exports = function(appPath, appName, verbose, originalDirectory, template
     fs.unlinkSync(templateDependenciesPath);
   }
 
-  console.log('Installing react and react-dom using ' + command + '...');
-  console.log();
+  // Install react and react-dom for backward compatibility with old CRA cli
+  // which doesn't install react and react-dom along with react-scripts
+  // or template is presetend (via --internal-testing-template)
+  if (!isReactInstalled(appPackage) || template) {
+    console.log('Installing react and react-dom using ' + command + '...');
+    console.log();
 
-  var proc = spawn(command, args, {stdio: 'inherit'});
-  proc.on('close', function (code) {
-    if (code !== 0) {
+    var proc = spawn.sync(command, args, {stdio: 'inherit'});
+    if (proc.status !== 0) {
       console.error('`' + command + ' ' + args.join(' ') + '` failed');
       return;
     }
+  }
 
-    // Display the most elegant way to cd.
-    // This needs to handle an undefined originalDirectory for
-    // backward compatibility with old global-cli's.
-    var cdpath;
-    if (originalDirectory &&
-        path.join(originalDirectory, appName) === appPath) {
-      cdpath = appName;
-    } else {
-      cdpath = appPath;
-    }
+  // Display the most elegant way to cd.
+  // This needs to handle an undefined originalDirectory for
+  // backward compatibility with old global-cli's.
+  var cdpath;
+  if (originalDirectory &&
+      path.join(originalDirectory, appName) === appPath) {
+    cdpath = appName;
+  } else {
+    cdpath = appPath;
+  }
 
+  console.log();
+  console.log('Success! Created ' + appName + ' at ' + appPath);
+  console.log('Inside that directory, you can run several commands:');
+  console.log();
+  console.log(chalk.cyan('  ' + command + ' start'));
+  console.log('    Starts the development server.');
+  console.log();
+  console.log(chalk.cyan('  ' + command + ' run build'));
+  console.log('    Bundles the app into static files for production.');
+  console.log();
+  console.log(chalk.cyan('  ' + command + ' test'));
+  console.log('    Starts the test runner.');
+  console.log();
+  console.log(chalk.cyan('  ' + command + ' run eject'));
+  console.log('    Removes this tool and copies build dependencies, configuration files');
+  console.log('    and scripts into the app directory. If you do this, you can’t go back!');
+  console.log();
+  console.log('We suggest that you begin by typing:');
+  console.log();
+  console.log(chalk.cyan('  cd'), cdpath);
+  console.log('  ' + chalk.cyan(command + ' start'));
+  if (readmeExists) {
     console.log();
-    console.log('Success! Created ' + appName + ' at ' + appPath);
-    console.log('Inside that directory, you can run several commands:');
-    console.log();
-    console.log(chalk.cyan('  ' + command + ' start'));
-    console.log('    Starts the development server.');
-    console.log();
-    console.log(chalk.cyan('  ' + command + ' run build'));
-    console.log('    Bundles the app into static files for production.');
-    console.log();
-    console.log(chalk.cyan('  ' + command + ' test'));
-    console.log('    Starts the test runner.');
-    console.log();
-    console.log(chalk.cyan('  ' + command + ' run eject'));
-    console.log('    Removes this tool and copies build dependencies, configuration files');
-    console.log('    and scripts into the app directory. If you do this, you can’t go back!');
-    console.log();
-    console.log('We suggest that you begin by typing:');
-    console.log();
-    console.log(chalk.cyan('  cd'), cdpath);
-    console.log('  ' + chalk.cyan(command + ' start'));
-    if (readmeExists) {
-      console.log();
-      console.log(chalk.yellow('You had a `README.md` file, we renamed it to `README.old.md`'));
-    }
-    console.log();
-    console.log('Happy hacking!');
-  });
+    console.log(chalk.yellow('You had a `README.md` file, we renamed it to `README.old.md`'));
+  }
+  console.log();
+  console.log('Happy hacking!');
 };
+
+function isReactInstalled(appPackage) {
+  var dependencies = appPackage.dependencies || {};
+
+  return (
+    typeof dependencies.react !== 'undefined' &&
+    typeof dependencies['react-dom'] !== 'undefined'
+  )
+}
-- 
GitLab