react-native-code-push-diff
Version:
This library builds upon the foundational strengths of the react-native-code-push library, adding advanced functionality to precisely identify and manage differences between code-push builds.
207 lines (203 loc) • 9.94 kB
JavaScript
;
import * as fs from 'fs';
import * as path from 'path';
import * as semver from 'semver';
function isValidVersion(version) {
return !!semver.valid(version) || /^\d+\.\d+$/.test(version);
}
export function getReactNativeProjectAppVersion(command, projectName) {
if (command.platform === 'ios') {
return getIosAppVersion(command, projectName);
}
if (command.platform === 'android') {
return getAndroidAppVersion(command);
}
throw new Error(`Unsupported platform: ${command.platform}`);
}
function getAndroidAppVersion(command) {
let buildGradlePath = path.join('android', 'app');
if (command.gradleFile) {
buildGradlePath = command.gradleFile;
}
if (fs.lstatSync(buildGradlePath).isDirectory()) {
buildGradlePath = path.join(buildGradlePath, 'build.gradle');
}
if (fileDoesNotExistOrIsDirectory(buildGradlePath)) {
throw new Error(`Unable to find gradle file "${buildGradlePath}".`);
}
const g2js = require('gradle-to-js/lib/parser');
return g2js.parseFile(buildGradlePath).catch(() => {
throw new Error(`Unable to parse the "${buildGradlePath}" file. Please ensure it is a well-formed Gradle file.`);
}).then(buildGradle => {
let versionName = null;
// First 'if' statement was implemented as workaround for case
// when 'build.gradle' file contains several 'android' nodes.
// In this case 'buildGradle.android' prop represents array instead of object
// due to parsing issue in 'g2js.parseFile' method.
if (buildGradle.android instanceof Array) {
for (let i = 0; i < buildGradle.android.length; i++) {
const gradlePart = buildGradle.android[i];
if (gradlePart.defaultConfig && gradlePart.defaultConfig.versionName) {
versionName = gradlePart.defaultConfig.versionName;
break;
}
}
} else if (buildGradle.android && buildGradle.android.defaultConfig && buildGradle.android.defaultConfig.versionName) {
versionName = buildGradle.android.defaultConfig.versionName;
} else {
throw new Error(`The "${buildGradlePath}" file doesn't specify a value for the "android.defaultConfig.versionName" property.`);
}
if (typeof versionName !== 'string') {
throw new Error(`The "android.defaultConfig.versionName" property value in "${buildGradlePath}" is not a valid string. If this is expected, consider using the --targetBinaryVersion option to specify the value manually.`);
}
let appVersion = versionName.replace(/"/g, '').trim();
if (isValidVersion(appVersion)) {
// The versionName property is a valid semver string,
// so we can safely use that and move on.
console.log(`Using the target binary version value "${appVersion}" from "${buildGradlePath}".\n`);
return appVersion;
} else if (/^\d.*/.test(appVersion)) {
// The versionName property isn't a valid semver string,
// but it starts with a number, and therefore, it can't
// be a valid Gradle property reference.
throw new Error(`The "android.defaultConfig.versionName" property in the "${buildGradlePath}" file needs to specify a valid semver string, containing both a major and minor version (e.g. 1.3.2, 1.1).`);
}
// The version property isn't a valid semver string
// so we assume it is a reference to a property variable.
const propertyName = appVersion.replace('project.', '');
const propertiesFileName = 'gradle.properties';
const knownLocations = [path.join('android', 'app', propertiesFileName), path.join('android', propertiesFileName)];
// Search for gradle properties across all `gradle.properties` files
let propertiesFile;
for (let i = 0; i < knownLocations.length; i++) {
propertiesFile = knownLocations[i];
if (fileExists(propertiesFile)) {
const propertiesContent = fs.readFileSync(propertiesFile).toString();
try {
const properties = require('properties');
const parsedProperties = properties.parse(propertiesContent);
appVersion = parsedProperties[propertyName];
if (appVersion) {
break;
}
} catch (e) {
throw new Error(`Unable to parse "${propertiesFile}". Please ensure it is a well-formed properties file.`);
}
}
}
if (!appVersion) {
throw new Error(`No property named "${propertyName}" exists in the "${propertiesFile}" file.`);
}
if (!isValidVersion(appVersion)) {
throw new Error(`The "${propertyName}" property in the "${propertiesFile}" file needs to specify a valid semver string, containing both a major and minor version (e.g. 1.3.2, 1.1).`);
}
console.log(`Using the target binary version value "${appVersion}" from the "${propertyName}" key in the "${propertiesFile}" file.\n`);
return appVersion.toString();
});
}
function getIosAppVersion(command, projectName) {
const projectPackageJson = require(path.join(process.cwd(), 'package.json'));
if (!projectName && projectPackageJson) {
projectName = projectPackageJson.name;
}
let resolvedPlistFile = command.plistFile;
if (resolvedPlistFile) {
// If a plist file path is explicitly provided, then we don't
// need to attempt to "resolve" it within the well-known locations.
if (!fileExists(resolvedPlistFile)) {
throw new Error("The specified plist file doesn't exist. Please check that the provided path is correct.");
}
} else {
// Allow the plist prefix to be specified with or without a trailing
// separator character, but prescribe the use of a hyphen when omitted,
// since this is the most commonly used convetion for plist files.
if (command.plistFilePrefix && /.+[^-.]$/.test(command.plistFilePrefix)) {
command.plistFilePrefix += '-';
}
const iOSDirectory = 'ios';
const plistFileName = `${command.plistFilePrefix || ''}Info.plist`;
const knownLocations = [path.join(iOSDirectory, plistFileName)];
if (projectName) {
knownLocations.push(path.join(iOSDirectory, projectName, plistFileName));
}
resolvedPlistFile = knownLocations.find(fileExists);
if (!resolvedPlistFile) {
throw new Error(`Unable to find either of the following plist files in order to infer your app's binary version: "${knownLocations.join('", "')}". If your plist has a different name, or is located in a different directory, consider using either the "--plistFile" or "--plistFilePrefix" parameters to help inform the CLI how to find it.`);
}
}
const plistContents = fs.readFileSync(resolvedPlistFile).toString();
let parsedPlist;
try {
const plist = require('plist');
parsedPlist = plist.parse(plistContents);
} catch (e) {
throw new Error(`Unable to parse "${resolvedPlistFile}". Please ensure it is a well-formed plist file.`);
}
if (parsedPlist && parsedPlist.CFBundleShortVersionString) {
if (isValidVersion(parsedPlist.CFBundleShortVersionString)) {
console.log(`Using the target binary version value "${parsedPlist.CFBundleShortVersionString}" from "${resolvedPlistFile}".\n`);
return parsedPlist.CFBundleShortVersionString;
} else {
if (parsedPlist.CFBundleShortVersionString !== '$(MARKETING_VERSION)') {
throw new Error(`The "CFBundleShortVersionString" key in the "${resolvedPlistFile}" file needs to specify a valid semver string, containing both a major and minor version (e.g. 1.3.2, 1.1).`);
}
return getAppVersionFromXcodeProject(command, projectName);
}
} else {
throw new Error(`The "CFBundleShortVersionString" key doesn't exist within the "${resolvedPlistFile}" file.`);
}
}
function getAppVersionFromXcodeProject(command, projectName) {
const pbxprojFileName = 'project.pbxproj';
let resolvedPbxprojFile = command.xcodeProjectFile;
if (resolvedPbxprojFile) {
// If the xcode project file path is explicitly provided, then we don't
// need to attempt to "resolve" it within the well-known locations.
if (!resolvedPbxprojFile.endsWith(pbxprojFileName)) {
// Specify path to pbxproj file if the provided file path is an Xcode project file.
resolvedPbxprojFile = path.join(resolvedPbxprojFile, pbxprojFileName);
}
if (!fileExists(resolvedPbxprojFile)) {
throw new Error("The specified pbx project file doesn't exist. Please check that the provided path is correct.");
}
} else {
const iOSDirectory = 'ios';
const pbxprojKnownLocations = [path.join(iOSDirectory, pbxprojFileName)];
if (projectName) {
pbxprojKnownLocations.push(path.join(iOSDirectory, `${projectName}.xcodeproj`, pbxprojFileName));
}
resolvedPbxprojFile = pbxprojKnownLocations.find(fileExists);
if (!resolvedPbxprojFile) {
throw new Error(`Unable to find either of the following pbxproj files in order to infer your app's binary version: "${pbxprojKnownLocations.join('", "')}".`);
}
}
const xcode = require('xcode');
const xcodeProj = xcode.project(resolvedPbxprojFile).parseSync();
const marketingVersion = xcodeProj.getBuildProperty('MARKETING_VERSION', command.buildConfigurationName, command.xcodeTargetName);
if (!isValidVersion(marketingVersion)) {
throw new Error(`The "MARKETING_VERSION" key in the "${resolvedPbxprojFile}" file needs to specify a valid semver string, containing both a major and minor version (e.g. 1.3.2, 1.1).`);
}
console.log(`Using the target binary version value "${marketingVersion}" from "${resolvedPbxprojFile}".\n`);
return marketingVersion;
}
function isDirectory(file) {
return fs.statSync(file).isDirectory();
}
function fileDoesNotExistOrIsDirectory(file) {
try {
return isDirectory(file);
} catch (error) {
return true;
}
}
function fileExists(file) {
if (!file) {
return false;
}
try {
return fs.statSync(file).isFile();
} catch (e) {
return false;
}
}
//# sourceMappingURL=getReactNativeProjectAppVersion.js.map