@bravemobile/react-native-code-push 12.4.1 → 13.0.0

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.
package/CodePush.podspec CHANGED
@@ -14,7 +14,7 @@ Pod::Spec.new do |s|
14
14
  s.tvos.deployment_target = '15.5'
15
15
  s.preserve_paths = '*.js'
16
16
  s.library = 'z'
17
- s.source_files = 'ios/CodePush/*.{h,m}'
17
+ s.source_files = 'ios/CodePush/*.{h,m,mm}'
18
18
  s.public_header_files = ['ios/CodePush/CodePush.h']
19
19
 
20
20
  # Note: Even though there are copy/pasted versions of some of these dependencies in the repo,
@@ -22,4 +22,17 @@ Pod::Spec.new do |s|
22
22
  # linked properly at a parent workspace level.
23
23
  s.dependency 'React-Core'
24
24
  s.dependency 'SSZipArchive', '~> 2.5.5'
25
+
26
+ if ENV['RCT_NEW_ARCH_ENABLED'] == '1'
27
+ s.compiler_flags = '-DRCT_NEW_ARCH_ENABLED=1'
28
+ s.pod_target_xcconfig = {
29
+ 'CLANG_CXX_LANGUAGE_STANDARD' => 'c++17',
30
+ 'HEADER_SEARCH_PATHS' => "\"$(PODS_ROOT)/Headers/Public/ReactCodegen\" \"${PODS_CONFIGURATION_BUILD_DIR}/ReactCodegen/ReactCodegen.framework/Headers\""
31
+ }
32
+
33
+ s.dependency 'ReactCodegen'
34
+ s.dependency 'RCTRequired'
35
+ s.dependency 'RCTTypeSafety'
36
+ s.dependency 'ReactCommon/turbomodule/core'
37
+ end
25
38
  end
package/README.md CHANGED
@@ -13,6 +13,9 @@ Supports React Native 0.77 ~ 0.86.
13
13
  > [!NOTE]
14
14
  > If you are using React Native 0.76 or lower, please use version `12.0.2` of this library.
15
15
 
16
+ > [!IMPORTANT]
17
+ > `13.0.0` migrates the native module to TurboModule. A `13.0.0` binary only runs OTA bundles built with `13.0.0` or later, so ship a new binary version and start its release history fresh when you upgrade.
18
+
16
19
  (Tested on the React Native CLI template apps)
17
20
 
18
21
  ### ✅ Requirements
@@ -1,10 +1,17 @@
1
1
  apply plugin: "com.android.library"
2
+ apply plugin: "com.facebook.react"
2
3
 
3
4
  def DEFAULT_COMPILE_SDK_VERSION = 26
4
5
  def DEFAULT_BUILD_TOOLS_VERSION = "26.0.3"
5
6
  def DEFAULT_TARGET_SDK_VERSION = 26
6
7
  def DEFAULT_MIN_SDK_VERSION = 16
7
8
 
9
+ react {
10
+ jsRootDir = file("../..")
11
+ libraryName = "RNCodePushSpec"
12
+ codegenJavaPackageName = "com.microsoft.codepush.react"
13
+ }
14
+
8
15
  android {
9
16
  namespace "com.microsoft.codepush.react"
10
17
 
@@ -4,18 +4,25 @@ import android.content.Context;
4
4
  import android.content.pm.PackageInfo;
5
5
  import android.content.pm.PackageManager;
6
6
 
7
- import com.facebook.react.ReactPackage;
7
+ import androidx.annotation.NonNull;
8
+ import androidx.annotation.Nullable;
9
+
10
+ import com.facebook.react.TurboReactPackage;
8
11
  import com.facebook.react.bridge.NativeModule;
9
12
  import com.facebook.react.bridge.ReactApplicationContext;
13
+ import com.facebook.react.module.model.ReactModuleInfo;
14
+ import com.facebook.react.module.model.ReactModuleInfoProvider;
10
15
  import com.facebook.react.uimanager.ViewManager;
11
16
 
12
17
  import org.json.JSONException;
13
18
  import org.json.JSONObject;
14
19
 
15
20
  import java.util.ArrayList;
21
+ import java.util.HashMap;
16
22
  import java.util.List;
23
+ import java.util.Map;
17
24
 
18
- public class CodePush implements ReactPackage {
25
+ public class CodePush extends TurboReactPackage {
19
26
 
20
27
  private static boolean sIsRunningBinaryVersion = false;
21
28
  private static boolean sNeedToReportRollback = false;
@@ -300,17 +307,45 @@ public class CodePush implements ReactPackage {
300
307
  }
301
308
 
302
309
  @Override
303
- public List<NativeModule> createNativeModules(ReactApplicationContext reactApplicationContext) {
304
- CodePushNativeModule codePushModule = new CodePushNativeModule(reactApplicationContext, this, mUpdateManager, mTelemetryManager, mSettingsManager);
305
- CodePushDialog dialogModule = new CodePushDialog(reactApplicationContext);
310
+ public @Nullable NativeModule getModule(String name, ReactApplicationContext reactApplicationContext) {
311
+ if (!CodePushNativeModule.NAME.equals(name)) {
312
+ return null;
313
+ }
314
+
315
+ return new CodePushNativeModule(
316
+ reactApplicationContext,
317
+ this,
318
+ mUpdateManager,
319
+ mTelemetryManager,
320
+ mSettingsManager
321
+ );
322
+ }
306
323
 
307
- List<NativeModule> nativeModules = new ArrayList<>();
308
- nativeModules.add(codePushModule);
309
- nativeModules.add(dialogModule);
310
- return nativeModules;
324
+ @Override
325
+ public ReactModuleInfoProvider getReactModuleInfoProvider() {
326
+ return new ReactModuleInfoProvider() {
327
+ @Override
328
+ public Map<String, ReactModuleInfo> getReactModuleInfos() {
329
+ Map<String, ReactModuleInfo> reactModuleInfoMap = new HashMap<>();
330
+ reactModuleInfoMap.put(
331
+ CodePushNativeModule.NAME,
332
+ new ReactModuleInfo(
333
+ CodePushNativeModule.NAME,
334
+ CodePushNativeModule.class.getName(),
335
+ false,
336
+ false,
337
+ false,
338
+ false,
339
+ true
340
+ )
341
+ );
342
+ return reactModuleInfoMap;
343
+ }
344
+ };
311
345
  }
346
+
312
347
  @Override
313
- public List<ViewManager> createViewManagers(ReactApplicationContext reactApplicationContext) {
348
+ public List<ViewManager> createViewManagers(@NonNull ReactApplicationContext reactApplicationContext) {
314
349
  return new ArrayList<>();
315
350
  }
316
351
  }
@@ -2,7 +2,6 @@ package com.microsoft.codepush.react;
2
2
 
3
3
  import android.app.Activity;
4
4
  import android.content.SharedPreferences;
5
- import android.os.AsyncTask;
6
5
  import android.os.Handler;
7
6
  import android.os.Looper;
8
7
  import android.view.View;
@@ -20,11 +19,11 @@ import com.facebook.react.bridge.JSBundleLoader;
20
19
  import com.facebook.react.bridge.LifecycleEventListener;
21
20
  import com.facebook.react.bridge.Promise;
22
21
  import com.facebook.react.bridge.ReactApplicationContext;
23
- import com.facebook.react.bridge.ReactContextBaseJavaModule;
24
22
  import com.facebook.react.bridge.ReactMethod;
25
23
  import com.facebook.react.bridge.ReadableMap;
26
24
  import com.facebook.react.bridge.WritableMap;
27
25
  import com.facebook.react.common.annotations.UnstableReactNativeAPI;
26
+ import com.facebook.react.module.annotations.ReactModule;
28
27
  import com.facebook.react.modules.core.DeviceEventManagerModule;
29
28
  import com.facebook.react.modules.core.ReactChoreographer;
30
29
  import com.facebook.react.runtime.ReactHostDelegate;
@@ -37,15 +36,19 @@ import java.io.IOException;
37
36
  import java.lang.reflect.Field;
38
37
  import java.util.ArrayList;
39
38
  import java.util.Date;
40
- import java.util.HashMap;
41
- import java.util.Map;
42
39
  import java.util.UUID;
40
+ import java.util.concurrent.ExecutorService;
41
+ import java.util.concurrent.Executors;
42
+
43
+ @ReactModule(name = CodePushNativeModule.NAME)
44
+ public class CodePushNativeModule extends NativeCodePushSpec {
45
+ public static final String NAME = "CodePush";
43
46
 
44
- public class CodePushNativeModule extends ReactContextBaseJavaModule {
45
47
  private String mBinaryContentsHash = null;
46
48
  private String mClientUniqueId = null;
47
49
  private LifecycleEventListener mLifecycleEventListener = null;
48
50
  private int mMinimumBackgroundDuration = 0;
51
+ private final ExecutorService mBackgroundExecutor = Executors.newSingleThreadExecutor();
49
52
 
50
53
  private CodePush mCodePush;
51
54
  private SettingsManager mSettingsManager;
@@ -76,24 +79,15 @@ public class CodePushNativeModule extends ReactContextBaseJavaModule {
76
79
  }
77
80
 
78
81
  @Override
79
- public Map<String, Object> getConstants() {
80
- final Map<String, Object> constants = new HashMap<>();
81
-
82
- constants.put("codePushInstallModeImmediate", CodePushInstallMode.IMMEDIATE.getValue());
83
- constants.put("codePushInstallModeOnNextRestart", CodePushInstallMode.ON_NEXT_RESTART.getValue());
84
- constants.put("codePushInstallModeOnNextResume", CodePushInstallMode.ON_NEXT_RESUME.getValue());
85
- constants.put("codePushInstallModeOnNextSuspend", CodePushInstallMode.ON_NEXT_SUSPEND.getValue());
86
-
87
- constants.put("codePushUpdateStateRunning", CodePushUpdateState.RUNNING.getValue());
88
- constants.put("codePushUpdateStatePending", CodePushUpdateState.PENDING.getValue());
89
- constants.put("codePushUpdateStateLatest", CodePushUpdateState.LATEST.getValue());
90
-
91
- return constants;
82
+ public String getName() {
83
+ return NAME;
92
84
  }
93
85
 
94
86
  @Override
95
- public String getName() {
96
- return "CodePush";
87
+ public void invalidate() {
88
+ clearLifecycleEventListener();
89
+ mBackgroundExecutor.shutdownNow();
90
+ super.invalidate();
97
91
  }
98
92
 
99
93
  private void loadBundleLegacy() {
@@ -265,6 +259,21 @@ public class CodePushNativeModule extends ReactContextBaseJavaModule {
265
259
  return null;
266
260
  }
267
261
 
262
+ private void executeInBackground(Runnable runnable) {
263
+ mBackgroundExecutor.execute(runnable);
264
+ }
265
+
266
+ private void emitDownloadProgressEvent(DownloadProgress downloadProgress) {
267
+ if (mEventEmitterCallback != null) {
268
+ emitOnDownloadProgress(downloadProgress.createWritableMap());
269
+ return;
270
+ }
271
+
272
+ getReactApplicationContext()
273
+ .getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class)
274
+ .emit(CodePushConstants.DOWNLOAD_PROGRESS_EVENT_NAME, downloadProgress.createWritableMap());
275
+ }
276
+
268
277
  private void restartAppInternal(boolean onlyIfUpdateIsPending) {
269
278
  if (this._restartInProgress) {
270
279
  CodePushUtils.log("Restart request queued until the current restart is completed");
@@ -292,7 +301,7 @@ public class CodePushNativeModule extends ReactContextBaseJavaModule {
292
301
  }
293
302
 
294
303
  @ReactMethod
295
- public void allow(Promise promise) {
304
+ public void allow() {
296
305
  CodePushUtils.log("Re-allowing restarts");
297
306
  this._allowed = true;
298
307
 
@@ -302,42 +311,29 @@ public class CodePushNativeModule extends ReactContextBaseJavaModule {
302
311
  this._restartQueue.remove(0);
303
312
  this.restartAppInternal(buf);
304
313
  }
305
-
306
- promise.resolve(null);
307
- return;
308
314
  }
309
315
 
310
316
  @ReactMethod
311
- public void clearPendingRestart(Promise promise) {
317
+ public void clearPendingRestart() {
312
318
  this._restartQueue.clear();
313
- promise.resolve(null);
314
- return;
315
319
  }
316
320
 
317
321
  @ReactMethod
318
- public void disallow(Promise promise) {
322
+ public void disallow() {
319
323
  CodePushUtils.log("Disallowing restarts");
320
324
  this._allowed = false;
321
- promise.resolve(null);
322
- return;
323
325
  }
324
326
 
325
327
  @ReactMethod
326
- public void restartApp(boolean onlyIfUpdateIsPending, Promise promise) {
327
- try {
328
- restartAppInternal(onlyIfUpdateIsPending);
329
- promise.resolve(null);
330
- } catch(CodePushUnknownException e) {
331
- CodePushUtils.log(e);
332
- promise.reject(e);
333
- }
328
+ public void restartApp(boolean onlyIfUpdateIsPending) {
329
+ restartAppInternal(onlyIfUpdateIsPending);
334
330
  }
335
331
 
336
332
  @ReactMethod
337
333
  public void downloadUpdate(final ReadableMap updatePackage, final boolean notifyProgress, final Promise promise) {
338
- AsyncTask<Void, Void, Void> asyncTask = new AsyncTask<Void, Void, Void>() {
334
+ executeInBackground(new Runnable() {
339
335
  @Override
340
- protected Void doInBackground(Void... params) {
336
+ public void run() {
341
337
  try {
342
338
  JSONObject mutableUpdatePackage = CodePushUtils.convertReadableToJsonObject(updatePackage);
343
339
  mUpdateManager.downloadPackage(mutableUpdatePackage, mCodePush.getAssetsBundleFileName(), new DownloadProgressCallback() {
@@ -347,9 +343,7 @@ public class CodePushNativeModule extends ReactContextBaseJavaModule {
347
343
  new DownloadProgressEventDispatcher.EventEmitter() {
348
344
  @Override
349
345
  public void emit(DownloadProgress downloadProgress) {
350
- getReactApplicationContext()
351
- .getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class)
352
- .emit(CodePushConstants.DOWNLOAD_PROGRESS_EVENT_NAME, downloadProgress.createWritableMap());
346
+ emitDownloadProgressEvent(downloadProgress);
353
347
  }
354
348
  });
355
349
 
@@ -399,12 +393,8 @@ public class CodePushNativeModule extends ReactContextBaseJavaModule {
399
393
  CodePushUtils.log(e);
400
394
  promise.reject(e);
401
395
  }
402
-
403
- return null;
404
396
  }
405
- };
406
-
407
- asyncTask.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
397
+ });
408
398
  }
409
399
 
410
400
  @ReactMethod
@@ -429,16 +419,17 @@ public class CodePushNativeModule extends ReactContextBaseJavaModule {
429
419
  }
430
420
 
431
421
  @ReactMethod
432
- public void getUpdateMetadata(final int updateState, final Promise promise) {
433
- AsyncTask<Void, Void, Void> asyncTask = new AsyncTask<Void, Void, Void>() {
422
+ public void getUpdateMetadata(final double updateStateValue, final Promise promise) {
423
+ executeInBackground(new Runnable() {
434
424
  @Override
435
- protected Void doInBackground(Void... params) {
425
+ public void run() {
436
426
  try {
427
+ int updateState = (int) updateStateValue;
437
428
  JSONObject currentPackage = mUpdateManager.getCurrentPackage();
438
429
 
439
430
  if (currentPackage == null) {
440
431
  promise.resolve(null);
441
- return null;
432
+ return;
442
433
  }
443
434
 
444
435
  Boolean currentUpdateIsPending = false;
@@ -459,7 +450,7 @@ public class CodePushNativeModule extends ReactContextBaseJavaModule {
459
450
 
460
451
  if (previousPackage == null) {
461
452
  promise.resolve(null);
462
- return null;
453
+ return;
463
454
  }
464
455
 
465
456
  promise.resolve(CodePushUtils.convertJsonObjectToWritable(previousPackage));
@@ -488,19 +479,15 @@ public class CodePushNativeModule extends ReactContextBaseJavaModule {
488
479
  CodePushUtils.log(e);
489
480
  promise.reject(e);
490
481
  }
491
-
492
- return null;
493
482
  }
494
- };
495
-
496
- asyncTask.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
483
+ });
497
484
  }
498
485
 
499
486
  @ReactMethod
500
487
  public void getNewStatusReport(final Promise promise) {
501
- AsyncTask<Void, Void, Void> asyncTask = new AsyncTask<Void, Void, Void>() {
488
+ executeInBackground(new Runnable() {
502
489
  @Override
503
- protected Void doInBackground(Void... params) {
490
+ public void run() {
504
491
  try {
505
492
  if (mCodePush.needToReportRollback()) {
506
493
  mCodePush.setNeedToReportRollback(false);
@@ -512,7 +499,7 @@ public class CodePushNativeModule extends ReactContextBaseJavaModule {
512
499
  WritableMap failedStatusReport = mTelemetryManager.getRollbackReport(lastFailedPackage);
513
500
  if (failedStatusReport != null) {
514
501
  promise.resolve(failedStatusReport);
515
- return null;
502
+ return;
516
503
  }
517
504
  } catch (JSONException e) {
518
505
  throw new CodePushUnknownException("Unable to read failed updates information stored in SharedPreferences.", e);
@@ -524,41 +511,40 @@ public class CodePushNativeModule extends ReactContextBaseJavaModule {
524
511
  WritableMap newPackageStatusReport = mTelemetryManager.getUpdateReport(CodePushUtils.convertJsonObjectToWritable(currentPackage));
525
512
  if (newPackageStatusReport != null) {
526
513
  promise.resolve(newPackageStatusReport);
527
- return null;
514
+ return;
528
515
  }
529
516
  }
530
517
  } else if (mCodePush.isRunningBinaryVersion()) {
531
518
  WritableMap newAppVersionStatusReport = mTelemetryManager.getBinaryUpdateReport(mCodePush.getAppVersion());
532
519
  if (newAppVersionStatusReport != null) {
533
520
  promise.resolve(newAppVersionStatusReport);
534
- return null;
521
+ return;
535
522
  }
536
523
  } else {
537
524
  WritableMap retryStatusReport = mTelemetryManager.getRetryStatusReport();
538
525
  if (retryStatusReport != null) {
539
526
  promise.resolve(retryStatusReport);
540
- return null;
527
+ return;
541
528
  }
542
529
  }
543
530
 
544
- promise.resolve("");
531
+ promise.resolve(null);
545
532
  } catch(CodePushUnknownException e) {
546
533
  CodePushUtils.log(e);
547
534
  promise.reject(e);
548
535
  }
549
- return null;
550
536
  }
551
- };
552
-
553
- asyncTask.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
537
+ });
554
538
  }
555
539
 
556
540
  @ReactMethod
557
- public void installUpdate(final ReadableMap updatePackage, final int installMode, final int minimumBackgroundDuration, final Promise promise) {
558
- AsyncTask<Void, Void, Void> asyncTask = new AsyncTask<Void, Void, Void>() {
541
+ public void installUpdate(final ReadableMap updatePackage, final double installModeValue, final double minimumBackgroundDurationValue, final Promise promise) {
542
+ executeInBackground(new Runnable() {
559
543
  @Override
560
- protected Void doInBackground(Void... params) {
544
+ public void run() {
561
545
  try {
546
+ int installMode = (int) installModeValue;
547
+ int minimumBackgroundDuration = (int) minimumBackgroundDurationValue;
562
548
  mUpdateManager.installPackage(CodePushUtils.convertReadableToJsonObject(updatePackage), mSettingsManager.isPendingUpdate(null));
563
549
 
564
550
  String pendingHash = CodePushUtils.tryGetString(updatePackage, CodePushConstants.PACKAGE_HASH_KEY);
@@ -568,13 +554,12 @@ public class CodePushNativeModule extends ReactContextBaseJavaModule {
568
554
  mSettingsManager.savePendingUpdate(pendingHash, /* isLoading */false);
569
555
  }
570
556
 
571
- if (installMode == CodePushInstallMode.ON_NEXT_RESUME.getValue() ||
572
- // We also add the resume listener if the installMode is IMMEDIATE, because
573
- // if the current activity is backgrounded, we want to reload the bundle when
574
- // it comes back into the foreground.
575
- installMode == CodePushInstallMode.IMMEDIATE.getValue() ||
576
- installMode == CodePushInstallMode.ON_NEXT_SUSPEND.getValue()) {
577
-
557
+ if (installMode == CodePushInstallMode.ON_NEXT_RESUME.getValue()
558
+ // We also add the resume listener if the installMode is IMMEDIATE, because
559
+ // if the current activity is backgrounded, we want to reload the bundle when
560
+ // it comes back into the foreground.
561
+ || installMode == CodePushInstallMode.IMMEDIATE.getValue()
562
+ || installMode == CodePushInstallMode.ON_NEXT_SUSPEND.getValue()) {
578
563
  // Store the minimum duration on the native module as an instance
579
564
  // variable instead of relying on a closure below, so that any
580
565
  // subsequent resume-based installs could override it.
@@ -628,17 +613,13 @@ public class CodePushNativeModule extends ReactContextBaseJavaModule {
628
613
  }
629
614
  }
630
615
 
631
- promise.resolve("");
616
+ promise.resolve(null);
632
617
  } catch(CodePushUnknownException e) {
633
618
  CodePushUtils.log(e);
634
619
  promise.reject(e);
635
620
  }
636
-
637
- return null;
638
621
  }
639
- };
640
-
641
- asyncTask.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
622
+ });
642
623
  }
643
624
 
644
625
  @ReactMethod
@@ -695,7 +676,7 @@ public class CodePushNativeModule extends ReactContextBaseJavaModule {
695
676
  public void notifyApplicationReady(Promise promise) {
696
677
  try {
697
678
  mSettingsManager.removePendingUpdate();
698
- promise.resolve("");
679
+ promise.resolve(null);
699
680
  } catch(CodePushUnknownException e) {
700
681
  CodePushUtils.log(e);
701
682
  promise.reject(e);
@@ -4,6 +4,7 @@
4
4
  import childProcess from "child_process";
5
5
  import fs from "fs";
6
6
  import path from "path";
7
+ import { createRequire } from "node:module";
7
8
  import shell from "shelljs";
8
9
  /**
9
10
  * Run Hermes compile CLI command
@@ -12,9 +13,10 @@ import shell from "shelljs";
12
13
  * @param outputPath {string} Path to output .hbc file
13
14
  * @param sourcemapOutput {string} Path to output sourcemap file (Warning: if sourcemapOutput points to the outputPath, the sourcemap will be included in the CodePush bundle and increase the deployment size)
14
15
  * @param extraHermesFlags {string[]} Additional options to pass to `hermesc` command
16
+ * @param projectRoot {string} Root directory of the target app project used to resolve the app's React Native module and locate the matching Hermes compiler. Defaults to the current working directory.
15
17
  * @return {Promise<void>}
16
18
  */
17
- export async function runHermesEmitBinaryCommand(bundleName, outputPath, sourcemapOutput, extraHermesFlags = []) {
19
+ export async function runHermesEmitBinaryCommand(bundleName, outputPath, sourcemapOutput, extraHermesFlags = [], projectRoot = process.cwd()) {
18
20
  const hermesArgs = [
19
21
  '-emit-binary',
20
22
  '-out',
@@ -28,7 +30,7 @@ export async function runHermesEmitBinaryCommand(bundleName, outputPath, sourcem
28
30
  console.log('Converting JS bundle to byte code via Hermes, running command:\n');
29
31
  return new Promise((resolve, reject) => {
30
32
  try {
31
- const hermesCommand = getHermesCommand();
33
+ const hermesCommand = getHermesCommand(projectRoot);
32
34
  const disableAllWarningsArg = '-w';
33
35
  shell.exec(`${hermesCommand} ${hermesArgs.join(' ')} ${disableAllWarningsArg}`);
34
36
  // Copy HBC bundle to overwrite JS bundle
@@ -47,7 +49,7 @@ export async function runHermesEmitBinaryCommand(bundleName, outputPath, sourcem
47
49
  return;
48
50
  }
49
51
  // compose-source-maps.js file path
50
- const composeSourceMapsPath = getComposeSourceMapsPath();
52
+ const composeSourceMapsPath = getComposeSourceMapsPath(projectRoot);
51
53
  if (composeSourceMapsPath === null) {
52
54
  throw new Error('react-native compose-source-maps.js scripts is not found');
53
55
  }
@@ -87,7 +89,7 @@ export async function runHermesEmitBinaryCommand(bundleName, outputPath, sourcem
87
89
  });
88
90
  });
89
91
  }
90
- function getHermesCommand() {
92
+ function getHermesCommand(projectRoot) {
91
93
  const fileExists = (file) => {
92
94
  try {
93
95
  return fs.statSync(file).isFile();
@@ -96,7 +98,7 @@ function getHermesCommand() {
96
98
  return false;
97
99
  }
98
100
  };
99
- const hermescExecutable = path.join(getHermesCompilerPath(), getHermesOSBin(), getHermesOSExe());
101
+ const hermescExecutable = path.join(getHermesCompilerPath(projectRoot), getHermesOSBin(), getHermesOSExe());
100
102
  if (fileExists(hermescExecutable)) {
101
103
  return hermescExecutable;
102
104
  }
@@ -124,45 +126,67 @@ function getHermesOSExe() {
124
126
  return hermesExecutableName;
125
127
  }
126
128
  }
127
- function getComposeSourceMapsPath() {
129
+ function getComposeSourceMapsPath(projectRoot) {
128
130
  // detect if compose-source-maps.js script exists
129
- const composeSourceMaps = path.join(getReactNativePackagePath(), 'scripts', 'compose-source-maps.js');
131
+ const composeSourceMaps = path.join(getReactNativePackagePath(projectRoot), 'scripts', 'compose-source-maps.js');
130
132
  if (fs.existsSync(composeSourceMaps)) {
131
133
  return composeSourceMaps;
132
134
  }
133
135
  return null;
134
136
  }
135
- function getReactNativePackagePath() {
136
- const result = childProcess.spawnSync('node', [
137
- '--print',
138
- "require.resolve('react-native/package.json')",
139
- ]);
140
- const packagePath = path.dirname(result.stdout.toString());
141
- if (result.status === 0 && directoryExistsSync(packagePath)) {
137
+ function getReactNativePackagePath(projectRoot) {
138
+ const packagePath = resolvePackageRoot(projectRoot, 'react-native');
139
+ if (packagePath !== null) {
142
140
  return packagePath;
143
141
  }
144
- return path.join('node_modules', 'react-native');
142
+ return path.join(projectRoot, 'node_modules', 'react-native');
145
143
  }
146
- function getHermescDirPathInHermesCompilerPackage() {
147
- const result = childProcess.spawnSync('node', [
148
- '--print',
149
- "require.resolve('hermes-compiler/package.json')",
150
- ]);
151
- const packagePath = path.dirname(result.stdout.toString());
152
- const hermescDirPath = path.join(packagePath, 'hermesc');
153
- if (result.status === 0 && directoryExistsSync(hermescDirPath)) {
144
+ function getHermescDirPathInHermesCompilerPackage(projectRoot) {
145
+ const reactNativePackagePath = getReactNativePackagePath(projectRoot);
146
+ const hermescDirPath = path.join(path.dirname(reactNativePackagePath), 'hermes-compiler', 'hermesc');
147
+ if (directoryExistsSync(hermescDirPath)) {
154
148
  return hermescDirPath;
155
149
  }
156
150
  return null;
157
151
  }
158
- function getHermesCompilerPath() {
159
- const hermescDirPath = getHermescDirPathInHermesCompilerPackage();
152
+ function getHermesCompilerPath(projectRoot) {
153
+ const hermescDirPath = getHermescDirPathInHermesCompilerPackage(projectRoot);
160
154
  if (hermescDirPath) {
161
155
  // Since react-native 0.83, Hermes compiler executables are in 'hermes-compiler' package
162
156
  return hermescDirPath;
163
157
  }
164
158
  else {
165
- return path.join(getReactNativePackagePath(), 'sdks', 'hermesc');
159
+ return path.join(getReactNativePackagePath(projectRoot), 'sdks', 'hermesc');
160
+ }
161
+ }
162
+ function resolvePackageRoot(projectRoot, packageName) {
163
+ try {
164
+ const projectRequire = createRequire(path.join(projectRoot, 'package.json'));
165
+ const resolvedPath = projectRequire.resolve(packageName);
166
+ return findPackageRoot(packageName, resolvedPath);
167
+ }
168
+ catch {
169
+ return null;
170
+ }
171
+ }
172
+ function findPackageRoot(packageName, resolvedPath) {
173
+ let currentPath = path.dirname(resolvedPath);
174
+ while (true) {
175
+ const packageJsonPath = path.join(currentPath, 'package.json');
176
+ try {
177
+ const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf8'));
178
+ if (packageJson.name === packageName) {
179
+ return currentPath;
180
+ }
181
+ }
182
+ catch {
183
+ // Continue traversing upward until the package root is found.
184
+ }
185
+ const parentPath = path.dirname(currentPath);
186
+ if (parentPath === currentPath) {
187
+ return null;
188
+ }
189
+ currentPath = parentPath;
166
190
  }
167
191
  }
168
192
  function directoryExistsSync(dirname) {