Skip to content

Commit ca403f0

Browse files
author
Martin Konicek
committed
Use Yarn if available
Summary: **Motivation** If the project is using yarn to manage its dependencies, we should be running 'yarn add' to upgrade the React Native dependency, rather than 'npm install'. **Test plan (required)** Running in a project that's in a bad state: Error: react-native version in "package.json" (0.29.2) doesn't match the installed version in "node_modules" (0.38.0). Try running "yarn" to fix this. Removed yarn.lock file, ran again: Error: react-native version in "package.json" (0.29.2) doesn't match the installed version in "node_modules" (0.38.0). Try running "npm install" to fix this. Running inside a folder that doesn't contain a `package.json` file: Error: Unable to find "/Users/mkonicek/Zero29App/package.json" or "/Users/mkonicek/Zero29App/node_modules/react-native/package.json". Make sure you ran "yarn" and that you are inside a React Native project. Removed yarn.lock file, ran again: Error: Unable to find "/Users/mkonicek/Zero29App/package.json" or "/Users/ Closes #11225 Differential Revision: D4261102 Pulled By: mkonicek fbshipit-source-id: b44ae91fe46f2c81b14616ca2868cc171692c895
1 parent 70d4023 commit ca403f0

File tree

4 files changed

+121
-41
lines changed

4 files changed

+121
-41
lines changed

react-native-git-upgrade/checks.js

+4-2
Original file line numberDiff line numberDiff line change
@@ -19,12 +19,14 @@ function checkDeclaredVersion(declaredVersion) {
1919
}
2020
}
2121

22-
function checkMatchingVersions(currentVersion, declaredVersion) {
22+
function checkMatchingVersions(currentVersion, declaredVersion, useYarn) {
2323
if (!semver.satisfies(currentVersion, declaredVersion)) {
2424
throw new Error(
2525
'react-native version in "package.json" (' + declaredVersion + ') doesn\'t match ' +
2626
'the installed version in "node_modules" (' + currentVersion + ').\n' +
27-
'Try running "npm install" to fix this.'
27+
(useYarn ?
28+
'Try running "yarn" to fix this.' :
29+
'Try running "npm install" to fix this.')
2830
);
2931
}
3032
}

react-native-git-upgrade/cliEntry.js

+57-38
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ const TerminalAdapter = require('yeoman-environment/lib/adapter');
1818
const log = require('npmlog');
1919
const rimraf = require('rimraf');
2020
const semver = require('semver');
21+
const yarn = require('./yarn');
2122

2223
const {
2324
checkDeclaredVersion,
@@ -31,8 +32,7 @@ log.heading = 'git-upgrade';
3132

3233
/**
3334
* Promisify the callback-based shelljs function exec
34-
* @param command
35-
* @param logOutput
35+
* @param logOutput If true, log the stdout of the command.
3636
* @returns {Promise}
3737
*/
3838
function exec(command, logOutput) {
@@ -62,42 +62,37 @@ stdout: ${stdout}`));
6262
})
6363
}
6464

65-
/**
66-
+ * Returns two objects:
67-
+ * - Parsed node_modules/react-native/package.json
68-
+ * - Parsed package.json
69-
+ */
70-
function readPackageFiles() {
65+
function parseJsonFile(path, useYarn) {
66+
const installHint = useYarn ?
67+
'Make sure you ran "yarn" and that you are inside a React Native project.' :
68+
'Make sure you ran "npm install" and that you are inside a React Native project.';
69+
let fileContents;
70+
try {
71+
fileContents = fs.readFileSync(path, 'utf8');
72+
} catch (err) {
73+
throw new Error('Cannot find "' + path + '". ' + installHint);
74+
}
75+
try {
76+
return JSON.parse(fileContents);
77+
} catch (err) {
78+
throw new Error('Cannot parse "' + path + '": ' + err.message);
79+
}
80+
}
81+
82+
function readPackageFiles(useYarn) {
7183
const reactNativeNodeModulesPakPath = path.resolve(
72-
process.cwd(),
73-
'node_modules',
74-
'react-native',
75-
'package.json'
84+
process.cwd(), 'node_modules', 'react-native', 'package.json'
7685
);
77-
7886
const reactNodeModulesPakPath = path.resolve(
79-
process.cwd(),
80-
'node_modules',
81-
'react',
82-
'package.json'
87+
process.cwd(), 'node_modules', 'react', 'package.json'
8388
);
84-
8589
const pakPath = path.resolve(
86-
process.cwd(),
87-
'package.json'
90+
process.cwd(), 'package.json'
8891
);
89-
90-
try {
91-
const reactNativeNodeModulesPak = JSON.parse(fs.readFileSync(reactNativeNodeModulesPakPath, 'utf8'));
92-
const reactNodeModulesPak = JSON.parse(fs.readFileSync(reactNodeModulesPakPath, 'utf8'));
93-
const pak = JSON.parse(fs.readFileSync(pakPath, 'utf8'));
94-
95-
return {reactNativeNodeModulesPak, reactNodeModulesPak, pak};
96-
} catch (err) {
97-
throw new Error(
98-
'Unable to find one of "' + pakPath + '", "' + rnPakPath + '" or "' + reactPakPath + '". ' +
99-
'Make sure you ran "npm install" and that you are inside a React Native project.'
100-
)
92+
return {
93+
reactNativeNodeModulesPak: parseJsonFile(reactNativeNodeModulesPakPath),
94+
reactNodeModulesPak: parseJsonFile(reactNodeModulesPakPath),
95+
pak: parseJsonFile(pakPath)
10196
}
10297
}
10398

@@ -191,6 +186,21 @@ async function checkForUpdates() {
191186
}
192187
}
193188

189+
/**
190+
* If true, use yarn instead of the npm client to upgrade the project.
191+
*/
192+
function shouldUseYarn(cliArgs, projectDir) {
193+
if (cliArgs && cliArgs.npm) {
194+
return false;
195+
}
196+
const yarnVersion = yarn.getYarnVersionIfAvailable();
197+
if (yarnVersion && yarn.isProjectUsingYarn(projectDir)) {
198+
log.info('Using yarn ' + yarnVersion);
199+
return true;
200+
}
201+
return false;
202+
}
203+
194204
/**
195205
* @param requestedVersion The version argument, e.g. 'react-native-git-upgrade 0.38'.
196206
* `undefined` if no argument passed.
@@ -204,8 +214,10 @@ async function run(requestedVersion, cliArgs) {
204214
try {
205215
await checkForUpdates();
206216

217+
const useYarn = shouldUseYarn(cliArgs, path.resolve(process.cwd()));
218+
207219
log.info('Read package.json files');
208-
const {reactNativeNodeModulesPak, reactNodeModulesPak, pak} = readPackageFiles();
220+
const {reactNativeNodeModulesPak, reactNodeModulesPak, pak} = readPackageFiles(useYarn);
209221
const appName = pak.name;
210222
const currentVersion = reactNativeNodeModulesPak.version;
211223
const currentReactVersion = reactNodeModulesPak.version;
@@ -218,7 +230,7 @@ async function run(requestedVersion, cliArgs) {
218230
checkDeclaredVersion(declaredVersion);
219231

220232
log.info('Check matching versions');
221-
checkMatchingVersions(currentVersion, declaredVersion);
233+
checkMatchingVersions(currentVersion, declaredVersion, useYarn);
222234

223235
log.info('Check React peer dependency');
224236
checkReactPeerDependency(currentVersion, declaredReactVersion);
@@ -231,6 +243,8 @@ async function run(requestedVersion, cliArgs) {
231243
const viewOutput = await exec(viewCommand, verbose).then(JSON.parse);
232244
const newVersion = viewOutput.version;
233245
const newReactVersionRange = viewOutput['peerDependencies.react'];
246+
// Print which versions we're upgrading to
247+
log.info('Upgrading to React Native ' + newVersion + (newReactVersionRange ? ', React ' + newReactVersionRange : ''));
234248

235249
log.info('Check new version');
236250
checkNewVersionValid(newVersion, requestedVersion);
@@ -264,9 +278,14 @@ async function run(requestedVersion, cliArgs) {
264278
await exec('git commit -m "Old version" --allow-empty', verbose);
265279

266280
log.info('Install the new version');
267-
let installCommand = 'npm install --save --color=always';
281+
let installCommand;
282+
if (useYarn) {
283+
installCommand = 'yarn add';
284+
} else {
285+
installCommand = 'npm install --save --color=always';
286+
}
268287
installCommand += ' react-native@' + newVersion;
269-
if (!semver.satisfies(currentReactVersion, newReactVersionRange)) {
288+
if (newReactVersionRange && !semver.satisfies(currentReactVersion, newReactVersionRange)) {
270289
// Install React as well to avoid unmet peer dependency
271290
installCommand += ' react@' + newReactVersionRange;
272291
}
@@ -296,8 +315,8 @@ async function run(requestedVersion, cliArgs) {
296315
await exec(`git apply --3way ${patchPath}`, true);
297316
} catch (err) {
298317
log.warn(
299-
'The upgrade process succeeded but there might be conflicts to be resolved.\n' +
300-
'See above for the list of files that had merge conflicts.');
318+
'The upgrade process succeeded but there might be conflicts to be resolved. ' +
319+
'See above for the list of files that have merge conflicts.');
301320
} finally {
302321
log.info('Upgrade done');
303322
if (cliArgs.verbose) {

react-native-git-upgrade/index.js

+2-1
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,8 @@ if (argv._.length === 0 && (argv.h || argv.help)) {
2727
'',
2828
' -h, --help output usage information',
2929
' -v, --version output the version number',
30-
' --verbose output',
30+
' --verbose output debugging info',
31+
' --npm force using the npm client even if your project uses yarn',
3132
'',
3233
].join('\n'));
3334
process.exit(0);

react-native-git-upgrade/yarn.js

+58
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
/**
2+
* Copyright (c) 2015-present, Facebook, Inc.
3+
* All rights reserved.
4+
*
5+
* This source code is licensed under the BSD-style license found in the
6+
* LICENSE file in the root directory of this source tree. An additional grant
7+
* of patent rights can be found in the PATENTS file in the same directory.
8+
*/
9+
'use strict';
10+
11+
const fs = require('fs');
12+
const path = require('path');
13+
const semver = require('semver');
14+
15+
/**
16+
* Use Yarn if available, it's much faster than the npm client.
17+
* Return the version of yarn installed on the system, null if yarn is not available.
18+
*/
19+
function getYarnVersionIfAvailable() {
20+
let yarnVersion;
21+
try {
22+
// execSync returns a Buffer -> convert to string
23+
if (process.platform.startsWith('win')) {
24+
yarnVersion = (execSync('yarn --version').toString() || '').trim();
25+
} else {
26+
yarnVersion = (execSync('yarn --version 2>/dev/null').toString() || '').trim();
27+
}
28+
} catch (error) {
29+
return null;
30+
}
31+
// yarn < 0.16 has a 'missing manifest' bug
32+
try {
33+
if (semver.gte(yarnVersion, '0.16.0')) {
34+
return yarnVersion;
35+
} else {
36+
return null;
37+
}
38+
} catch (error) {
39+
console.error('Cannot parse yarn version: ' + yarnVersion);
40+
return null;
41+
}
42+
}
43+
44+
/**
45+
* Check that 'react-native init' itself used yarn to install React Native.
46+
* When using an old global react-native-cli@1.0.0 (or older), we don't want
47+
* to install React Native with npm, and React + Jest with yarn.
48+
* Let's be safe and not mix yarn and npm in a single project.
49+
* @param projectDir e.g. /Users/martin/AwesomeApp
50+
*/
51+
function isProjectUsingYarn(projectDir) {
52+
return fs.existsSync(path.join(projectDir, 'yarn.lock'));
53+
}
54+
55+
module.exports = {
56+
getYarnVersionIfAvailable: getYarnVersionIfAvailable,
57+
isProjectUsingYarn: isProjectUsingYarn,
58+
};

0 commit comments

Comments
 (0)