Use yarn when available
Summary: **Motivation** `react-native init` takes minutes even on a fast network. Yarn makes it much quicker. When yarn is not installed on the system: <img width="440" alt="screenshot 2016-10-31 22 21 19" src="https://cloud.githubusercontent.com/assets/346214/19873897/7bad5662-9fb9-11e6-85fb-ad4879949dad.png"> When yarn is installed: <img width="441" alt="screenshot 2016-10-31 22 02 20" src="https://cloud.githubusercontent.com/assets/346214/19873898/7baf4c56-9fb9-11e6-96b3-007f93d2438a.png"> Also added the option `react-native init AwesomeApp --npm` as a fallback in case yarn is not stable enough for some people (I saw some Github issues that yarn hangs for example; for me it works great though). **Test plan** 1. Publish to Sinopia: https://github.com/facebook/react-native/tree/master/react-native-cli 2. `react-native init AwesomeApp` ***Tested the following setups*** - New CLI - uses yarn, new react-native - uses yarn - Old CLI (1.0.0) - doesn't use yarn, new react-native - uses yarn - Closes https://github.com/facebook/react-native/pull/10626 Differential Revision: D4110883 Pulled By: bestander fbshipit-source-id: 8a3427e2bc9158cf5fadd8ddf5b31e4b50ce6453
This commit is contained in:
parent
5105c09f56
commit
94711bfb06
|
@ -8,11 +8,40 @@
|
||||||
*/
|
*/
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
|
var execSync = require('child_process').execSync;
|
||||||
var fs = require('fs');
|
var fs = require('fs');
|
||||||
var path = require('path');
|
var path = require('path');
|
||||||
|
var semver = require('semver')
|
||||||
var utils = require('../generator-utils');
|
var utils = require('../generator-utils');
|
||||||
var yeoman = require('yeoman-generator');
|
var yeoman = require('yeoman-generator');
|
||||||
|
|
||||||
|
// Use Yarn if available, it's much faster than the npm client.
|
||||||
|
// Return the version of yarn installed on the system, null if yarn is not available.
|
||||||
|
function getYarnVersionIfAvailable() {
|
||||||
|
let yarnVersion;
|
||||||
|
try {
|
||||||
|
// execSync returns a Buffer -> convert to string
|
||||||
|
if (process.platform.startsWith('win')) {
|
||||||
|
yarnVersion = (execSync('yarn --version').toString() || '').trim();
|
||||||
|
} else {
|
||||||
|
yarnVersion = (execSync('yarn --version 2>/dev/null').toString() || '').trim();
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
// yarn < 0.16 has a 'missing manifest' bug
|
||||||
|
try {
|
||||||
|
if (semver.gte(yarnVersion, '0.16.0')) {
|
||||||
|
return yarnVersion;
|
||||||
|
} else {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Cannot parse yarn version: ' + yarnVersion);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
module.exports = yeoman.generators.NamedBase.extend({
|
module.exports = yeoman.generators.NamedBase.extend({
|
||||||
constructor: function() {
|
constructor: function() {
|
||||||
yeoman.generators.NamedBase.apply(this, arguments);
|
yeoman.generators.NamedBase.apply(this, arguments);
|
||||||
|
@ -36,6 +65,12 @@ module.exports = yeoman.generators.NamedBase.extend({
|
||||||
type: Boolean,
|
type: Boolean,
|
||||||
defaults: false
|
defaults: false
|
||||||
});
|
});
|
||||||
|
// Temporary option until yarn becomes stable.
|
||||||
|
this.option('npm', {
|
||||||
|
desc: 'Use the npm client, even if yarn is available.',
|
||||||
|
type: Boolean,
|
||||||
|
defaults: false
|
||||||
|
});
|
||||||
|
|
||||||
// this passes command line arguments down to the composed generators
|
// this passes command line arguments down to the composed generators
|
||||||
var args = {args: arguments[0], options: this.options};
|
var args = {args: arguments[0], options: this.options};
|
||||||
|
@ -112,12 +147,24 @@ module.exports = yeoman.generators.NamedBase.extend({
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.npmInstall(`react@${reactVersion}`, { '--save': true, '--save-exact': true });
|
const yarnVersion = (!this.options['npm']) && getYarnVersionIfAvailable();
|
||||||
|
|
||||||
|
console.log('Installing React...');
|
||||||
|
if (yarnVersion) {
|
||||||
|
execSync(`yarn add react@${reactVersion}`);
|
||||||
|
} else {
|
||||||
|
this.npmInstall(`react@${reactVersion}`, { '--save': true, '--save-exact': true });
|
||||||
|
}
|
||||||
if (!this.options['skip-jest']) {
|
if (!this.options['skip-jest']) {
|
||||||
this.npmInstall(`jest babel-jest babel-preset-react-native react-test-renderer@${reactVersion}`.split(' '), {
|
console.log('Installing Jest...');
|
||||||
saveDev: true,
|
if (yarnVersion) {
|
||||||
'--save-exact': true
|
execSync(`yarn add jest babel-jest jest-react-native babel-preset-react-native react-test-renderer@${reactVersion} --dev --exact`);
|
||||||
});
|
} else {
|
||||||
|
this.npmInstall(`jest babel-jest babel-preset-react-native react-test-renderer@${reactVersion}`.split(' '), {
|
||||||
|
saveDev: true,
|
||||||
|
'--save-exact': true
|
||||||
|
});
|
||||||
|
}
|
||||||
fs.writeFileSync(
|
fs.writeFileSync(
|
||||||
path.join(
|
path.join(
|
||||||
this.destinationRoot(),
|
this.destinationRoot(),
|
||||||
|
@ -125,7 +172,6 @@ module.exports = yeoman.generators.NamedBase.extend({
|
||||||
),
|
),
|
||||||
'{\n"presets": ["react-native"]\n}'
|
'{\n"presets": ["react-native"]\n}'
|
||||||
);
|
);
|
||||||
|
|
||||||
this.fs.copy(
|
this.fs.copy(
|
||||||
this.templatePath('__tests__'),
|
this.templatePath('__tests__'),
|
||||||
this.destinationPath('__tests__'),
|
this.destinationPath('__tests__'),
|
||||||
|
@ -133,7 +179,6 @@ module.exports = yeoman.generators.NamedBase.extend({
|
||||||
nodir: false
|
nodir: false
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
var packageJSONPath = path.join(
|
var packageJSONPath = path.join(
|
||||||
this.destinationRoot(),
|
this.destinationRoot(),
|
||||||
'package.json'
|
'package.json'
|
||||||
|
|
|
@ -38,6 +38,7 @@
|
||||||
var fs = require('fs');
|
var fs = require('fs');
|
||||||
var path = require('path');
|
var path = require('path');
|
||||||
var exec = require('child_process').exec;
|
var exec = require('child_process').exec;
|
||||||
|
var execSync = require('child_process').execSync;
|
||||||
var spawn = require('child_process').spawn;
|
var spawn = require('child_process').spawn;
|
||||||
var chalk = require('chalk');
|
var chalk = require('chalk');
|
||||||
var prompt = require('prompt');
|
var prompt = require('prompt');
|
||||||
|
@ -74,6 +75,33 @@ var REACT_NATIVE_PACKAGE_JSON_PATH = function() {
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Use Yarn if available, it's much faster than the npm client.
|
||||||
|
// Return the version of yarn installed on the system, null if yarn is not available.
|
||||||
|
function getYarnVersionIfAvailable() {
|
||||||
|
let yarnVersion;
|
||||||
|
try {
|
||||||
|
// execSync returns a Buffer -> convert to string
|
||||||
|
if (process.platform.startsWith('win')) {
|
||||||
|
yarnVersion = (execSync('yarn --version').toString() || '').trim();
|
||||||
|
} else {
|
||||||
|
yarnVersion = (execSync('yarn --version 2>/dev/null').toString() || '').trim();
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
// yarn < 0.16 has a 'missing manifest' bug
|
||||||
|
try {
|
||||||
|
if (semver.gte(yarnVersion, '0.16.0')) {
|
||||||
|
return yarnVersion;
|
||||||
|
} else {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Cannot parse yarn version: ' + yarnVersion);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
checkForVersionArgument();
|
checkForVersionArgument();
|
||||||
|
|
||||||
var cli;
|
var cli;
|
||||||
|
@ -102,8 +130,8 @@ if (cli) {
|
||||||
);
|
);
|
||||||
process.exit(1);
|
process.exit(1);
|
||||||
} else {
|
} else {
|
||||||
if (!argv.verbose) console.log('This may take some time...');
|
const rnPackage = argv.version;
|
||||||
init(commands[1], argv.verbose, argv.version);
|
init(commands[1], argv.verbose, rnPackage, argv.npm);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
|
@ -117,7 +145,7 @@ if (cli) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function validatePackageName(name) {
|
function validateProjectName(name) {
|
||||||
if (!name.match(/^[$A-Z_][0-9A-Z_$]*$/i)) {
|
if (!name.match(/^[$A-Z_][0-9A-Z_$]*$/i)) {
|
||||||
console.error(
|
console.error(
|
||||||
'"%s" is not a valid name for a project. Please use a valid identifier ' +
|
'"%s" is not a valid name for a project. Please use a valid identifier ' +
|
||||||
|
@ -137,17 +165,24 @@ function validatePackageName(name) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function init(name, verbose, rnPackage) {
|
/**
|
||||||
validatePackageName(name);
|
* @param name Project name, e.g. 'AwesomeApp'.
|
||||||
|
* @param verbose If true, will run 'npm install' in verbose mode (for debugging).
|
||||||
|
* @param rnPackage Version of React Native to install, e.g. '0.38.0'.
|
||||||
|
* @param forceNpmClient If true, always use the npm command line client,
|
||||||
|
* don't use yarn even if available.
|
||||||
|
*/
|
||||||
|
function init(name, verbose, rnPackage, forceNpmClient) {
|
||||||
|
validateProjectName(name);
|
||||||
|
|
||||||
if (fs.existsSync(name)) {
|
if (fs.existsSync(name)) {
|
||||||
createAfterConfirmation(name, verbose, rnPackage);
|
createAfterConfirmation(name, verbose, rnPackage, forceNpmClient);
|
||||||
} else {
|
} else {
|
||||||
createProject(name, verbose, rnPackage);
|
createProject(name, verbose, rnPackage, forceNpmClient);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function createAfterConfirmation(name, verbose, rnPackage) {
|
function createAfterConfirmation(name, verbose, rnPackage, forceNpmClient) {
|
||||||
prompt.start();
|
prompt.start();
|
||||||
|
|
||||||
var property = {
|
var property = {
|
||||||
|
@ -160,7 +195,7 @@ function createAfterConfirmation(name, verbose, rnPackage) {
|
||||||
|
|
||||||
prompt.get(property, function (err, result) {
|
prompt.get(property, function (err, result) {
|
||||||
if (result.yesno[0] === 'y') {
|
if (result.yesno[0] === 'y') {
|
||||||
createProject(name, verbose, rnPackage);
|
createProject(name, verbose, rnPackage, forceNpmClient);
|
||||||
} else {
|
} else {
|
||||||
console.log('Project initialization canceled');
|
console.log('Project initialization canceled');
|
||||||
process.exit();
|
process.exit();
|
||||||
|
@ -168,7 +203,7 @@ function createAfterConfirmation(name, verbose, rnPackage) {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function createProject(name, verbose, rnPackage) {
|
function createProject(name, verbose, rnPackage, forceNpmClient) {
|
||||||
var root = path.resolve(name);
|
var root = path.resolve(name);
|
||||||
var projectName = path.basename(root);
|
var projectName = path.basename(root);
|
||||||
|
|
||||||
|
@ -192,20 +227,18 @@ function createProject(name, verbose, rnPackage) {
|
||||||
fs.writeFileSync(path.join(root, 'package.json'), JSON.stringify(packageJson));
|
fs.writeFileSync(path.join(root, 'package.json'), JSON.stringify(packageJson));
|
||||||
process.chdir(root);
|
process.chdir(root);
|
||||||
|
|
||||||
console.log('Installing react-native package from npm...');
|
|
||||||
|
|
||||||
if (verbose) {
|
if (verbose) {
|
||||||
runVerbose(root, projectName, rnPackage);
|
runVerbose(root, projectName, rnPackage, forceNpmClient);
|
||||||
} else {
|
} else {
|
||||||
run(root, projectName, rnPackage);
|
run(root, projectName, rnPackage, forceNpmClient);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function getInstallPackage(rnPackage) {
|
function getInstallPackage(rnPackage) {
|
||||||
var packageToInstall = 'react-native';
|
var packageToInstall = 'react-native';
|
||||||
var valideSemver = semver.valid(rnPackage);
|
var isValidSemver = semver.valid(rnPackage);
|
||||||
if (valideSemver) {
|
if (isValidSemver) {
|
||||||
packageToInstall += '@' + valideSemver;
|
packageToInstall += '@' + isValidSemver;
|
||||||
} else if (rnPackage) {
|
} else if (rnPackage) {
|
||||||
// for tar.gz or alternative paths
|
// for tar.gz or alternative paths
|
||||||
packageToInstall = rnPackage;
|
packageToInstall = rnPackage;
|
||||||
|
@ -213,23 +246,36 @@ function getInstallPackage(rnPackage) {
|
||||||
return packageToInstall;
|
return packageToInstall;
|
||||||
}
|
}
|
||||||
|
|
||||||
function run(root, projectName, rnPackage) {
|
function run(root, projectName, rnPackage, forceNpmClient) {
|
||||||
exec('npm install --save --save-exact ' + getInstallPackage(rnPackage), function(e, stdout, stderr) {
|
const yarnVersion = (!forceNpmClient) && getYarnVersionIfAvailable();
|
||||||
if (e) {
|
let installCommand;
|
||||||
|
if (yarnVersion) {
|
||||||
|
console.log('Using yarn v' + yarnVersion);
|
||||||
|
console.log('Installing ' + getInstallPackage(rnPackage) + '...');
|
||||||
|
installCommand = 'yarn add ' + getInstallPackage(rnPackage) + ' --exact';
|
||||||
|
} else {
|
||||||
|
console.log('Installing ' + getInstallPackage(rnPackage) + ' from npm...');
|
||||||
|
if (!forceNpmClient) {
|
||||||
|
console.log('Consider installing yarn to make this faster: https://yarnpkg.com');
|
||||||
|
}
|
||||||
|
installCommand = 'npm install --save --save-exact ' + getInstallPackage(rnPackage);
|
||||||
|
}
|
||||||
|
exec(installCommand, function(err, stdout, stderr) {
|
||||||
|
if (err) {
|
||||||
console.log(stdout);
|
console.log(stdout);
|
||||||
console.error(stderr);
|
console.error(stderr);
|
||||||
console.error('`npm install --save --save-exact react-native` failed');
|
console.error('Command `' + installCommand + '` failed.');
|
||||||
process.exit(1);
|
process.exit(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
checkNodeVersion();
|
checkNodeVersion();
|
||||||
|
|
||||||
cli = require(CLI_MODULE_PATH());
|
cli = require(CLI_MODULE_PATH());
|
||||||
cli.init(root, projectName);
|
cli.init(root, projectName);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function runVerbose(root, projectName, rnPackage) {
|
function runVerbose(root, projectName, rnPackage, forceNpmClient) {
|
||||||
|
// Use npm client, yarn doesn't support --verbose yet
|
||||||
|
console.log('Installing ' + getInstallPackage(rnPackage) + ' from npm. This might take a while...');
|
||||||
var proc = spawn(/^win/.test(process.platform) ? 'npm.cmd' : 'npm', ['install', '--verbose', '--save', '--save-exact', getInstallPackage(rnPackage)], {stdio: 'inherit'});
|
var proc = spawn(/^win/.test(process.platform) ? 'npm.cmd' : 'npm', ['install', '--verbose', '--save', '--save-exact', getInstallPackage(rnPackage)], {stdio: 'inherit'});
|
||||||
proc.on('close', function (code) {
|
proc.on('close', function (code) {
|
||||||
if (code !== 0) {
|
if (code !== 0) {
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "react-native-cli",
|
"name": "react-native-cli",
|
||||||
"version": "1.0.0",
|
"version": "1.1.0",
|
||||||
"license": "BSD-3-Clause",
|
"license": "BSD-3-Clause",
|
||||||
"description": "The React Native CLI tools",
|
"description": "The React Native CLI tools",
|
||||||
"main": "index.js",
|
"main": "index.js",
|
||||||
|
|
Loading…
Reference in New Issue