@revopush/code-push-cli 0.0.5 → 0.0.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,35 +1,17 @@
1
1
  // Copyright (c) Microsoft Corporation.
2
2
  // Licensed under the MIT License.
3
3
 
4
- import AccountManager = require("./management-sdk");
5
-
6
4
  const childProcess = require("child_process");
7
5
  import debugCommand from "./commands/debug";
8
6
  import * as fs from "fs";
9
7
  import * as chalk from "chalk";
10
-
11
- const g2js = require("gradle-to-js/lib/parser");
12
8
  import * as moment from "moment";
13
-
14
- const opener = require("opener");
15
9
  import * as os from "os";
16
10
  import * as path from "path";
17
-
18
- const plist = require("plist");
19
- const progress = require("progress");
20
- const prompt = require("prompt");
21
11
  import * as Q from "q";
22
-
23
- const rimraf = require("rimraf");
24
12
  import * as semver from "semver";
25
-
26
- const Table = require("cli-table");
27
- const which = require("which");
28
- import wordwrap = require("wordwrap");
29
13
  import * as cli from "../script/types/cli";
30
14
  import sign from "./sign";
31
-
32
- const xcode = require("xcode");
33
15
  import {
34
16
  AccessKey,
35
17
  Account,
@@ -45,14 +27,38 @@ import {
45
27
  Session,
46
28
  UpdateMetrics,
47
29
  } from "../script/types";
48
- import { getAndroidHermesEnabled, getiOSHermesEnabled, runHermesEmitBinaryCommand, isValidVersion } from "./react-native-utils";
49
- import { fileDoesNotExistOrIsDirectory, isBinaryOrZip, fileExists } from "./utils/file-utils";
30
+ import {
31
+ getBundleSourceMapOutput,
32
+ getMinifyParams,
33
+ getReactNativePackagePath,
34
+ isHermesEnabled,
35
+ isValidVersion,
36
+ runHermesEmitBinaryCommand,
37
+ } from "./react-native-utils";
38
+ import { fileDoesNotExistOrIsDirectory, fileExists, isBinaryOrZip } from "./utils/file-utils";
39
+
40
+ import AccountManager = require("./management-sdk");
41
+ import wordwrap = require("wordwrap");
42
+ import Promise = Q.Promise;
43
+
44
+ const g2js = require("gradle-to-js/lib/parser");
45
+
46
+ const opener = require("opener");
47
+
48
+ const plist = require("plist");
49
+ const progress = require("progress");
50
+ const prompt = require("prompt");
51
+
52
+ const rimraf = require("rimraf");
53
+
54
+ const Table = require("cli-table");
55
+
56
+ const xcode = require("xcode");
50
57
 
51
58
  const configFilePath: string = path.join(process.env.LOCALAPPDATA || process.env.HOME, ".revopush.config");
52
59
  const emailValidator = require("email-validator");
53
60
  const packageJson = require("../../package.json");
54
61
  const parseXml = Q.denodeify(require("xml2js").parseString);
55
- import Promise = Q.Promise;
56
62
 
57
63
  const properties = require("properties");
58
64
 
@@ -1259,6 +1265,7 @@ export const releaseReact = (command: cli.IReleaseReactCommand): Promise<void> =
1259
1265
  let bundleName: string = command.bundleName;
1260
1266
  let entryFile: string = command.entryFile;
1261
1267
  const outputFolder: string = command.outputDir || path.join(os.tmpdir(), "CodePush");
1268
+ const sourcemapOutputFolder: string = command.sourcemapOutput || path.join(os.tmpdir(), "CodePushSourceMap");
1262
1269
  const platform: string = (command.platform = command.platform.toLowerCase());
1263
1270
  const releaseCommand: cli.IReleaseCommand = <any>command;
1264
1271
  // Check for app and deployment exist before releasing an update.
@@ -1266,7 +1273,7 @@ export const releaseReact = (command: cli.IReleaseReactCommand): Promise<void> =
1266
1273
  return (
1267
1274
  sdk
1268
1275
  .getDeployment(command.appName, command.deploymentName)
1269
- .then((): any => {
1276
+ .then(async () => {
1270
1277
  releaseCommand.package = outputFolder;
1271
1278
 
1272
1279
  switch (platform) {
@@ -1319,8 +1326,9 @@ export const releaseReact = (command: cli.IReleaseReactCommand): Promise<void> =
1319
1326
  ? Q(command.appStoreVersion)
1320
1327
  : getReactNativeProjectAppVersion(command, projectName);
1321
1328
 
1322
- if (command.sourcemapOutput && !command.sourcemapOutput.endsWith(".map")) {
1323
- command.sourcemapOutput = path.join(command.sourcemapOutput, bundleName + ".map");
1329
+ if (!sourcemapOutputFolder.endsWith(".map") && !command.sourcemapOutput) {
1330
+ // create tmp dir only if no dir was given by user. User must crete a directory if --sourcemapOutput is passes
1331
+ await createEmptyTempReleaseFolder(sourcemapOutputFolder);
1324
1332
  }
1325
1333
 
1326
1334
  return appVersionPromise;
@@ -1334,29 +1342,28 @@ export const releaseReact = (command: cli.IReleaseReactCommand): Promise<void> =
1334
1342
  // This is needed to clear the react native bundler cache:
1335
1343
  // https://github.com/facebook/react-native/issues/4289
1336
1344
  .then(() => deleteFolder(`${os.tmpdir()}/react-*`))
1337
- .then(() =>
1338
- runReactNativeBundleCommand(
1345
+ .then(async () => {
1346
+ await runReactNativeBundleCommand(
1347
+ command,
1339
1348
  bundleName,
1340
1349
  command.development || false,
1341
1350
  entryFile,
1342
1351
  outputFolder,
1352
+ sourcemapOutputFolder,
1343
1353
  platform,
1344
- command.sourcemapOutput,
1345
1354
  command.extraBundlerOptions
1346
- )
1347
- )
1355
+ );
1356
+ })
1348
1357
  .then(async () => {
1349
- const isHermesEnabled =
1350
- command.useHermes ||
1351
- (platform === "android" && (await getAndroidHermesEnabled(command.gradleFile))) || // Check if we have to run hermes to compile JS to Byte Code if Hermes is enabled in build.gradle and we're releasing an Android build
1352
- (platform === "ios" && (await getiOSHermesEnabled(command.podFile))); // Check if we have to run hermes to compile JS to Byte Code if Hermes is enabled in Podfile and we're releasing an iOS build
1358
+ const isHermes = await isHermesEnabled(command, platform);
1353
1359
 
1354
- if (isHermesEnabled) {
1360
+ if (isHermes) {
1355
1361
  log(chalk.cyan("\nRunning hermes compiler...\n"));
1356
1362
  await runHermesEmitBinaryCommand(
1363
+ command,
1357
1364
  bundleName,
1358
1365
  outputFolder,
1359
- command.sourcemapOutput,
1366
+ sourcemapOutputFolder,
1360
1367
  command.extraHermesFlags,
1361
1368
  command.gradleFile
1362
1369
  );
@@ -1374,13 +1381,16 @@ export const releaseReact = (command: cli.IReleaseReactCommand): Promise<void> =
1374
1381
  log(chalk.cyan("\nReleasing update contents to CodePush:\n"));
1375
1382
  return release(releaseCommand);
1376
1383
  })
1377
- .then(() => {
1384
+ .then(async () => {
1378
1385
  if (!command.outputDir) {
1379
- deleteFolder(outputFolder);
1386
+ await deleteFolder(outputFolder);
1387
+ }
1388
+
1389
+ if (!command.sourcemapOutput) {
1390
+ await deleteFolder(sourcemapOutputFolder);
1380
1391
  }
1381
1392
  })
1382
- .catch((err: Error) => {
1383
- deleteFolder(outputFolder);
1393
+ .catch(async (err: Error) => {
1384
1394
  throw err;
1385
1395
  })
1386
1396
  );
@@ -1427,15 +1437,16 @@ function requestAccessKey(): Promise<string> {
1427
1437
  });
1428
1438
  }
1429
1439
 
1430
- export const runReactNativeBundleCommand = (
1440
+ export const runReactNativeBundleCommand = async (
1441
+ command: cli.IReleaseReactCommand,
1431
1442
  bundleName: string,
1432
1443
  development: boolean,
1433
1444
  entryFile: string,
1434
1445
  outputFolder: string,
1446
+ sourcemapOutputFolder: string,
1435
1447
  platform: string,
1436
- sourcemapOutput: string,
1437
1448
  extraBundlerOptions: string[]
1438
- ): Promise<void> => {
1449
+ ) => {
1439
1450
  const reactNativeBundleArgs: string[] = [];
1440
1451
  const envNodeArgs: string = process.env.CODE_PUSH_NODE_ARGS;
1441
1452
 
@@ -1443,10 +1454,12 @@ export const runReactNativeBundleCommand = (
1443
1454
  Array.prototype.push.apply(reactNativeBundleArgs, envNodeArgs.trim().split(/\s+/));
1444
1455
  }
1445
1456
 
1446
- const isOldCLI = fs.existsSync(path.join("node_modules", "react-native", "local-cli", "cli.js"));
1457
+ const reactNativePackagePath = getReactNativePackagePath();
1458
+ const oldCliPath = path.join(reactNativePackagePath, "local-cli", "cli.js");
1459
+ const cliPath = fs.existsSync(oldCliPath) ? oldCliPath : path.join(reactNativePackagePath, "cli.js");
1447
1460
 
1448
1461
  Array.prototype.push.apply(reactNativeBundleArgs, [
1449
- isOldCLI ? path.join("node_modules", "react-native", "local-cli", "cli.js") : path.join("node_modules", "react-native", "cli.js"),
1462
+ cliPath,
1450
1463
  "bundle",
1451
1464
  "--assets-dest",
1452
1465
  outputFolder,
@@ -1458,12 +1471,22 @@ export const runReactNativeBundleCommand = (
1458
1471
  entryFile,
1459
1472
  "--platform",
1460
1473
  platform,
1474
+ "--reset-cache",
1461
1475
  ]);
1462
1476
 
1463
- if (sourcemapOutput) {
1464
- reactNativeBundleArgs.push("--sourcemap-output", sourcemapOutput);
1477
+ if (sourcemapOutputFolder) {
1478
+ let bundleSourceMapOutput = sourcemapOutputFolder;
1479
+ if (!sourcemapOutputFolder.endsWith(".map")) {
1480
+ // user defined full path to source map. let's use that instead
1481
+ bundleSourceMapOutput = await getBundleSourceMapOutput(command, bundleName, sourcemapOutputFolder);
1482
+ }
1483
+
1484
+ reactNativeBundleArgs.push("--sourcemap-output", bundleSourceMapOutput);
1465
1485
  }
1466
1486
 
1487
+ const minifyValue = await getMinifyParams(command);
1488
+ Array.prototype.push.apply(reactNativeBundleArgs, minifyValue);
1489
+
1467
1490
  if (extraBundlerOptions.length > 0) {
1468
1491
  reactNativeBundleArgs.push(...extraBundlerOptions);
1469
1492
  }
@@ -4,6 +4,9 @@ import * as path from "path";
4
4
  import * as childProcess from "child_process";
5
5
  import { coerce, compare, valid } from "semver";
6
6
  import { fileDoesNotExistOrIsDirectory } from "./utils/file-utils";
7
+ import * as dotenv from "dotenv";
8
+ import { DotenvParseOutput } from "dotenv";
9
+ import * as cli from "../script/types/cli";
7
10
 
8
11
  const g2js = require("gradle-to-js/lib/parser");
9
12
 
@@ -11,12 +14,46 @@ export function isValidVersion(version: string): boolean {
11
14
  return !!valid(version) || /^\d+\.\d+$/.test(version);
12
15
  }
13
16
 
17
+ export async function getBundleSourceMapOutput(command: cli.IReleaseReactCommand, bundleName: string, sourcemapOutputFolder: string) {
18
+ let bundleSourceMapOutput: string | undefined;
19
+ switch (command.platform) {
20
+ case "android": {
21
+ // see BundleHermesCTask -> resolvePackagerSourceMapFile
22
+ // for Hermes targeted bundles there are 2 source maps: "packager" (metro) and "compiler" (Hermes)
23
+ // Metro bundles use <bundleAssetName>.packager.map notation
24
+ const isHermes = await isHermesEnabled(command, command.platform);
25
+ if (isHermes) {
26
+ bundleSourceMapOutput = path.join(sourcemapOutputFolder, bundleName + ".packager.map");
27
+ } else {
28
+ bundleSourceMapOutput = path.join(sourcemapOutputFolder, bundleName + ".map");
29
+ }
30
+
31
+ break;
32
+ }
33
+ case "ios": {
34
+ // see react-native-xcode.sh
35
+ // to match js bundle generated by Xcode and by Revopush cli we must respect SOURCEMAP_FILE value
36
+ // because it appears as //# sourceMappingURL value in a js bundle
37
+ const xcodeDotEnvValue = getXcodeDotEnvValue("SOURCEMAP_FILE");
38
+ const sourceMapFilename = xcodeDotEnvValue ? path.basename(xcodeDotEnvValue) : bundleName + ".map";
39
+
40
+ bundleSourceMapOutput = path.join(sourcemapOutputFolder, sourceMapFilename);
41
+
42
+ break;
43
+ }
44
+ default:
45
+ throw new Error('Platform must be either "android", "ios" or "windows".');
46
+ }
47
+ return bundleSourceMapOutput;
48
+ }
49
+
14
50
  export async function runHermesEmitBinaryCommand(
15
- bundleName: string,
16
- outputFolder: string,
17
- sourcemapOutput: string,
18
- extraHermesFlags: string[],
19
- gradleFile: string
51
+ command: cli.IReleaseReactCommand,
52
+ bundleName: string,
53
+ outputFolder: string,
54
+ sourcemapOutputFolder: string,
55
+ extraHermesFlags: string[],
56
+ gradleFile: string
20
57
  ): Promise<void> {
21
58
  const hermesArgs: string[] = [];
22
59
  const envNodeArgs: string = process.env.CODE_PUSH_NODE_ARGS;
@@ -27,13 +64,14 @@ export async function runHermesEmitBinaryCommand(
27
64
 
28
65
  Array.prototype.push.apply(hermesArgs, [
29
66
  "-emit-binary",
67
+ "-O",
30
68
  "-out",
31
69
  path.join(outputFolder, bundleName + ".hbc"),
32
70
  path.join(outputFolder, bundleName),
33
71
  ...extraHermesFlags,
34
72
  ]);
35
73
 
36
- if (sourcemapOutput) {
74
+ if (sourcemapOutputFolder) {
37
75
  hermesArgs.push("-output-source-map");
38
76
  }
39
77
 
@@ -72,8 +110,8 @@ export async function runHermesEmitBinaryCommand(
72
110
  });
73
111
  });
74
112
  });
75
- }).then(() => {
76
- if (!sourcemapOutput) {
113
+ }).then(async () => {
114
+ if (!sourcemapOutputFolder) {
77
115
  // skip source map compose if source map is not enabled
78
116
  return;
79
117
  }
@@ -88,8 +126,33 @@ export async function runHermesEmitBinaryCommand(
88
126
  throw new Error(`sourcemap file ${jsCompilerSourceMapFile} is not found`);
89
127
  }
90
128
 
129
+ const platformSourceMapOutput = await getBundleSourceMapOutput(command, bundleName, sourcemapOutputFolder);
91
130
  return new Promise((resolve, reject) => {
92
- const composeSourceMapsArgs = [composeSourceMapsPath, sourcemapOutput, jsCompilerSourceMapFile, "-o", sourcemapOutput];
131
+ let bundleSourceMapOutput = sourcemapOutputFolder;
132
+ let combinedSourceMapOutput = sourcemapOutputFolder;
133
+
134
+ if (!sourcemapOutputFolder.endsWith(".map")) {
135
+ bundleSourceMapOutput = platformSourceMapOutput;
136
+ switch (command.platform) {
137
+ case "android": {
138
+ combinedSourceMapOutput = path.join(sourcemapOutputFolder, bundleName + ".map");
139
+ break;
140
+ }
141
+ case "ios": {
142
+ combinedSourceMapOutput = bundleSourceMapOutput;
143
+ break;
144
+ }
145
+ default:
146
+ throw new Error('Platform must be either "android", "ios" or "windows".');
147
+ }
148
+ }
149
+ const composeSourceMapsArgs = [
150
+ composeSourceMapsPath,
151
+ bundleSourceMapOutput,
152
+ jsCompilerSourceMapFile,
153
+ "-o",
154
+ combinedSourceMapOutput,
155
+ ];
93
156
 
94
157
  // https://github.com/facebook/react-native/blob/master/react.gradle#L211
95
158
  // https://github.com/facebook/react-native/blob/master/scripts/react-native-xcode.sh#L178
@@ -124,6 +187,40 @@ export async function runHermesEmitBinaryCommand(
124
187
  });
125
188
  }
126
189
 
190
+ export function getXcodeDotEnvValue(key: string): string | undefined {
191
+ const xcodeEnvs = loadEnvAsMap([path.join("ios", ".xcode.env.local"), path.join("ios", ".xcode.env.local")]);
192
+ return xcodeEnvs.get(key);
193
+ }
194
+
195
+ export async function getMinifyParams(command: cli.IReleaseReactCommand) {
196
+ const isHermes = await isHermesEnabled(command);
197
+
198
+ switch (command.platform) {
199
+ case "android": {
200
+ // android always explicitly pass --minify true/false
201
+ // TaskConfiguration it.minifyEnabled.set(!isHermesEnabledInThisVariant)
202
+ return ["--minify", !isHermes];
203
+ }
204
+ case "ios": {
205
+ //if [[ $USE_HERMES != false && $DEV == false ]]; then
206
+ // EXTRA_ARGS+=("--minify" "false")
207
+ // fi
208
+ // ios does pass --minify false only if Hermes enables and does pass anything otherwise
209
+ return isHermes ? ["--minify", false] : [];
210
+ }
211
+ default:
212
+ throw new Error('Platform must be either "android", "ios" or "windows".');
213
+ }
214
+ }
215
+
216
+ export async function isHermesEnabled(command: cli.IReleaseReactCommand, platform: string = command.platform.toLowerCase()) {
217
+ // Check if we have to run hermes to compile JS to Byte Code if Hermes is enabled in Podfile and we're releasing an iOS build
218
+ const isAndroidHermesEnabled = await getAndroidHermesEnabled(command.gradleFile);
219
+ const isIOSHermesEnabled = getiOSHermesEnabled(command.podFile);
220
+
221
+ return command.useHermes || (platform === "android" && isAndroidHermesEnabled) || (platform === "ios" && isIOSHermesEnabled);
222
+ }
223
+
127
224
  function parseBuildGradleFile(gradleFile: string) {
128
225
  let buildGradlePath: string = path.join("android", "app");
129
226
  if (gradleFile) {
@@ -142,6 +239,47 @@ function parseBuildGradleFile(gradleFile: string) {
142
239
  });
143
240
  }
144
241
 
242
+ function parseGradlePropertiesFile(gradleFile: string): Record<string, string> {
243
+ let gradlePropsPath: string = path.join("android", "gradle.properties");
244
+
245
+ try {
246
+ if (gradleFile) {
247
+ const base = gradleFile;
248
+ const stat = fs.lstatSync(base);
249
+
250
+ if (stat.isDirectory()) {
251
+ if (path.basename(base) === "app") {
252
+ gradlePropsPath = path.join(base, "..", "gradle.properties");
253
+ } else {
254
+ gradlePropsPath = path.join(base, "gradle.properties");
255
+ }
256
+ } else {
257
+ gradlePropsPath = path.join(path.dirname(base), "..", "gradle.properties");
258
+ }
259
+ }
260
+ } catch {}
261
+
262
+ gradlePropsPath = path.normalize(gradlePropsPath);
263
+
264
+ if (fileDoesNotExistOrIsDirectory(gradlePropsPath)) {
265
+ throw new Error(`Unable to find gradle.properties file "${gradlePropsPath}".`);
266
+ }
267
+
268
+ const text = fs.readFileSync(gradlePropsPath, "utf8");
269
+ const props: Record<string, string> = {};
270
+ for (const rawLine of text.split(/\r?\n/)) {
271
+ const line = rawLine.trim();
272
+ if (!line || line.startsWith("#")) continue;
273
+ const m = line.match(/^([^=\s]+)\s*=\s*(.*)$/);
274
+ if (m) {
275
+ const key = m[1].trim();
276
+ const val = m[2].trim();
277
+ props[key] = val;
278
+ }
279
+ }
280
+ return props;
281
+ }
282
+
145
283
  async function getHermesCommandFromGradle(gradleFile: string): Promise<string> {
146
284
  const buildGradle: any = await parseBuildGradleFile(gradleFile);
147
285
  const hermesCommandProperty: any = Array.from(buildGradle["project.ext.react"] || []).find((prop: string) =>
@@ -154,13 +292,28 @@ async function getHermesCommandFromGradle(gradleFile: string): Promise<string> {
154
292
  }
155
293
  }
156
294
 
157
- export function getAndroidHermesEnabled(gradleFile: string): boolean {
158
- return parseBuildGradleFile(gradleFile).then((buildGradle: any) => {
159
- return Array.from(buildGradle["project.ext.react"] || []).some((line: string) => /^enableHermes\s{0,}:\s{0,}true/.test(line));
160
- });
295
+ async function getAndroidHermesEnabled(gradleFile: string): Promise<boolean> {
296
+ try {
297
+ const props = parseGradlePropertiesFile(gradleFile);
298
+ if (typeof props.hermesEnabled !== "undefined") {
299
+ const v = String(props.hermesEnabled).trim().toLowerCase();
300
+ if (v === "true") return true;
301
+ if (v === "false") return false;
302
+ }
303
+ } catch {}
304
+
305
+ try {
306
+ const buildGradle: any = await parseBuildGradleFile(gradleFile);
307
+ const lines: string[] = Array.from(buildGradle["project.ext.react"] || []);
308
+ if (lines.some((l) => /\benableHermes\s*:\s*true\b/.test(l))) return true;
309
+ if (lines.some((l) => /\benableHermes\s*:\s*false\b/.test(l))) return false;
310
+ } catch {}
311
+
312
+ const rnVersion = coerce(getReactNativeVersion())?.version;
313
+ return rnVersion && compare(rnVersion, "0.70.0") >= 0;
161
314
  }
162
315
 
163
- export function getiOSHermesEnabled(podFile: string): boolean {
316
+ function getiOSHermesEnabled(podFile: string): boolean {
164
317
  let podPath = path.join("ios", "Podfile");
165
318
  if (podFile) {
166
319
  podPath = podFile;
@@ -171,12 +324,33 @@ export function getiOSHermesEnabled(podFile: string): boolean {
171
324
 
172
325
  try {
173
326
  const podFileContents = fs.readFileSync(podPath).toString();
174
- return /([^#\n]*:?hermes_enabled(\s+|\n+)?(=>|:)(\s+|\n+)?true)/.test(podFileContents);
327
+
328
+ const hasTrue = /([^#\n]*:?hermes_enabled(\s+|\n+)?(=>|:)(\s+|\n+)?true)/.test(podFileContents);
329
+ if (hasTrue) return true;
330
+
331
+ const hasFalse = /([^#\n]*:?hermes_enabled(\s+|\n+)?(=>|:)(\s+|\n+)?false)/.test(podFileContents);
332
+ if (hasFalse) return false;
333
+
334
+ const rnVersion = coerce(getReactNativeVersion())?.version;
335
+ return rnVersion && compare(rnVersion, "0.70.0") >= 0;
175
336
  } catch (error) {
176
337
  throw error;
177
338
  }
178
339
  }
179
340
 
341
+ function loadEnvAsMap(envPaths = []): Map<string, string | undefined> {
342
+ const merged: DotenvParseOutput = {};
343
+
344
+ for (const envPath of envPaths) {
345
+ if (fs.existsSync(envPath)) {
346
+ Object.assign(merged, dotenv.parse(fs.readFileSync(envPath))); // later files override earlier ones
347
+ }
348
+ }
349
+
350
+ // fallback to process.env for anything missing
351
+ return new Map([...Object.entries(process.env), ...Object.entries(merged)]);
352
+ }
353
+
180
354
  function getHermesOSBin(): string {
181
355
  switch (process.platform) {
182
356
  case "win32":
@@ -211,7 +385,8 @@ async function getHermesCommand(gradleFile: string): Promise<string> {
211
385
  }
212
386
  };
213
387
  // Hermes is bundled with react-native since 0.69
214
- const bundledHermesEngine = path.join(getReactNativePackagePath(), "sdks", "hermesc", getHermesOSBin(), getHermesOSExe());
388
+ const reactNativePath = getReactNativePackagePath();
389
+ const bundledHermesEngine = path.join(reactNativePath, "sdks", "hermesc", getHermesOSBin(), getHermesOSExe());
215
390
  if (fileExists(bundledHermesEngine)) {
216
391
  return bundledHermesEngine;
217
392
  }
@@ -220,12 +395,14 @@ async function getHermesCommand(gradleFile: string): Promise<string> {
220
395
  if (gradleHermesCommand) {
221
396
  return path.join("android", "app", gradleHermesCommand.replace("%OS-BIN%", getHermesOSBin()));
222
397
  } else {
398
+ const nodeModulesPath = getNodeModulesPath(reactNativePath);
399
+
223
400
  // assume if hermes-engine exists it should be used instead of hermesvm
224
- const hermesEngine = path.join("node_modules", "hermes-engine", getHermesOSBin(), getHermesOSExe());
401
+ const hermesEngine = path.join(nodeModulesPath, "hermes-engine", getHermesOSBin(), getHermesOSExe());
225
402
  if (fileExists(hermesEngine)) {
226
403
  return hermesEngine;
227
404
  }
228
- return path.join("node_modules", "hermesvm", getHermesOSBin(), "hermes");
405
+ return path.join(nodeModulesPath, "hermesvm", getHermesOSBin(), "hermes");
229
406
  }
230
407
  }
231
408
 
@@ -238,7 +415,16 @@ function getComposeSourceMapsPath(): string {
238
415
  return null;
239
416
  }
240
417
 
241
- function getReactNativePackagePath(): string {
418
+ function getNodeModulesPath(reactNativePath: string): string {
419
+ const nodeModulesPath = path.dirname(reactNativePath)
420
+ if (directoryExistsSync(nodeModulesPath)) {
421
+ return nodeModulesPath;
422
+ }
423
+
424
+ return path.join("node_modules");
425
+ }
426
+
427
+ export function getReactNativePackagePath(): string {
242
428
  const result = childProcess.spawnSync("node", ["--print", "require.resolve('react-native/package.json')"]);
243
429
  const packagePath = path.dirname(result.stdout.toString());
244
430
  if (result.status === 0 && directoryExistsSync(packagePath)) {
@@ -280,4 +466,4 @@ export function getReactNativeVersion(): string {
280
466
  (projectPackageJson.dependencies && projectPackageJson.dependencies["react-native"]) ||
281
467
  (projectPackageJson.devDependencies && projectPackageJson.devDependencies["react-native"])
282
468
  );
283
- }
469
+ }