Skip to content

Commit

Permalink
fix(CLI): Ensure CLI downloads current version (#2559)
Browse files Browse the repository at this point in the history
The CLI is currently attempting to install pre-release versions of react-native-windows, and because "vnext" > "rc" in terms of semver, it's always picking up the vnext version.

This change assumes that unless specified, the user will either want to install a stable version or a prerelease labeled `rc` version of `react-native-windows`.

Recent changes to `rnpm-plugin-windows` added a bunch of logic to patch the react-native dependency version. This change also moves this logic into the project generator instead, allowing us to iterate on the "patch" logic in `react-native-windows` as opposed to `rnpm-plugin-windows`.
  • Loading branch information
rozele authored Jun 5, 2019
1 parent 0b4a51e commit 4ff550e
Show file tree
Hide file tree
Showing 6 changed files with 101 additions and 103 deletions.
5 changes: 2 additions & 3 deletions current/local-cli/rnpm/windows/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,11 @@ module.exports = [{
description: 'The native project namespace.'
}, {
command: '--verbose',
description: 'Enables logging',
description: 'Enables logging.',
default: false,
}, {
command: '--template [template]',
description: 'Template to install. Possible values vnext, csharp',
default: 'csharp',
description: 'Template to install. E.g., `vnext`.',
}]
},{
func: require('./src/wpf'),
Expand Down
2 changes: 1 addition & 1 deletion current/local-cli/rnpm/windows/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "rnpm-plugin-windows",
"version": "0.2.9",
"version": "0.2.10",
"description": "rnpm plugin that generates a Windows template project",
"main": "index.js",
"keywords": [
Expand Down
79 changes: 43 additions & 36 deletions current/local-cli/rnpm/windows/src/common.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,6 @@ const semver = require('semver');
const Registry = require('npm-registry');
const child_process = require('child_process');
const validUrl = require('valid-url');
const request = require('request');
const fsextra = require('fs-extra');
var extract = require('extract-zip')

let npmConfReg = child_process.execSync('npm config get registry').toString().trim();
let NPM_REGISTRY_URL = validUrl.is_uri(npmConfReg) ? npmConfReg : 'http://registry.npmjs.org';
Expand Down Expand Up @@ -42,7 +39,20 @@ function getLatestVersion() {
});
}

function getMatchingVersion(version) {
function isTagMatch(packageVersion, requestTag) {
const prerelease = semver.prerelease(packageVersion);
return prerelease && prerelease[0] === requestTag;
}

function isVersionMatch(packageVersion, requestVersion, requestTag) {
const { major, minor } = semver.parse(packageVersion);
const minVersion = semver.minVersion(requestVersion);
return major === minVersion.major &&
minor === minVersion.minor &&
isTagMatch(packageVersion, requestTag);
}

function getMatchingVersion(version, tag, ignoreStable) {
console.log(`Checking for react-native-windows version matching ${version}...`);
return new Promise(function (resolve, reject) {
npm.packages.range('react-native-windows', version, (err, release) => {
Expand All @@ -53,16 +63,35 @@ function getMatchingVersion(version) {
`Latest version of react-native-windows is ${latestVersion}, try switching to ` +
`react-native@${semver.major(latestVersion)}.${semver.minor(latestVersion)}.*.`));
}).catch(error => reject(new Error(`Could not find react-native-windows@${version}.`)));
}

const matchedVersion = release.version;
const matchedPrerelease = semver.prerelease(matchedVersion);
const isPrerelease = tag && !!matchedPrerelease;
if (!isVersionMatch(matchedVersion, version, tag) && (ignoreStable || isPrerelease)) {
const versions = Object.keys(release.versions);
const candidates = versions.filter(v => isVersionMatch(v, version, tag)).sort(semver.rcompare);
if (candidates.length === 0) {
const tagMatches = versions.filter(v => isTagMatch(v, tag)).sort(semver.rcompare);
if (tagMatches.length === 0) {
reject(new Error(`Could not find react-native-windows@${version}-${tag}.*.`));
} else {
const latestVersion = tagMatches[0];
reject(new Error(`Could not find react-native-windows@${version}-${tag}.*. ` +
`Latest version of react-native-windows for tag '${tag}' is ${latestVersion}, try switching to ` +
`react-native@${semver.major(latestVersion)}.${semver.minor(latestVersion)}.*.`));
}
}
resolve(candidates[0]);
} else {
resolve(release.version);
resolve(matchedVersion);
}
});
});
}

const getInstallPackage = function (version) {
let packageToInstall = 'react-native-windows';

const getInstallPackage = function (version, tag, useStable) {
const packageToInstall = 'react-native-windows';
const validVersion = semver.valid(version);
const validRange = semver.validRange(version);
if ((validVersion && !semver.gtr(validVersion, '0.26.*')) ||
Expand All @@ -73,9 +102,11 @@ const getInstallPackage = function (version) {
process.exit(1);
}

if (validVersion || validRange) {
return getMatchingVersion(version)
.then(resultVersion => packageToInstall + '@' + resultVersion);
if (validVersion) {
return Promise.resolve(`${packageToInstall}@${resultVersion}`);
} else if (validRange) {
return getMatchingVersion(version, tag, useStable)
.then(resultVersion => `${packageToInstall}@${resultVersion}`);
} else {
return Promise.resolve(version);
}
Expand Down Expand Up @@ -105,33 +136,9 @@ const isGlobalCliUsingYarn = function(projectDir) {
return fs.existsSync(path.join(projectDir, 'yarn.lock'));
};

const downloadFile = function (filename, url) {
return fsextra.ensureFile(filename).then(function () {
return new Promise(function (resolve, reject) {
request.get(url).pipe(fs.createWriteStream(filename)).on('close', resolve).on('error', function (err) {
reject(err);
});
});
});
};

const unzipFile = function (file, dir) {
return new Promise(function (resolve, reject) {
extract(file, { dir }, function (err) {
if (err) {
reject(err);
} else {
resolve();
}
});
});
}

module.exports = {
getInstallPackage,
getReactNativeVersion,
getReactNativeAppName,
isGlobalCliUsingYarn,
downloadFile,
unzipFile
isGlobalCliUsingYarn
};
74 changes: 14 additions & 60 deletions current/local-cli/rnpm/windows/src/windows.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,8 @@ const Common = require('./common');
const chalk = require('chalk');
const execSync = require('child_process').execSync;
const path = require('path');
const fs = require('fs');

const REACT_NATIVE_WINDOWS_GENERATE_PATH = function () {
const REACT_NATIVE_WINDOWS_GENERATE_PATH = function() {
return path.resolve(
process.cwd(),
'node_modules',
Expand All @@ -20,70 +19,25 @@ const REACT_NATIVE_WINDOWS_GENERATE_PATH = function () {
);
};

/**
* Returns latest version of react-native-windows where version contains 'vnext'
* @param {'yarn'|'npm'} pkgmgr
* @param {boolean} isYarn
*/
const getLatestVnextVersion = function (pkgmgr, isYarn) {
let response = JSON.parse(execSync(`${pkgmgr} info react-native-windows --silent --json`));
response = isYarn ? response.data : response;
const version = response["dist-tags"].vnext;
return version;
}

module.exports = function windows(config, args, options) {
module.exports = function (config, args, options) {
const name = args[0] ? args[0] : Common.getReactNativeAppName();
const ns = options.namespace ? options.namespace : name;
const template = options.template;
const execOptions = options.verbose ? { stdio: 'inherit' } : {};
const cwd = process.cwd();
const isYarn = Common.isGlobalCliUsingYarn(cwd);
const version = options.windowsVersion ? options.windowsVersion : Common.getReactNativeVersion();

if (template === 'vnext') {
const pkgmgr = isYarn ? 'yarn' : 'npm';
const version = options.windowsVersion ? options.windowsVersion : getLatestVnextVersion(pkgmgr, isYarn);
const projectPackageJson = path.join(cwd, 'package.json');
const reactNativeWindowsPackageJson = path.join(cwd, 'node_modules', 'react-native-windows', 'package.json');
// If the template is not set, look for a stable or 'rc' version
const template = options.template ? options.template : 'rc';
const ignoreStable = !!options.template;

console.log('Installing deps...');
return Common.getInstallPackage(version).then((rnwPackage) => {
return Common.getInstallPackage(version, template, ignoreStable)
.then(rnwPackage => {
console.log(`Installing ${rnwPackage}...`);
const pkgmgrInstall = isYarn ? `${pkgmgr} add` : `${pkgmgr} install --save`
execSync(`${pkgmgrInstall} ${rnwPackage}`, execOptions);
console.log(chalk.green(`${rnwPackage} successfully installed.`));
const pkgmgr = Common.isGlobalCliUsingYarn(process.cwd()) ? 'yarn add' : 'npm install --save';

const vnextPackageJson = JSON.parse(fs.readFileSync(reactNativeWindowsPackageJson, { encoding: 'UTF8' }));
let reactNativeVersion = vnextPackageJson.peerDependencies['react-native'];
const depDelim = ' || ';
const delimIndex = reactNativeVersion.indexOf(depDelim);
if (delimIndex !== -1) {
reactNativeVersion = reactNativeVersion.substring(delimIndex + depDelim.length);
}

// patching package.json to have proper react-native version and start script
const prjPackageJson = JSON.parse(fs.readFileSync(projectPackageJson, { encoding: 'UTF8' }));
prjPackageJson.scripts.start = 'node node_modules/react-native-windows/Scripts/cli.js start';
prjPackageJson.dependencies['react-native'] = reactNativeVersion;
fs.writeFileSync(projectPackageJson, JSON.stringify(prjPackageJson, null, 2));
execSync(isYarn ? pkgmgr : `${pkgmgr} i`, execOptions);
const execOptions = options.verbose ? { stdio: 'inherit' } : {};
execSync(`${pkgmgr} ${rnwPackage}`, execOptions);
console.log(chalk.green(`${rnwPackage} successfully installed.`));

const generateWindows = require(REACT_NATIVE_WINDOWS_GENERATE_PATH());
generateWindows(process.cwd(), name, ns);
});
}
else {
const version = options.windowsVersion ? options.windowsVersion : Common.getReactNativeVersion();
return Common.getInstallPackage(version)
.then(rnwPackage => {
console.log(`Installing ${rnwPackage}...`);
const pkgmgr = isYarn ? 'yarn add' : 'npm install --save';

execSync(`${pkgmgr} ${rnwPackage}`, execOptions);
console.log(chalk.green(`${rnwPackage} successfully installed.`));

const generateWindows = require(REACT_NATIVE_WINDOWS_GENERATE_PATH());
generateWindows(process.cwd(), name, ns);
}).catch(error => console.error(chalk.red(error.message)));
}
generateWindows(process.cwd(), name, ns, options);
}).catch(error => console.error(chalk.red(error.message)));
};
11 changes: 9 additions & 2 deletions vnext/local-cli/generate-windows.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,19 +2,26 @@

const fs = require('fs');
const path = require('path');
const copyProjectTemplateAndReplace = require('./generator-windows').copyProjectTemplateAndReplace;
const {
copyProjectTemplateAndReplace,
installDependencies,
} = require('./generator-windows');

/**
* Simple utility for running the Windows generator.
*
* @param {String} projectDir root project directory (i.e. contains index.js)
* @param {String} name name of the root JS module for this app
* @param {String} ns namespace for the project
* @param {Object} options command line options container
*/
function generateWindows (projectDir, name, ns) {
function generateWindows (projectDir, name, ns, options) {
if (!fs.existsSync(projectDir)) {
fs.mkdirSync(projectDir);
}

installDependencies(options);

copyProjectTemplateAndReplace(
path.join(__dirname, 'generator-windows', 'templates'),
projectDir,
Expand Down
33 changes: 32 additions & 1 deletion vnext/local-cli/generator-windows/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ const path = require('path');
const username = require('username');
const uuid = require('uuid');
const childProcess = require('child_process');
const fs = require('fs');
const os = require('os');
const {
createDir,
Expand Down Expand Up @@ -102,6 +103,36 @@ function copyProjectTemplateAndReplace(
console.log(chalk.white(' react-native run-windows'));
}

function installDependencies(options) {
const cwd = process.cwd();

// Extract react-native peer dependency version
const vnextPackageJsonPath = path.join(cwd, 'node_modules', 'react-native-windows', 'package.json');
const vnextPackageJson = JSON.parse(fs.readFileSync(vnextPackageJsonPath, { encoding: 'UTF8' }));
let reactNativeVersion = vnextPackageJson.peerDependencies['react-native'];
const depDelim = ' || ';
const delimIndex = reactNativeVersion.indexOf(depDelim);
if (delimIndex !== -1) {
reactNativeVersion = reactNativeVersion.substring(delimIndex + depDelim.length);
}

console.log(chalk.green('Updating to compatible version of react-native:'));
console.log(chalk.white(` ${reactNativeVersion}`));

// Patch package.json to have proper react-native version and install
const projectPackageJsonPath = path.join(cwd, 'package.json');
const projectPackageJson = JSON.parse(fs.readFileSync(projectPackageJsonPath, { encoding: 'UTF8' }));
projectPackageJson.scripts.start = 'node node_modules/react-native-windows/Scripts/cli.js start';
projectPackageJson.dependencies['react-native'] = reactNativeVersion;
fs.writeFileSync(projectPackageJsonPath, JSON.stringify(projectPackageJson, null, 2));

// Install dependencies using correct package manager
const isYarn = fs.existsSync(path.join(cwd, 'yarn.lock'));
const execOptions = options && options.verbose ? { stdio: 'inherit' } : {};
childProcess.execSync(isYarn ? `yarn` : `npm i`, execOptions);
}

module.exports = {
copyProjectTemplateAndReplace
copyProjectTemplateAndReplace,
installDependencies
};

0 comments on commit 4ff550e

Please sign in to comment.