rnpm-plugin-windows
Version:
rnpm plugin that generates a Windows template project
197 lines (176 loc) • 6.69 kB
JavaScript
/**
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License.
*/
;
const fs = require('fs');
const path = require('path');
const semver = require('semver');
const Registry = require('npm-registry');
const child_process = require('child_process');
const validUrl = require('valid-url');
let npmConfReg = child_process.execSync('npm config get registry').toString().trim();
let NPM_REGISTRY_URL = validUrl.is_uri(npmConfReg) ? npmConfReg : 'http://registry.npmjs.org';
const REACT_NATIVE_PACKAGE_JSON_PATH = function() {
return path.resolve(
process.cwd(),
'node_modules',
'react-native',
'package.json'
);
};
const npm = new Registry({registry: NPM_REGISTRY_URL});
function getLatestVersion() {
return new Promise(function (resolve, reject) {
npm.packages.release('react-native-windows', 'latest', (err, releases) => {
if (err) {
reject(err);
} else if (!releases || releases.length === 0) {
reject(new Error('Could not find react-native-windows@latest.'));
} else {
resolve(releases[0].version);
}
});
});
}
function isTagMatch(packageVersion, requestTag) {
const prerelease = semver.prerelease(packageVersion);
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 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);
if (matchingVersions.length > 0) {
resolve(matchingVersions[0]);
} else {
reject();
}
}
});
}
});
}
/**
* 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 (!isSupportedVersion(validVersion, validRange)) {
console.error(
'Please upgrade react-native to ^0.27 or specify a --windowsVersion that is >=0.27.0'
);
process.exit(1);
}
if (validVersion) {
return Promise.resolve(`${packageToInstall}@${version}`);
} else {
return getMatchingVersion(version, tag)
.then(resultVersion => `${packageToInstall}@${resultVersion}`);
}
};
const getReactNativeVersion = function () {
console.log('Reading react-native version from node_modules...');
if (fs.existsSync(REACT_NATIVE_PACKAGE_JSON_PATH())) {
const version = JSON.parse(fs.readFileSync(REACT_NATIVE_PACKAGE_JSON_PATH(), 'utf-8')).version;
return `${semver.major(version)}.${semver.minor(version)}.*`;
}
};
const getReactNativeAppName = function () {
console.log('Reading application name from package.json...');
let name = JSON.parse(fs.readFileSync('package.json', 'utf8')).name;
if (!name) {
if (fs.existsSync('app.json')) {
console.log('Reading application name from app.json...');
name = JSON.parse(fs.readFileSync('app.json', 'utf8')).name;
}
}
if (!name) {
console.error('Please specify name in package.json or app.json');
}
return name;
};
/**
* Check that 'react-native init' itself used yarn to install React Native.
* When using an old global react-native-cli@1.0.0 (or older), we don't want
* to install React Native with npm, and React + Jest with yarn.
* Let's be safe and not mix yarn and npm in a single project.
* @param projectDir e.g. /Users/martin/AwesomeApp
*/
const isGlobalCliUsingYarn = function(projectDir) {
return fs.existsSync(path.join(projectDir, 'yarn.lock'));
};
module.exports = {
getInstallPackage,
getReactNativeVersion,
getReactNativeAppName,
isGlobalCliUsingYarn,
};