@callstack/react-native-brownfield 3.9.0 → 3.11.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.
Files changed (59) hide show
  1. package/CHANGELOG.md +24 -0
  2. package/ReactBrownfield.podspec +23 -3
  3. package/ios/BrownfieldDevLoadingViewBridge.h +11 -0
  4. package/ios/BrownfieldDevLoadingViewBridge.m +12 -0
  5. package/ios/{ExpoHostRuntime.swift → Expo/ExpoHostRuntime.swift} +65 -23
  6. package/ios/ReactNativeBrownfield.swift +14 -0
  7. package/ios/ReactNativeBrownfield.xcodeproj/project.pbxproj +74 -24
  8. package/ios/{ReactNativeHostRuntime.swift → Vanilla/ReactNativeHostRuntime.swift} +40 -12
  9. package/ios/swiftpm/Package.swift +27 -0
  10. package/ios/swiftpm/Sources/BrownfieldBundleSupport/BrownfieldBundleURLResolver.swift +28 -0
  11. package/ios/swiftpm/Tests/BrownfieldBundleSupportTests/BrownfieldBundleURLResolverTests.swift +156 -0
  12. package/lib/commonjs/expo-config-plugin/ios/withBrownfieldIos.js +1 -1
  13. package/lib/commonjs/expo-config-plugin/ios/withBrownfieldIos.js.map +1 -1
  14. package/lib/commonjs/expo-config-plugin/ios/withFmtFix.js +12 -0
  15. package/lib/commonjs/expo-config-plugin/ios/withFmtFix.js.map +1 -0
  16. package/lib/commonjs/expo-config-plugin/ios/withIosFrameworkFiles.js +1 -1
  17. package/lib/commonjs/expo-config-plugin/ios/withIosFrameworkFiles.js.map +1 -1
  18. package/lib/commonjs/expo-config-plugin/ios/xcodeHelpers.js +6 -1
  19. package/lib/commonjs/expo-config-plugin/ios/xcodeHelpers.js.map +1 -1
  20. package/lib/commonjs/expo-config-plugin/logging.js +1 -1
  21. package/lib/commonjs/expo-config-plugin/logging.js.map +1 -1
  22. package/lib/commonjs/expo-config-plugin/template/ios/FrameworkInterface.swift +4 -1
  23. package/lib/module/expo-config-plugin/ios/withBrownfieldIos.js +1 -1
  24. package/lib/module/expo-config-plugin/ios/withBrownfieldIos.js.map +1 -1
  25. package/lib/module/expo-config-plugin/ios/withFmtFix.js +12 -0
  26. package/lib/module/expo-config-plugin/ios/withFmtFix.js.map +1 -0
  27. package/lib/module/expo-config-plugin/ios/withIosFrameworkFiles.js +1 -1
  28. package/lib/module/expo-config-plugin/ios/withIosFrameworkFiles.js.map +1 -1
  29. package/lib/module/expo-config-plugin/ios/xcodeHelpers.js +6 -1
  30. package/lib/module/expo-config-plugin/ios/xcodeHelpers.js.map +1 -1
  31. package/lib/module/expo-config-plugin/logging.js +1 -1
  32. package/lib/module/expo-config-plugin/logging.js.map +1 -1
  33. package/lib/module/expo-config-plugin/template/ios/FrameworkInterface.swift +4 -1
  34. package/lib/typescript/commonjs/src/expo-config-plugin/ios/withBrownfieldIos.d.ts.map +1 -1
  35. package/lib/typescript/commonjs/src/expo-config-plugin/ios/withFmtFix.d.ts +5 -0
  36. package/lib/typescript/commonjs/src/expo-config-plugin/ios/withFmtFix.d.ts.map +1 -0
  37. package/lib/typescript/commonjs/src/expo-config-plugin/ios/withIosFrameworkFiles.d.ts.map +1 -1
  38. package/lib/typescript/commonjs/src/expo-config-plugin/ios/xcodeHelpers.d.ts +14 -0
  39. package/lib/typescript/commonjs/src/expo-config-plugin/ios/xcodeHelpers.d.ts.map +1 -1
  40. package/lib/typescript/commonjs/src/expo-config-plugin/logging.d.ts +1 -0
  41. package/lib/typescript/commonjs/src/expo-config-plugin/logging.d.ts.map +1 -1
  42. package/lib/typescript/module/src/expo-config-plugin/ios/withBrownfieldIos.d.ts.map +1 -1
  43. package/lib/typescript/module/src/expo-config-plugin/ios/withFmtFix.d.ts +5 -0
  44. package/lib/typescript/module/src/expo-config-plugin/ios/withFmtFix.d.ts.map +1 -0
  45. package/lib/typescript/module/src/expo-config-plugin/ios/withIosFrameworkFiles.d.ts.map +1 -1
  46. package/lib/typescript/module/src/expo-config-plugin/ios/xcodeHelpers.d.ts +14 -0
  47. package/lib/typescript/module/src/expo-config-plugin/ios/xcodeHelpers.d.ts.map +1 -1
  48. package/lib/typescript/module/src/expo-config-plugin/logging.d.ts +1 -0
  49. package/lib/typescript/module/src/expo-config-plugin/logging.d.ts.map +1 -1
  50. package/package.json +3 -3
  51. package/src/expo-config-plugin/ios/withBrownfieldIos.ts +14 -2
  52. package/src/expo-config-plugin/ios/withFmtFix.ts +72 -0
  53. package/src/expo-config-plugin/ios/withIosFrameworkFiles.ts +10 -10
  54. package/src/expo-config-plugin/ios/xcodeHelpers.ts +134 -14
  55. package/src/expo-config-plugin/logging.ts +4 -0
  56. package/src/expo-config-plugin/template/ios/FrameworkInterface.swift +4 -1
  57. /package/ios/{ReactNativeView.swift → Views/ReactNativeView.swift} +0 -0
  58. /package/ios/{ReactNativeViewController.swift → Views/ReactNativeViewController.swift} +0 -0
  59. /package/ios/{BrownfieldBundlePathResolver.swift → swiftpm/Sources/BrownfieldBundleSupport/BrownfieldBundlePathResolver.swift} +0 -0
@@ -401,7 +401,7 @@ export function ensureTargetHasFileReferenceInResourcesBuildPhase(
401
401
  * @param options The user configuration
402
402
  * @returns Build settings object
403
403
  */
404
- function getFrameworkBuildSettings(
404
+ export function getFrameworkBuildSettings(
405
405
  {
406
406
  configuration,
407
407
  }: {
@@ -424,11 +424,16 @@ function getFrameworkBuildSettings(
424
424
  USER_SCRIPT_SANDBOXING: 'NO',
425
425
  SKIP_INSTALL: 'NO',
426
426
  ENABLE_MODULE_VERIFIER: 'NO',
427
+ INSTALL_PATH: '"$(LOCAL_LIBRARY_DIR)/Frameworks"',
427
428
 
428
429
  // basic settings
429
430
  PRODUCT_BUNDLE_IDENTIFIER: `"${bundleIdentifier}"`,
430
431
  IPHONEOS_DEPLOYMENT_TARGET: deploymentTarget,
431
432
 
433
+ // Ensure the BrownfieldLib (or equivalent name) is installed at the correct path
434
+ DYLIB_INSTALL_NAME_BASE: '"@rpath"',
435
+ LD_DYLIB_INSTALL_NAME: '"@rpath/$(EXECUTABLE_PATH)"',
436
+
432
437
  // Swift settings - use modern Swift version (5.0+) to avoid legacy Swift 3.x migration prompts
433
438
  SWIFT_VERSION: '5.0',
434
439
  TARGETED_DEVICE_FAMILY: `"1,2"`,
@@ -442,6 +447,62 @@ function getFrameworkBuildSettings(
442
447
  };
443
448
  }
444
449
 
450
+ export function rewriteBundleReactNativePhaseScriptForFrameworkTarget(
451
+ shellScript: string
452
+ ): string {
453
+ const debugBundlingOverride = `# Brownfield framework packaging must embed JS in Debug builds.
454
+ if [[ "$CONFIGURATION" = *Debug* ]]; then
455
+ unset SKIP_BUNDLING
456
+ export FORCE_BUNDLING=1
457
+ fi
458
+ `;
459
+ const debugSkipBundlingBlock =
460
+ /if \[\[ "\$CONFIGURATION" = \*Debug\* \]\]; then\s+export SKIP_BUNDLING=1\s+fi\s*/m;
461
+
462
+ if (debugSkipBundlingBlock.test(shellScript)) {
463
+ return shellScript.replace(
464
+ debugSkipBundlingBlock,
465
+ `${debugBundlingOverride}\n`
466
+ );
467
+ }
468
+
469
+ if (shellScript.includes('export FORCE_BUNDLING=1')) {
470
+ return shellScript;
471
+ }
472
+
473
+ return `${debugBundlingOverride}\n${shellScript}`;
474
+ }
475
+
476
+ function decodePbxString(value: string | undefined): string {
477
+ if (!value) {
478
+ return '';
479
+ }
480
+
481
+ if (value.startsWith('"') && value.endsWith('"')) {
482
+ try {
483
+ return JSON.parse(value) as string;
484
+ } catch {
485
+ return value.slice(1, -1).replace(/\\"/g, '"');
486
+ }
487
+ }
488
+
489
+ return value;
490
+ }
491
+
492
+ function encodePbxString(value: string): string {
493
+ return JSON.stringify(value);
494
+ }
495
+
496
+ function hasBuildPhaseComment(
497
+ phase: { comment?: string },
498
+ expectedComment: string
499
+ ): boolean {
500
+ return (
501
+ phase.comment === expectedComment ||
502
+ phase.comment === `"${expectedComment}"`
503
+ );
504
+ }
505
+
445
506
  /**
446
507
  * Finds the "Bundle React Native code and images" build phase from the main app target
447
508
  * and adds it to the framework target's build phases
@@ -465,40 +526,85 @@ export function copyBundleReactNativePhase(
465
526
 
466
527
  // find the phase by name
467
528
  let existingPhaseUuid: string | null = null;
529
+ let existingPhase: Record<string, any> | null = null;
468
530
  for (const key of Object.keys(shellScriptPhases)) {
469
531
  if (key.endsWith('_comment')) continue;
470
532
  const phase = shellScriptPhases[key];
471
533
  if (phase.name === `"${buildPhaseName}"` || phase.name === buildPhaseName) {
472
534
  existingPhaseUuid = key;
535
+ existingPhase = phase;
473
536
  break;
474
537
  }
475
538
  }
476
539
 
477
- if (!existingPhaseUuid) {
540
+ if (!existingPhaseUuid || !existingPhase) {
478
541
  throw new SourceModificationError(
479
542
  `Could not find "${buildPhaseName}" build phase, skipping`
480
543
  );
481
544
  }
482
545
 
483
- // add the phase reference to the framework target's buildPhases array
484
546
  const nativeTargets = project.hash.project.objects.PBXNativeTarget;
485
547
  if (nativeTargets && nativeTargets[targetUuid]) {
486
548
  const target = nativeTargets[targetUuid];
487
549
  if (target.buildPhases) {
488
- // check if phase is already added
489
- if (
490
- !target.buildPhases.some(
491
- (phase: { value: string }) => phase.value === existingPhaseUuid
492
- )
493
- ) {
494
- target.buildPhases.push(
495
- createPbxCommentedReference(existingPhaseUuid, buildPhaseName)
550
+ const targetPhaseIndex = target.buildPhases.findIndex(
551
+ (phase: { comment?: string }) =>
552
+ hasBuildPhaseComment(phase, buildPhaseName)
553
+ );
554
+ const frameworkShellPath =
555
+ decodePbxString(existingPhase.shellPath) || '/bin/sh';
556
+ const frameworkShellScript =
557
+ rewriteBundleReactNativePhaseScriptForFrameworkTarget(
558
+ decodePbxString(existingPhase.shellScript)
496
559
  );
497
560
 
498
- Logger.logDebug(
499
- `Added "${buildPhaseName}" build phase to framework target ${target.name}`
500
- );
561
+ if (targetPhaseIndex !== -1) {
562
+ const currentPhaseUuid = target.buildPhases[targetPhaseIndex].value;
563
+ const currentPhase = shellScriptPhases[currentPhaseUuid];
564
+
565
+ if (currentPhase && currentPhaseUuid !== existingPhaseUuid) {
566
+ currentPhase.inputPaths = existingPhase.inputPaths ?? [];
567
+ currentPhase.outputPaths = existingPhase.outputPaths ?? [];
568
+ currentPhase.shellPath = encodePbxString(frameworkShellPath);
569
+ currentPhase.shellScript = encodePbxString(frameworkShellScript);
570
+
571
+ if (existingPhase.showEnvVarsInLog !== undefined) {
572
+ currentPhase.showEnvVarsInLog = existingPhase.showEnvVarsInLog;
573
+ }
574
+
575
+ Logger.logDebug(
576
+ `Updated framework-specific "${buildPhaseName}" build phase on target ${target.name}`
577
+ );
578
+ return;
579
+ }
580
+
581
+ if (currentPhaseUuid === existingPhaseUuid) {
582
+ target.buildPhases.splice(targetPhaseIndex, 1);
583
+ } else {
584
+ return;
585
+ }
501
586
  }
587
+
588
+ const addedPhase = project.addBuildPhase(
589
+ [],
590
+ 'PBXShellScriptBuildPhase',
591
+ buildPhaseName,
592
+ targetUuid,
593
+ {
594
+ inputPaths: existingPhase.inputPaths ?? [],
595
+ outputPaths: existingPhase.outputPaths ?? [],
596
+ shellPath: frameworkShellPath,
597
+ shellScript: frameworkShellScript,
598
+ }
599
+ );
600
+
601
+ if (existingPhase.showEnvVarsInLog !== undefined) {
602
+ addedPhase.buildPhase.showEnvVarsInLog = existingPhase.showEnvVarsInLog;
603
+ }
604
+
605
+ Logger.logDebug(
606
+ `Added framework-specific "${buildPhaseName}" build phase to target ${target.name}`
607
+ );
502
608
  }
503
609
  }
504
610
  }
@@ -561,6 +667,10 @@ function resolveAppTargetName(
561
667
  return null;
562
668
  }
563
669
 
670
+ /**
671
+ * Adds the "Patch ExpoModulesProvider" shell script phase to the framework target.
672
+ * Safe to call on every prebuild: skips creation when the phase is already present.
673
+ */
564
674
  export function addExpoPre55ShellPatchScriptPhase(
565
675
  modRequest: ModProps<XcodeProject>,
566
676
  project: XcodeProject,
@@ -582,6 +692,16 @@ export function addExpoPre55ShellPatchScriptPhase(
582
692
  );
583
693
  }
584
694
 
695
+ const existingBuildPhases =
696
+ project.pbxNativeTargetSection()[frameworkTargetUUID]?.buildPhases ?? [];
697
+ if (
698
+ existingBuildPhases.some((phase: { comment?: string }) =>
699
+ hasBuildPhaseComment(phase, 'Patch ExpoModulesProvider')
700
+ )
701
+ ) {
702
+ return;
703
+ }
704
+
585
705
  project.addBuildPhase(
586
706
  [
587
707
  // no associated files
@@ -19,6 +19,10 @@ export class Logger {
19
19
  console.debug(`${LOG_TAG} ${message}`, ...args);
20
20
  }
21
21
 
22
+ static getIsDebug() {
23
+ return this.debug;
24
+ }
25
+
22
26
  static logWarning(message: string, ...args: any[]) {
23
27
  console.warn(`${LOG_TAG} ${message}`, ...args);
24
28
  }
@@ -2,7 +2,10 @@ import Foundation
2
2
  import ReactBrownfield
3
3
 
4
4
  // Initializes a Bundle instance that points at the framework target.
5
- public let ReactNativeBundle = Bundle(for: InternalClassForBundle.self)
5
+ public let ReactNativeBundle =
6
+ Bundle(identifier: "{{BUNDLE_IDENTIFIER}}")
7
+ ?? Bundle.allFrameworks.first { $0.bundleIdentifier == "{{BUNDLE_IDENTIFIER}}" }
8
+ ?? Bundle(for: InternalClassForBundle.self)
6
9
 
7
10
  class InternalClassForBundle {}
8
11