microsoft/vscode-react-native

Public

mirrored from https://github.com/microsoft/vscode-react-nativeAvailable

CodeCommitsIssuesPull requestsActionsInsightsSecurity
b035ec713060b735b1c4b0da76caa459f98bcd38

Branches

Tags

  • No tags available.
0Branches0Tags
Go to file
Add file
Code

Clone

HTTPS

Download ZIP

src/extension/appcenter/lib/codepush-node-sdk/dist/react-native/react-native-utils.js

326lines · modecode

1"use strict";
2var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
3 return new (P || (P = Promise))(function (resolve, reject) {
4 function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
5 function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
6 function step(result) { result.done ? resolve(result.value) : new P(function (resolve) { resolve(result.value); }).then(fulfilled, rejected); }
7 step((generator = generator.apply(thisArg, _arguments || [])).next());
8 });
9};
10Object.defineProperty(exports, "__esModule", { value: true });
11const fs = require("fs");
12const path = require("path");
13const chalk_1 = require("chalk");
14const xml2js = require("xml2js");
15const mkdirp = require("mkdirp");
16const plist = require('plist');
17const g2js = require('gradle-to-js/lib/parser');
18const properties = require('properties');
19const childProcess = require('child_process');
20const validation_utils_1 = require("../utils/validation-utils");
21const fileUtils = require("../utils/file-utils");
22exports.spawn = childProcess.spawn;
23function getAndroidAppVersion(projectRoot, gradleFile) {
24 return __awaiter(this, void 0, void 0, function* () {
25 projectRoot = projectRoot || process.cwd();
26 const projectPackageJson = require(path.join(projectRoot, 'package.json'));
27 const projectName = projectPackageJson.name;
28 console.log(chalk_1.default.cyan(`Detecting "Android" app version:\n`));
29 let buildGradlePath = path.join(projectRoot, 'android', 'app');
30 if (gradleFile) {
31 buildGradlePath = gradleFile;
32 }
33 if (fs.lstatSync(buildGradlePath).isDirectory()) {
34 buildGradlePath = path.join(buildGradlePath, 'build.gradle');
35 }
36 if (fileUtils.fileDoesNotExistOrIsDirectory(buildGradlePath)) {
37 throw new Error(`Unable to find gradle file "${buildGradlePath}".`);
38 }
39 return g2js.parseFile(buildGradlePath)
40 .catch(() => {
41 throw new Error(`Unable to parse the "${buildGradlePath}" file. Please ensure it is a well-formed Gradle file.`);
42 })
43 .then((buildGradle) => {
44 let versionName = null;
45 // First 'if' statement was implemented as workaround for case
46 // when 'build.gradle' file contains several 'android' nodes.
47 // In this case 'buildGradle.android' prop represents array instead of object
48 // due to parsing issue in 'g2js.parseFile' method.
49 if (buildGradle.android instanceof Array) {
50 for (let i = 0; i < buildGradle.android.length; i++) {
51 const gradlePart = buildGradle.android[i];
52 if (gradlePart.defaultConfig && gradlePart.defaultConfig.versionName) {
53 versionName = gradlePart.defaultConfig.versionName;
54 break;
55 }
56 }
57 }
58 else if (buildGradle.android && buildGradle.android.defaultConfig && buildGradle.android.defaultConfig.versionName) {
59 versionName = buildGradle.android.defaultConfig.versionName;
60 }
61 else {
62 throw new Error(`The "${buildGradlePath}" file doesn't specify a value for the "android.defaultConfig.versionName" property.`);
63 }
64 if (typeof versionName !== 'string') {
65 throw new Error(`The "android.defaultConfig.versionName" property value in "${buildGradlePath}" is not a valid string. If this is expected, consider using the --target-binary-version option to specify the value manually.`);
66 }
67 let appVersion = versionName.replace(/"/g, '').trim();
68 if (validation_utils_1.isValidVersion(appVersion)) {
69 // The versionName property is a valid semver string,
70 // so we can safely use that and move on.
71 console.log(`Using the target binary version value "${appVersion}" from "${buildGradlePath}".\n`);
72 return appVersion;
73 }
74 // The version property isn't a valid semver string
75 // so we assume it is a reference to a property variable.
76 const propertyName = appVersion.replace('project.', '');
77 const propertiesFileName = 'gradle.properties';
78 const knownLocations = [
79 path.join(projectRoot, 'android', 'app', propertiesFileName),
80 path.join(projectRoot, 'android', propertiesFileName)
81 ];
82 // Search for gradle properties across all `gradle.properties` files
83 let propertiesFile = null;
84 for (let i = 0; i < knownLocations.length; i++) {
85 propertiesFile = knownLocations[i];
86 if (fileUtils.fileExists(propertiesFile)) {
87 const propertiesContent = fs.readFileSync(propertiesFile).toString();
88 try {
89 const parsedProperties = properties.parse(propertiesContent);
90 appVersion = parsedProperties[propertyName];
91 if (appVersion) {
92 break;
93 }
94 }
95 catch (e) {
96 throw new Error(`Unable to parse "${propertiesFile}". Please ensure it is a well-formed properties file.`);
97 }
98 }
99 }
100 if (!appVersion) {
101 throw new Error(`No property named "${propertyName}" exists in the "${propertiesFile}" file.`);
102 }
103 if (!validation_utils_1.isValidVersion(appVersion)) {
104 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).`);
105 }
106 console.log(`Using the target binary version value "${appVersion}" from the "${propertyName}" key in the "${propertiesFile}" file.\n`);
107 return appVersion.toString();
108 });
109 });
110}
111exports.getAndroidAppVersion = getAndroidAppVersion;
112function getiOSAppVersion(projectRoot, plistFilePrefix, plistFile) {
113 return __awaiter(this, void 0, void 0, function* () {
114 projectRoot = projectRoot || process.cwd();
115 const projectPackageJson = require(path.join(projectRoot, 'package.json'));
116 const projectName = projectPackageJson.name;
117 console.log(chalk_1.default.cyan(`Detecting "iOS" app version:\n`));
118 let resolvedPlistFile = plistFile;
119 if (resolvedPlistFile) {
120 // If a plist file path is explicitly provided, then we don't
121 // need to attempt to "resolve" it within the well-known locations.
122 if (!fileUtils.fileExists(resolvedPlistFile)) {
123 throw new Error(`The specified plist file doesn't exist. Please check that the provided path is correct.`);
124 }
125 }
126 else {
127 // Allow the plist prefix to be specified with or without a trailing
128 // separator character, but prescribe the use of a hyphen when omitted,
129 // since this is the most commonly used convetion for plist files.
130 if (plistFilePrefix && /.+[^-.]$/.test(plistFilePrefix)) {
131 plistFilePrefix += '-';
132 }
133 const iOSDirectory = 'ios';
134 const plistFileName = `${plistFilePrefix || ''}Info.plist`;
135 const knownLocations = [
136 path.join(projectRoot, iOSDirectory, projectName, plistFileName),
137 path.join(projectRoot, iOSDirectory, plistFileName)
138 ];
139 resolvedPlistFile = knownLocations.find(fileUtils.fileExists);
140 if (!resolvedPlistFile) {
141 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 "--plist-file" or "--plist-file-prefix" parameters to help inform the CLI how to find it.`);
142 }
143 }
144 const plistContents = fs.readFileSync(resolvedPlistFile).toString();
145 let parsedPlist;
146 try {
147 parsedPlist = plist.parse(plistContents);
148 }
149 catch (e) {
150 throw new Error(`Unable to parse "${resolvedPlistFile}". Please ensure it is a well-formed plist file.`);
151 }
152 if (parsedPlist && parsedPlist.CFBundleShortVersionString) {
153 if (validation_utils_1.isValidVersion(parsedPlist.CFBundleShortVersionString)) {
154 console.log(`Using the target binary version value "${parsedPlist.CFBundleShortVersionString}" from "${resolvedPlistFile}".\n`);
155 return Promise.resolve(parsedPlist.CFBundleShortVersionString);
156 }
157 else {
158 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).`);
159 }
160 }
161 else {
162 throw new Error(`The "CFBundleShortVersionString" key doesn't exist within the "${resolvedPlistFile}" file.`);
163 }
164 });
165}
166exports.getiOSAppVersion = getiOSAppVersion;
167function getWindowsAppVersion(projectRoot) {
168 return __awaiter(this, void 0, void 0, function* () {
169 projectRoot = projectRoot || process.cwd();
170 const projectPackageJson = require(path.join(projectRoot, 'package.json'));
171 const projectName = projectPackageJson.name;
172 console.log(chalk_1.default.cyan(`Detecting "Windows" app version:\n`));
173 const appxManifestFileName = 'Package.appxmanifest';
174 let appxManifestContainingFolder;
175 let appxManifestContents;
176 try {
177 appxManifestContainingFolder = path.join(projectRoot, 'windows', projectName);
178 appxManifestContents = fs.readFileSync(path.join(appxManifestContainingFolder, appxManifestFileName)).toString();
179 }
180 catch (err) {
181 throw new Error(`Unable to find or read "${appxManifestFileName}" in the "${path.join('windows', projectName)}" folder.`);
182 }
183 return new Promise((resolve, reject) => {
184 xml2js.parseString(appxManifestContents, (err, parsedAppxManifest) => {
185 if (err) {
186 reject(new Error(`Unable to parse the "${path.join(appxManifestContainingFolder, appxManifestFileName)}" file, it could be malformed.`));
187 return;
188 }
189 try {
190 const appVersion = parsedAppxManifest.Package.Identity[0]['$'].Version.match(/^\d+\.\d+\.\d+/)[0];
191 console.log(`Using the target binary version value "${appVersion}" from the "Identity" key in the "${appxManifestFileName}" file.\n`);
192 return resolve(appVersion);
193 }
194 catch (e) {
195 reject(new Error(`Unable to parse the package version from the "${path.join(appxManifestContainingFolder, appxManifestFileName)}" file.`));
196 return;
197 }
198 });
199 });
200 });
201}
202exports.getWindowsAppVersion = getWindowsAppVersion;
203function runReactNativeBundleCommand(projectRootPath, bundleName, development, entryFile, outputFolder, platform, sourcemapOutput) {
204 const reactNativeBundleArgs = [];
205 const envNodeArgs = process.env.CODE_PUSH_NODE_ARGS;
206 if (typeof envNodeArgs !== 'undefined') {
207 Array.prototype.push.apply(reactNativeBundleArgs, envNodeArgs.trim().split(/\s+/));
208 }
209 Array.prototype.push.apply(reactNativeBundleArgs, [
210 path.join(projectRootPath, 'node_modules', 'react-native', 'local-cli', 'cli.js'), 'bundle',
211 '--assets-dest', outputFolder,
212 '--bundle-output', path.join(outputFolder, bundleName),
213 '--dev', development,
214 '--entry-file', entryFile,
215 '--platform', platform,
216 ]);
217 if (sourcemapOutput) {
218 reactNativeBundleArgs.push('--sourcemap-output', sourcemapOutput);
219 }
220 console.log(chalk_1.default.cyan(`Running "react-native bundle" command:\n`));
221 const reactNativeBundleProcess = exports.spawn('node', reactNativeBundleArgs);
222 console.log(`node ${reactNativeBundleArgs.join(' ')}`);
223 return new Promise((resolve, reject) => {
224 reactNativeBundleProcess.stdout.on('data', (data) => {
225 console.log(data.toString().trim());
226 });
227 reactNativeBundleProcess.stderr.on('data', (data) => {
228 console.error(data.toString().trim());
229 });
230 reactNativeBundleProcess.on('close', (exitCode) => {
231 if (exitCode) {
232 reject(new Error(`"react-native bundle" command exited with code ${exitCode}.`));
233 }
234 resolve(null);
235 });
236 });
237}
238exports.runReactNativeBundleCommand = runReactNativeBundleCommand;
239function isValidOS(os) {
240 switch (os.toLowerCase()) {
241 case 'android':
242 case 'ios':
243 case 'windows':
244 return true;
245 default:
246 return false;
247 }
248}
249exports.isValidOS = isValidOS;
250function isValidPlatform(platform) {
251 return platform.toLowerCase() === 'react-native';
252}
253exports.isValidPlatform = isValidPlatform;
254function isReactNativeProject() {
255 try {
256 const projectPackageJson = require(path.join(process.cwd(), 'package.json'));
257 const projectName = projectPackageJson.name;
258 if (!projectName) {
259 throw new Error(`The "package.json" file in the CWD does not have the "name" field set.`);
260 }
261 return projectPackageJson.dependencies['react-native'] || (projectPackageJson.devDependencies && projectPackageJson.devDependencies['react-native']);
262 }
263 catch (error) {
264 throw new Error(`Unable to find or read "package.json" in the CWD. The "release-react" command must be executed in a React Native project folder.`);
265 }
266}
267exports.isReactNativeProject = isReactNativeProject;
268function getDefaultBundleName(os) {
269 if (!isValidOS(os)) {
270 throw new Error(`Platform must be either "ios" or "android".`);
271 }
272 return os === 'ios'
273 ? 'main.jsbundle'
274 : `index.${os}.bundle`;
275}
276exports.getDefaultBundleName = getDefaultBundleName;
277function getDefautEntryFilePath(os, projectDir) {
278 if (!isValidOS(os)) {
279 throw new Error(`Platform must be either "ios" or "android".`);
280 }
281 if (!projectDir) {
282 projectDir = process.cwd();
283 }
284 let entryFilePath = path.join(projectDir, `index.${os}.js`);
285 if (fileUtils.fileDoesNotExistOrIsDirectory(path.join(projectDir, entryFilePath))) {
286 entryFilePath = path.join(projectDir, 'index.js');
287 }
288 if (fileUtils.fileDoesNotExistOrIsDirectory(entryFilePath)) {
289 throw new Error(`Entry file "index.${os}.js" or "index.js" does not exist.`);
290 }
291 return entryFilePath;
292}
293exports.getDefautEntryFilePath = getDefautEntryFilePath;
294class BundleConfig {
295}
296exports.BundleConfig = BundleConfig;
297function makeUpdateContents(bundleConfig) {
298 return __awaiter(this, void 0, void 0, function* () {
299 if (!isValidOS(bundleConfig.os)) {
300 throw new Error(`Platform must be either "ios" or "android".`);
301 }
302 if (!bundleConfig.projectRootPath) {
303 bundleConfig.projectRootPath = process.cwd();
304 }
305 let updateContentsPath;
306 updateContentsPath = bundleConfig.outputDir || (yield fileUtils.mkTempDir('code-push'));
307 // we have to add "CodePush" root folder to make update contents file structure
308 // to be compatible with React Native client SDK
309 updateContentsPath = path.join(updateContentsPath, 'CodePush');
310 mkdirp.sync(updateContentsPath);
311 if (!bundleConfig.bundleName) {
312 bundleConfig.bundleName = getDefaultBundleName(bundleConfig.os);
313 }
314 if (!bundleConfig.entryFilePath) {
315 bundleConfig.entryFilePath = getDefautEntryFilePath(bundleConfig.os, bundleConfig.projectRootPath);
316 }
317 if (bundleConfig.outputDir) {
318 bundleConfig.sourcemapOutput = path.join(updateContentsPath, bundleConfig.bundleName + '.map');
319 }
320 fileUtils.createEmptyTmpReleaseFolder(updateContentsPath);
321 fileUtils.removeReactTmpDir();
322 yield runReactNativeBundleCommand(bundleConfig.projectRootPath, bundleConfig.bundleName, bundleConfig.development, bundleConfig.entryFilePath, updateContentsPath, bundleConfig.os, bundleConfig.sourcemapOutput);
323 return updateContentsPath;
324 });
325}
326exports.makeUpdateContents = makeUpdateContents;
327