diff --git a/packages/create-react-app/index.js b/packages/create-react-app/index.js index d5afee43b8f..6a5dcbdc4ed 100755 --- a/packages/create-react-app/index.js +++ b/packages/create-react-app/index.js @@ -58,6 +58,8 @@ var path = require('path'); var execSync = require('child_process').execSync; var spawn = require('cross-spawn'); var semver = require('semver'); +var https = require('https'); +var request = require('request'); var projectName; @@ -145,12 +147,14 @@ function shouldUseYarn() { } } -function install(dependencies, verbose, callback) { +function install(dependencies, verbose, useYarnLock, callback) { var command; var args; if (shouldUseYarn()) { command = 'yarnpkg'; - args = [ 'add', '--exact'].concat(dependencies); + + // If the yarn.lock exists, just run `yarnpkg`. + args = useYarnLock ? [] : ['add', '--exact'].concat(dependencies); } else { checkNpmVersion(); command = 'npm'; @@ -173,6 +177,56 @@ function run(root, appName, version, verbose, originalDirectory, template) { var allDependencies = ['react', 'react-dom', packageToInstall]; + var installDependencies = function(yarnLockExists) { + install(allDependencies, verbose, yarnLockExists, function(code, command, args) { + if (code !== 0) { + console.log(); + console.error('Aborting installation.', chalk.cyan(command + ' ' + args.join(' ')), 'has failed.'); + // On 'exit' we will delete these files from target directory. + var knownGeneratedFiles = [ + 'package.json', 'npm-debug.log', 'yarn-error.log', 'yarn-debug.log', 'node_modules' + ]; + var currentFiles = fs.readdirSync(path.join(root)); + currentFiles.forEach(function (file) { + knownGeneratedFiles.forEach(function (fileToMatch) { + // This will catch `(npm-debug|yarn-error|yarn-debug).log*` files + // and the rest of knownGeneratedFiles. + if ((fileToMatch.match(/.log/g) && file.indexOf(fileToMatch) === 0) || file === fileToMatch) { + console.log('Deleting generated file...', chalk.cyan(file)); + fs.removeSync(path.join(root, file)); + } + }); + }); + var remainingFiles = fs.readdirSync(path.join(root)); + if (!remainingFiles.length) { + // Delete target folder if empty + console.log('Deleting', chalk.cyan(appName + '/'), 'from', chalk.cyan(path.resolve(root, '..'))); + fs.removeSync(path.join(root)); + } + console.log('Done.'); + process.exit(1); + } + + checkNodeVersion(packageName); + + // Since react-scripts has been installed with --save + // We need to move it into devDependencies and rewrite package.json + if (!yarnLockExists) { + moveReactScriptsToDev(packageName); + } + + var scriptsPath = path.resolve( + process.cwd(), + 'node_modules', + packageName, + 'scripts', + 'init.js' + ); + var init = require(scriptsPath); + init(root, appName, verbose, originalDirectory, template); + }); + } + console.log('Installing packages. This might take a couple minutes.'); console.log( 'Installing ' + chalk.cyan('react') + ', ' + chalk.cyan('react-dom') + @@ -180,51 +234,43 @@ function run(root, appName, version, verbose, originalDirectory, template) { ); console.log(); - install(allDependencies, verbose, function(code, command, args) { - if (code !== 0) { - console.log(); - console.error('Aborting installation.', chalk.cyan(command + ' ' + args.join(' ')), 'has failed.'); - // On 'exit' we will delete these files from target directory. - var knownGeneratedFiles = [ - 'package.json', 'npm-debug.log', 'yarn-error.log', 'yarn-debug.log', 'node_modules' - ]; - var currentFiles = fs.readdirSync(path.join(root)); - currentFiles.forEach(function (file) { - knownGeneratedFiles.forEach(function (fileToMatch) { - // This will catch `(npm-debug|yarn-error|yarn-debug).log*` files - // and the rest of knownGeneratedFiles. - if ((fileToMatch.match(/.log/g) && file.indexOf(fileToMatch) === 0) || file === fileToMatch) { - console.log('Deleting generated file...', chalk.cyan(file)); - fs.removeSync(path.join(root, file)); - } - }); - }); - var remainingFiles = fs.readdirSync(path.join(root)); - if (!remainingFiles.length) { - // Delete target folder if empty - console.log('Deleting', chalk.cyan(appName + '/'), 'from', chalk.cyan(path.resolve(root, '..'))); - fs.removeSync(path.join(root)); + // Try to download the yarn.lock file. + if (shouldUseYarn()) { + var releaseRequest = request({ + uri: 'https://api.github.com/repos/facebookincubator/create-react-app/releases/latest', + method: 'GET', + headers: { + 'User-Agent': 'create-react-app' } - console.log('Done.'); - process.exit(1); - } - - 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', - packageName, - 'scripts', - 'init.js' - ); - var init = require(scriptsPath); - init(root, appName, verbose, originalDirectory, template); - }); + }, function(err, response, releasesBody) { + var yarnLockUrl = JSON.parse(releasesBody).assets.reduce(function(acc, asset) { + return asset.name === 'yarn.lock' ? asset.browser_download_url : acc; + }, ""); + + // TODO: We'll want to copy the package.json && yarn.lock, + // `yarn install` will change the lockfile if the deps don't + // satisfy versions in the package.json. + + if (!yarnLockUrl) { + installDependencies(false); + + return; + } + + var yarnLockRequest = request(yarnLockUrl, function(err, response, body) { + fs.writeFileSync('yarn.lock', body); // TODO: Async + + // Success. Write the package.json dependencies manually. + writePackageJsonFromYarnLock(packageName, packageToInstall); + installDependencies(true); + }).on('error', function(err) { + fs.unlink(dest); + installDependencies(false); + }); + }); + } else { + installDependencies(false); + } } function getInstallPackage(version) { @@ -352,6 +398,26 @@ function moveReactScriptsToDev(packageName) { fs.writeFileSync(packagePath, JSON.stringify(packageJson, null, 2)); } +// TODO: Get rid of most this - we'll copy a package.json +// Will need to write the name, though. +function writePackageJsonFromYarnLock(packageName, packageVersion) { + var packagePath = path.join(process.cwd(), 'package.json'); + var packageJson = require(packagePath); + + packageJson.devDependencies = packageJson.devDependencies || {}; + packageJson.devDependencies[packageName] = packageVersion; + + var reactVersion = 'latest'; + var reactDomVersion = 'latest'; + + packageJson.dependencies = { + 'react': reactVersion, + 'react-dom': reactDomVersion + }; + + 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/create-react-app/package.json b/packages/create-react-app/package.json index e1eb1c45ef2..bc6bcf4f952 100644 --- a/packages/create-react-app/package.json +++ b/packages/create-react-app/package.json @@ -24,6 +24,7 @@ "commander": "^2.9.0", "cross-spawn": "^4.0.0", "fs-extra": "^1.0.0", + "request": "^2.79.0", "semver": "^5.0.3" } }