Skip to content

Commit

Permalink
Teach React Native CLI about master and 0.61 versions (#4403)
Browse files Browse the repository at this point in the history
* Teach React Native CLI about master and 0.61 versions

Add special rules for the master template to rnpm-plugin-windows to allow 0.0.0 versions of React Native Windows to match to non 0.0.0 versions of React Native, while we're not caught up yet.

Do not force the "vnext" tag for 0.61 builds without a template. We also rewrite some of the fuzzy logic added in #2559, which was explicitly allowing prereleases to be matched when we didn't force a tag. We didn't run into this in 0.59 or 0.60 given forcing vnext ot legacy.

Part of the master logic was tested end-to-end in the 0.62 branch, but I plan to revalidate before checkin. Behavior was tested for different versions of RN by using the "--windowsVersion" flag to simulate different installed RN versions, and observing the package we try to install. Will verify this works as expected in 0.61-stable before checkin.

Will cherry-pick this into 0.61-stable and submit in the same change as the pblish change. This should also allow us to change master to 0.0.0 scheme shortly after publishing.

* Change files

* Fix default tag for 0.57 and below

* Fix formatting

* Add a comment explaining the overall version matching algorithm
  • Loading branch information
NickGerleman committed Mar 24, 2020
1 parent 6c0c6a4 commit 1a13b00
Show file tree
Hide file tree
Showing 4 changed files with 141 additions and 70 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
{
"type": "minor",
"comment": "Teach React Native CLI about master and 0.61 versions",
"packageName": "rnpm-plugin-windows",
"email": "ngerlem@microsoft.com",
"commit": "89e1071abb6ec12023ae422bda3e948f5cc5c2ea",
"dependentChangeType": "patch",
"date": "2020-03-23T22:44:43.124Z"
}
120 changes: 81 additions & 39 deletions packages/rnpm-plugin-windows/src/common.js
Original file line number Diff line number Diff line change
Expand Up @@ -41,61 +41,105 @@ function getLatestVersion() {

function isTagMatch(packageVersion, requestTag) {
const prerelease = semver.prerelease(packageVersion);
return prerelease && prerelease[0] === requestTag;
if (prerelease === null && !requestTag) {
return true;
} else {
return prerelease && prerelease[0] === requestTag;
}
}

function isVersionMatch(packageVersion, requestVersion, requestTag) {
if (semver.parse(packageVersion) === null) {
return false;
}

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) => {
if (err || !release) {
return getLatestVersion()
.then(latestVersion => {
reject(new Error(`Could not find react-native-windows@${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}.`)));
}
function getLatestMatchingVersion(versionRange, tag) {
return new Promise((resolve, reject) => {
// Ignore the version range of React Native if asking for master, since our
// RNW version may not align.
if (tag === 'master') {
npm.packages.release('react-native-windows', 'master', (err, rel) => {
if (err) {
reject(err);
} else {
resolve(rel[0].version);
}
});
} else {
npm.packages.releases('react-native-windows', (err, rels) => {
if (err) {
reject(err);
} else {
const matchingVersions = Object.keys(rels)
.filter(v => isVersionMatch(v, versionRange, tag))
.sort(semver.rcompare);

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}.*.`));
if (matchingVersions.length > 0) {
resolve(matchingVersions[0]);
} 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)}.*.`));
reject();
}
}
resolve(candidates[0]);
} else {
resolve(matchedVersion);
}
});
});
}
});
}

const getInstallPackage = function (version, tag, useStable) {
/**
* Matches a version range to a version of react-native-windows to install. This is a hairy process
* with a few different cases. Note that we will not run this at all if an exact version was given
* for --windowsVersion.
*
* - If no version was specified we are passed a range based on React Native version. E.g. "0.61.*".
* We will try to match this against available packages, but need special rules for prerelease
* tags since we use them for even stable releases for several versions. Because of that we cannot
* use the semver range directly.
*
* - If our tag is "master", we grab the latest master label builds, since our RN version may not
* correspond to our RNW version. This will change once we're aligned to Facebook's master builds.
*
* - Users can pass in a range to --windowsVersion and we will try to respect it according to above
* matching logic. We likely do not do the right thing here in many cases sine we do not query the
* registry for the range directly, instead using custom logic to also ensure tag matches.
*/
async function getMatchingVersion(versionRange, tag) {
const versionStr = tag === 'master' ? 'master' : versionRange;
console.log(`Checking for react-native-windows version matching ${versionStr}...`);

try {
return await getLatestMatchingVersion(versionRange, tag);
} catch (ex) {
const latestVersion = await getLatestVersion();
throw new Error(`Could not find react-native-windows@${versionStr}. ` +
`Latest version of react-native-windows is ${latestVersion}, try switching to ` +
`react-native@${semver.major(latestVersion)}.${semver.minor(latestVersion)}.*.`);
}
}

const isSupportedVersion = function(validVersion, validRange) {
// Allow 0.0.0-x master builds
if (validVersion) {
return semver.lt(validVersion, '0.0.0') || semver.gte(validVersion, '0.27.0');
} else if (validRange) {
return semver.gtr('0.0.0', validRange) || semver.ltr('0.27.0', validRange);
} else {
return false;
}
};

const getInstallPackage = function (version, tag) {
const packageToInstall = 'react-native-windows';
const validVersion = semver.valid(version);
const validRange = semver.validRange(version);
if ((validVersion && !semver.gtr(validVersion, '0.26.*')) ||
(!validVersion && validRange && semver.gtr('0.27.0', validRange))) {

if (!isSupportedVersion(validVersion, validRange)) {
console.error(
'Please upgrade react-native to ^0.27 or specify a --windowsVersion that is >=0.27.0'
);
Expand All @@ -104,11 +148,9 @@ const getInstallPackage = function (version, tag, useStable) {

if (validVersion) {
return Promise.resolve(`${packageToInstall}@${version}`);
} else if (validRange) {
return getMatchingVersion(version, tag, useStable)
.then(resultVersion => `${packageToInstall}@${resultVersion}`);
} else {
return Promise.resolve(version);
return getMatchingVersion(version, tag)
.then(resultVersion => `${packageToInstall}@${resultVersion}`);
}
};

Expand Down
79 changes: 50 additions & 29 deletions packages/rnpm-plugin-windows/src/windows.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,38 +21,58 @@ const REACT_NATIVE_WINDOWS_GENERATE_PATH = function() {
);
};

module.exports = async function (config, args, options) {
try {
const name = args[0] ? args[0] : Common.getReactNativeAppName();
const ns = options.namespace ? options.namespace : name;
let version = options.windowsVersion ? options.windowsVersion : Common.getReactNativeVersion();
async function getDefaultVersionTag(version) {
const validVersion = semver.valid(version);
const validRange = semver.validRange(version);
if (!validVersion && !validRange) {
console.error(chalk.red(`'${version}' is not a valid version`));
process.exit(1);
}

let template = options.template;
if (!template) {
const validVersion = semver.valid(version);
const validRange = semver.validRange(version);
// If the RN version is >= 0.60 we know they have to use vnext
if ((validVersion && semver.gte(validVersion, '0.60')) ||
(!validVersion && validRange && semver.ltr('0.59.1000', validRange))) {
template = 'vnext';
}
// 0.57 and below had stable untagged releases
if ((validVersion && semver.lt(validVersion, '0.58.0'))
|| (validRange && semver.gtr('0.58.0', validRange))) {
return null;
}

// If the RN version is >= 0.59 then we need to query which version the user wants
if (!template && ((validVersion && semver.gte(validVersion, '0.59.0')) ||
(!validVersion && validRange && semver.ltr('0.58.1000', validRange)))) {
template = (await prompts({
type: 'select',
name: 'template',
message: 'What version of react-native-windows would you like to install?',
choices: [
{ value: 'vnext', title: ' Latest - High performance react-native-windows built on a shared C++ core from facebook (supports C++ or C#).' },
{ value: 'legacy', title: ' Legacy - Older react-native-windows implementation - (C# only, react-native <= 0.59 only)' },
],
})).template;
}
}
// 0.58 went to RC (See #2559)
if ((validVersion && semver.lt(validVersion, '0.59.0'))
|| (validRange && semver.gtr('0.59.0', validRange))) {
return 'rc';
}

// 0.59 tags releases as "legacy" or "vnext"
if ((validVersion && semver.lt(validVersion, '0.60.0'))
|| (validRange && semver.gtr('0.60.0', validRange))) {
return (await prompts({
type: 'select',
name: 'template',
message: 'What version of react-native-windows would you like to install?',
choices: [
{ value: 'vnext', title: ' Latest - High performance react-native-windows built on a shared C++ core from facebook (supports C++ or C#).' },
{ value: 'legacy', title: ' Legacy - Older react-native-windows implementation - (C# only, react-native <= 0.59 only)' },
],
})).template;
}

// 0.60 releases all use the vnext tag
if ((validVersion && semver.lt(validVersion, '0.61.0'))
|| (validRange && semver.gtr('0.61.0', validRange))) {
return 'vnext';
}

// 0.61 and after don't tag stable releases
return null;
}

module.exports = async function (config, args, options) {
try {
const name = args[0] || Common.getReactNativeAppName();
const ns = options.namespace || name;
const version = options.windowsVersion || Common.getReactNativeVersion();
const versionTag = options.template || await getDefaultVersionTag(version);

let rnwPackage = await Common.getInstallPackage(version, template, !!template);
const rnwPackage = await Common.getInstallPackage(version, versionTag);

console.log(`Installing ${rnwPackage}...`);
const pkgmgr = Common.isGlobalCliUsingYarn(process.cwd()) ? 'yarn add' : 'npm install --save';
Expand All @@ -66,5 +86,6 @@ module.exports = async function (config, args, options) {
} catch (error) {
console.error(chalk.red(error.message));
console.error(error);
process.exit(1);
}
};
3 changes: 1 addition & 2 deletions packages/rnpm-plugin-windows/src/wpf.js
Original file line number Diff line number Diff line change
Expand Up @@ -26,9 +26,8 @@ module.exports = function (config, args, options) {

// If the template is not set, look for a stable or 'rc' version
const template = options.template ? options.template : 'rc';
const ignoreStable = !!options.template;

return Common.getInstallPackage(version, template, ignoreStable)
return Common.getInstallPackage(version, template)
.then(rnwPackage => {
console.log(`Installing ${rnwPackage}...`);
const pkgmgr = Common.isGlobalCliUsingYarn(process.cwd()) ? 'yarn add' : 'npm install --save';
Expand Down

0 comments on commit 1a13b00

Please sign in to comment.