react-native 0.87.0-rc.2 → 0.87.0-rc.4

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 (96) hide show
  1. package/Libraries/Core/ReactNativeVersion.js +1 -1
  2. package/React/Base/RCTVersion.m +1 -1
  3. package/ReactAndroid/gradle.properties +1 -1
  4. package/ReactAndroid/src/main/java/com/facebook/react/modules/systeminfo/ReactNativeVersion.kt +1 -1
  5. package/ReactCommon/cxxreact/ReactNativeVersion.h +1 -1
  6. package/ReactCommon/jsinspector-modern/HostTargetTraceRecording.cpp +10 -7
  7. package/ReactCommon/jsinspector-modern/tracing/HostTracingProfile.h +14 -1
  8. package/ReactCommon/jsinspector-modern/tracing/TraceRecordingState.h +11 -0
  9. package/ReactCommon/react/nativemodule/dom/NativeDOM.cpp +10 -4
  10. package/package.json +10 -8
  11. package/scripts/cocoapods/rncore.rb +1 -0
  12. package/scripts/replace-rncore-version.js +51 -127
  13. package/scripts/setup-apple-spm.js +111 -9
  14. package/scripts/spm/__doc__/spm-scripts.md +36 -1
  15. package/scripts/spm/autolinking-plugins.js +2 -2
  16. package/scripts/spm/download-spm-artifacts.js +3 -3
  17. package/scripts/spm/generate-spm-autolinking-config.js +62 -2
  18. package/scripts/spm/generate-spm-autolinking.js +1 -1
  19. package/scripts/spm/generate-spm-xcodeproj.js +54 -12
  20. package/scripts/spm/scaffold-package-swift.js +3 -3
  21. package/scripts/spm/spm-types.js +3 -0
  22. package/types_generated/Libraries/ActionSheetIOS/ActionSheetIOS.d.ts +7 -7
  23. package/types_generated/Libraries/Alert/Alert.d.ts +6 -6
  24. package/types_generated/Libraries/Animated/AnimatedEvent.d.ts +2 -2
  25. package/types_generated/Libraries/Animated/AnimatedImplementation.d.ts +3 -3
  26. package/types_generated/Libraries/Animated/animations/Animation.d.ts +7 -7
  27. package/types_generated/Libraries/Animated/animations/DecayAnimation.d.ts +5 -5
  28. package/types_generated/Libraries/Animated/animations/SpringAnimation.d.ts +49 -49
  29. package/types_generated/Libraries/Animated/animations/TimingAnimation.d.ts +13 -13
  30. package/types_generated/Libraries/Animated/components/AnimatedFlatList.d.ts +3 -3
  31. package/types_generated/Libraries/Animated/components/AnimatedSectionList.d.ts +3 -3
  32. package/types_generated/Libraries/Animated/createAnimatedComponent.d.ts +4 -4
  33. package/types_generated/Libraries/Animated/nodes/AnimatedColor.d.ts +2 -2
  34. package/types_generated/Libraries/Animated/nodes/AnimatedInterpolation.d.ts +9 -9
  35. package/types_generated/Libraries/Animated/nodes/AnimatedNode.d.ts +3 -3
  36. package/types_generated/Libraries/Blob/BlobTypes.d.ts +4 -4
  37. package/types_generated/Libraries/Components/AccessibilityInfo/AccessibilityInfo.d.ts +3 -3
  38. package/types_generated/Libraries/Components/ActivityIndicator/ActivityIndicator.d.ts +3 -3
  39. package/types_generated/Libraries/Components/Button.d.ts +5 -5
  40. package/types_generated/Libraries/Components/Keyboard/KeyboardAvoidingView.d.ts +3 -3
  41. package/types_generated/Libraries/Components/Pressable/Pressable.d.ts +5 -5
  42. package/types_generated/Libraries/Components/Pressable/useAndroidRippleForView.d.ts +6 -6
  43. package/types_generated/Libraries/Components/ProgressBarAndroid/ProgressBarAndroid.d.ts +3 -3
  44. package/types_generated/Libraries/Components/SafeAreaView/SafeAreaView.d.ts +3 -3
  45. package/types_generated/Libraries/Components/ScrollView/ScrollView.d.ts +20 -20
  46. package/types_generated/Libraries/Components/ScrollView/ScrollViewStickyHeader.d.ts +4 -4
  47. package/types_generated/Libraries/Components/Switch/Switch.d.ts +3 -3
  48. package/types_generated/Libraries/Components/TextInput/TextInput.flow.d.ts +5 -5
  49. package/types_generated/Libraries/Components/Touchable/TouchableHighlight.d.ts +4 -4
  50. package/types_generated/Libraries/Components/Touchable/TouchableOpacity.d.ts +3 -3
  51. package/types_generated/Libraries/Components/Touchable/TouchableWithoutFeedback.d.ts +2 -2
  52. package/types_generated/Libraries/Components/View/View.d.ts +3 -3
  53. package/types_generated/Libraries/Components/View/ViewAccessibility.d.ts +12 -12
  54. package/types_generated/Libraries/Components/View/ViewPropTypes.d.ts +10 -10
  55. package/types_generated/Libraries/Core/ExtendedError.d.ts +10 -10
  56. package/types_generated/Libraries/Image/ImageProps.d.ts +13 -13
  57. package/types_generated/Libraries/Image/ImageTypes.flow.d.ts +5 -5
  58. package/types_generated/Libraries/Lists/FlatList.d.ts +13 -13
  59. package/types_generated/Libraries/Lists/SectionList.d.ts +4 -4
  60. package/types_generated/Libraries/LogBox/Data/LogBoxData.d.ts +2 -2
  61. package/types_generated/Libraries/LogBox/Data/LogBoxLog.d.ts +2 -2
  62. package/types_generated/Libraries/LogBox/Data/parseLogBoxLog.d.ts +2 -2
  63. package/types_generated/Libraries/Modal/Modal.d.ts +4 -4
  64. package/types_generated/Libraries/Network/FormData.d.ts +5 -5
  65. package/types_generated/Libraries/PermissionsAndroid/PermissionsAndroid.d.ts +4 -4
  66. package/types_generated/Libraries/Pressability/Pressability.d.ts +5 -5
  67. package/types_generated/Libraries/PushNotificationIOS/PushNotificationIOS.d.ts +11 -11
  68. package/types_generated/Libraries/ReactNative/AppRegistry.flow.d.ts +4 -4
  69. package/types_generated/Libraries/ReactNative/IPerformanceLogger.flow.d.ts +5 -5
  70. package/types_generated/Libraries/Renderer/shims/ReactNativeTypes.d.ts +34 -34
  71. package/types_generated/Libraries/Share/Share.d.ts +11 -11
  72. package/types_generated/Libraries/StyleSheet/PlatformColorValueTypesIOS.d.ts +3 -3
  73. package/types_generated/Libraries/StyleSheet/StyleSheetTypes.d.ts +182 -182
  74. package/types_generated/Libraries/StyleSheet/private/_TransformStyle.d.ts +3 -3
  75. package/types_generated/Libraries/Text/Text.d.ts +3 -3
  76. package/types_generated/Libraries/Text/TextProps.d.ts +5 -5
  77. package/types_generated/Libraries/TurboModule/RCTExport.d.ts +2 -2
  78. package/types_generated/Libraries/Types/CoreEventTypes.d.ts +8 -8
  79. package/types_generated/Libraries/Utilities/PlatformTypes.d.ts +6 -6
  80. package/types_generated/Libraries/Utilities/codegenNativeComponent.d.ts +5 -5
  81. package/types_generated/Libraries/Utilities/differ/deepDiffer.d.ts +2 -2
  82. package/types_generated/src/private/assets/AssetRegistry.d.ts +2 -2
  83. package/types_generated/src/private/components/virtualcollection/VirtualCollectionView.d.ts +5 -5
  84. package/types_generated/src/private/components/virtualcollection/column/VirtualColumn.d.ts +3 -3
  85. package/types_generated/src/private/components/virtualcollection/row/VirtualRow.d.ts +3 -3
  86. package/types_generated/src/private/components/virtualview/VirtualView.d.ts +7 -7
  87. package/types_generated/src/private/components/virtualview/VirtualViewExperimentalNativeComponent.d.ts +5 -5
  88. package/types_generated/src/private/components/virtualview/VirtualViewNativeComponent.d.ts +5 -5
  89. package/types_generated/src/private/specs_DEPRECATED/modules/NativeDeviceInfo.d.ts +7 -7
  90. package/types_generated/src/private/specs_DEPRECATED/modules/NativeExceptionsManager.d.ts +5 -5
  91. package/types_generated/src/private/specs_DEPRECATED/modules/NativeUIManager.d.ts +8 -8
  92. package/types_generated/src/private/types/HostComponent.d.ts +3 -3
  93. package/types_generated/src/private/webapis/dom/events/CustomEvent.d.ts +2 -2
  94. package/types_generated/src/private/webapis/dom/events/Event.d.ts +4 -4
  95. package/types_generated/src/private/webapis/dom/events/EventTarget.d.ts +8 -8
  96. package/types_generated/src/private/webapis/dom/nodes/specs/NativeDOM.d.ts +3 -3
@@ -29,7 +29,7 @@ export default class ReactNativeVersion {
29
29
  static major: number = 0;
30
30
  static minor: number = 87;
31
31
  static patch: number = 0;
32
- static prerelease: string | null = 'rc.2';
32
+ static prerelease: string | null = 'rc.4';
33
33
 
34
34
  static getVersionString(): string {
35
35
  return `${this.major}.${this.minor}.${this.patch}${this.prerelease != null ? `-${this.prerelease}` : ''}`;
@@ -24,7 +24,7 @@ NSDictionary* RCTGetReactNativeVersion(void)
24
24
  RCTVersionMajor: @(0),
25
25
  RCTVersionMinor: @(87),
26
26
  RCTVersionPatch: @(0),
27
- RCTVersionPrerelease: @"rc.2",
27
+ RCTVersionPrerelease: @"rc.4",
28
28
  };
29
29
  });
30
30
  return __rnVersion;
@@ -1,4 +1,4 @@
1
- VERSION_NAME=0.87.0-rc.2
1
+ VERSION_NAME=0.87.0-rc.4
2
2
  react.internal.publishingGroup=com.facebook.react
3
3
  react.internal.hermesPublishingGroup=com.facebook.hermes
4
4
 
@@ -15,6 +15,6 @@ public object ReactNativeVersion {
15
15
  "major" to 0,
16
16
  "minor" to 87,
17
17
  "patch" to 0,
18
- "prerelease" to "rc.2"
18
+ "prerelease" to "rc.4"
19
19
  )
20
20
  }
@@ -22,7 +22,7 @@ struct ReactNativeVersionType {
22
22
  int32_t Major = 0;
23
23
  int32_t Minor = 87;
24
24
  int32_t Patch = 0;
25
- std::string_view Prerelease = "rc.2";
25
+ std::string_view Prerelease = "rc.4";
26
26
  };
27
27
 
28
28
  constexpr ReactNativeVersionType ReactNativeVersion;
@@ -64,13 +64,16 @@ tracing::HostTracingProfile HostTargetTraceRecording::stop() {
64
64
  auto startTime = *startTime_;
65
65
  startTime_.reset();
66
66
 
67
- return tracing::HostTracingProfile{
68
- .processId = oscompat::getCurrentProcessId(),
69
- .startTime = startTime,
70
- .frameTimings = frameTimings_.pruneExpiredAndExtract(),
71
- .instanceTracingProfiles = std::move(state.instanceTracingProfiles),
72
- .runtimeSamplingProfiles = std::move(state.runtimeSamplingProfiles),
73
- };
67
+ // Member-wise assignment instead of designated initializers:
68
+ // HostTracingProfile declares its special members (move-only) and is no
69
+ // longer an aggregate.
70
+ tracing::HostTracingProfile profile;
71
+ profile.processId = oscompat::getCurrentProcessId();
72
+ profile.startTime = startTime;
73
+ profile.frameTimings = frameTimings_.pruneExpiredAndExtract();
74
+ profile.instanceTracingProfiles = std::move(state.instanceTracingProfiles);
75
+ profile.runtimeSamplingProfiles = std::move(state.runtimeSamplingProfiles);
76
+ return profile;
74
77
  }
75
78
 
76
79
  void HostTargetTraceRecording::recordFrameTimings(
@@ -23,9 +23,22 @@ namespace facebook::react::jsinspector_modern::tracing {
23
23
  * messages.
24
24
  */
25
25
  struct HostTracingProfile {
26
+ HostTracingProfile() = default;
27
+
28
+ // Explicitly move-only: FrameTimingSequence and RuntimeSamplingProfile are
29
+ // not copyable, so the implicit copy constructor is ill-formed the moment it
30
+ // is instantiated. Plain C++ never instantiates it, but Swift's C++ interop
31
+ // does when these headers are reached from an imported module, turning it
32
+ // into a hard compile error (Xcode 26.3).
33
+ HostTracingProfile(const HostTracingProfile &) = delete;
34
+ HostTracingProfile &operator=(const HostTracingProfile &) = delete;
35
+ HostTracingProfile(HostTracingProfile &&) = default;
36
+ HostTracingProfile &operator=(HostTracingProfile &&) = default;
37
+ ~HostTracingProfile() = default;
38
+
26
39
  // The ID of the OS-level process that this Trace Recording is associated
27
40
  // with.
28
- ProcessId processId;
41
+ ProcessId processId{};
29
42
 
30
43
  // The timestamp at which this Trace Recording started.
31
44
  HighResTimeStamp startTime;
@@ -31,6 +31,17 @@ struct TraceRecordingState {
31
31
  {
32
32
  }
33
33
 
34
+ // Explicitly move-only: RuntimeSamplingProfile is not copyable, so the
35
+ // implicit copy constructor is ill-formed the moment it is instantiated.
36
+ // Plain C++ never instantiates it, but Swift's C++ interop (ClangImporter)
37
+ // does when a consumer imports these headers as part of a module, turning
38
+ // it into a hard compile error (Xcode 26.3).
39
+ TraceRecordingState(const TraceRecordingState &) = delete;
40
+ TraceRecordingState &operator=(const TraceRecordingState &) = delete;
41
+ TraceRecordingState(TraceRecordingState &&) = default;
42
+ TraceRecordingState &operator=(TraceRecordingState &&) = default;
43
+ ~TraceRecordingState() = default;
44
+
34
45
  // The mode of this Trace Recording.
35
46
  tracing::Mode mode;
36
47
 
@@ -198,10 +198,6 @@ jsi::Value NativeDOM::getParentNode(
198
198
  }
199
199
 
200
200
  auto shadowNode = getShadowNode(rt, nativeNodeReference);
201
- if (isRootShadowNode(*shadowNode)) {
202
- // The parent of the root node is the document.
203
- return jsi::Value{shadowNode->getSurfaceId()};
204
- }
205
201
 
206
202
  auto currentRevision =
207
203
  getCurrentShadowTreeRevision(rt, shadowNode->getSurfaceId());
@@ -209,6 +205,16 @@ jsi::Value NativeDOM::getParentNode(
209
205
  return jsi::Value::undefined();
210
206
  }
211
207
 
208
+ // The parent of the surface's root node is the document. Only the actual
209
+ // root node qualifies: nested nodes that carry the `RootNodeKind` trait
210
+ // (e.g. <Modal>, portals/overlays) still have a real parent in the shadow
211
+ // tree and must report it. Otherwise capture/bubble event propagation is
212
+ // silently severed at that boundary (a listener on an ancestor rendered
213
+ // above the modal would never receive descendant focus/blur, etc.).
214
+ if (ShadowNode::sameFamily(*currentRevision, *shadowNode)) {
215
+ return jsi::Value{shadowNode->getSurfaceId()};
216
+ }
217
+
212
218
  auto parentShadowNode = dom::getParentNode(currentRevision, *shadowNode);
213
219
  if (parentShadowNode == nullptr) {
214
220
  return jsi::Value::undefined();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "react-native",
3
- "version": "0.87.0-rc.2",
3
+ "version": "0.87.0-rc.4",
4
4
  "description": "A framework for building native apps using React",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -42,7 +42,9 @@
42
42
  "types": null,
43
43
  "default": "./Libraries/*.js"
44
44
  },
45
- "./scripts/*": "./scripts/*",
45
+ "./scripts/*": "./scripts/*.js",
46
+ "./scripts/*.sh": "./scripts/*.sh",
47
+ "./scripts/*.rb": "./scripts/*.rb",
46
48
  "./asset-registry": {
47
49
  "types": null,
48
50
  "default": "./src/asset-registry.js"
@@ -148,12 +150,12 @@
148
150
  }
149
151
  },
150
152
  "dependencies": {
151
- "@react-native/asset-utils": "0.87.0-rc.2",
152
- "@react-native/codegen": "0.87.0-rc.2",
153
- "@react-native/community-cli-plugin": "0.87.0-rc.2",
154
- "@react-native/gradle-plugin": "0.87.0-rc.2",
155
- "@react-native/normalize-colors": "0.87.0-rc.2",
156
- "@react-native/virtualized-lists": "0.87.0-rc.2",
153
+ "@react-native/asset-utils": "0.87.0-rc.4",
154
+ "@react-native/codegen": "0.87.0-rc.4",
155
+ "@react-native/community-cli-plugin": "0.87.0-rc.4",
156
+ "@react-native/gradle-plugin": "0.87.0-rc.4",
157
+ "@react-native/normalize-colors": "0.87.0-rc.4",
158
+ "@react-native/virtualized-lists": "0.87.0-rc.4",
157
159
  "anser": "^1.4.9",
158
160
  "ansi-regex": "^5.0.0",
159
161
  "babel-plugin-syntax-hermes-parser": "0.36.1",
@@ -568,6 +568,7 @@ class ReactNativeCoreUtils
568
568
  # Quoted so a $(PODS_ROOT) containing spaces stays a single clang argument.
569
569
  module_map_flag = " \"-fmodule-map-file=$(PODS_ROOT)/React-Core-prebuilt/Headers/module.modulemap\""
570
570
  ReactNativePodsUtils.add_flag_to_map_with_inheritance(attributes, "OTHER_CFLAGS", module_map_flag)
571
+ ReactNativePodsUtils.add_flag_to_map_with_inheritance(attributes, "OTHER_CPLUSPLUSFLAGS", module_map_flag)
571
572
  ReactNativePodsUtils.add_flag_to_map_with_inheritance(attributes, "OTHER_SWIFT_FLAGS", " -Xcc" + module_map_flag)
572
573
  end
573
574
  end
@@ -59,7 +59,7 @@ function replaceRNCoreConfiguration(
59
59
  configuration /*: string */,
60
60
  version /*: string */,
61
61
  podsRoot /*: string */,
62
- ) {
62
+ ) /*: void */ {
63
63
  // Filename comes from rncore.rb
64
64
  const tarballURLPath = `${podsRoot}/ReactNativeCore-artifacts/reactnative-core-${version.toLowerCase()}-${configuration.toLowerCase()}.tar.gz`;
65
65
 
@@ -73,18 +73,6 @@ function replaceRNCoreConfiguration(
73
73
  const tmpExtractDir = path.join(tmpDir, 'React-Core-prebuilt');
74
74
  fs.mkdirSync(tmpExtractDir, {recursive: true});
75
75
 
76
- // Preserve Expo-generated modulemap before replacing directories
77
- const useFrameworksModulemapName = 'React-use-frameworks.modulemap';
78
- const useFrameworksModulemapPath = path.join(
79
- finalLocation,
80
- useFrameworksModulemapName,
81
- );
82
- let savedModulemap = null;
83
- if (fs.existsSync(useFrameworksModulemapPath)) {
84
- console.log('Preserving', useFrameworksModulemapName);
85
- savedModulemap = fs.readFileSync(useFrameworksModulemapPath);
86
- }
87
-
88
76
  try {
89
77
  console.log('Extracting the tarball to temp dir', tarballURLPath);
90
78
  const result = spawnSync(
@@ -110,98 +98,30 @@ function replaceRNCoreConfiguration(
110
98
  );
111
99
  }
112
100
 
113
- // Delete only directories in finalLocation (e.g. the React.xcframework) -
114
- // not files, so any sibling files written during pod install are preserved.
115
- const dirs = fs
116
- .readdirSync(finalLocation, {withFileTypes: true})
117
- .filter(dirent => dirent.isDirectory());
118
- for (const dirent of dirs) {
119
- const direntName =
120
- typeof dirent.name === 'string' ? dirent.name : dirent.name.toString();
121
- const dirPath = `${finalLocation}/${direntName}`;
122
- console.log('Removing directory', dirPath);
123
- fs.rmSync(dirPath, {force: true, recursive: true});
124
- }
125
-
126
- // Move extracted directories from temp to final location
127
- const extractedEntries = fs
128
- .readdirSync(tmpExtractDir, {withFileTypes: true})
129
- .filter(dirent => dirent.isDirectory());
130
- for (const dirent of extractedEntries) {
131
- const direntName =
132
- typeof dirent.name === 'string' ? dirent.name : dirent.name.toString();
133
- const src = path.join(tmpExtractDir, direntName);
134
- const dst = path.join(finalLocation, direntName);
135
- const mvResult = spawnSync('mv', [src, dst], {stdio: 'inherit'});
136
- if (mvResult.status !== 0) {
137
- // Fallback: copy recursively then remove source
138
- console.log(`mv failed for ${direntName}, falling back to cp -R`);
139
- const cpResult = spawnSync('cp', ['-R', src, dst], {
140
- stdio: 'inherit',
141
- });
142
- if (cpResult.status !== 0) {
143
- throw new Error(
144
- `cp fallback failed with exit code ${cpResult.status}`,
145
- );
146
- }
101
+ // Replace only the compiled framework. Headers/ is flattened from
102
+ // ReactNativeHeaders by the podspec prepare_command, and the prebuild
103
+ // compose job emits one set of those headers for both configurations, so a
104
+ // config switch leaves them identical. Leaving them alone keeps
105
+ // Headers/module.modulemap which consumers activate through
106
+ // -fmodule-map-file — in place for the whole build; deleting and recreating
107
+ // it mid-build lets a concurrent dependency scan miss it, and the React
108
+ // module then precompiles without it (#57803).
109
+ const dest = path.join(finalLocation, 'React.xcframework');
110
+ console.log('Replacing', dest);
111
+ fs.rmSync(dest, {force: true, recursive: true});
112
+ const mvResult = spawnSync('mv', [xcfwPath, dest], {stdio: 'inherit'});
113
+ if (mvResult.status !== 0) {
114
+ // Fallback: copy recursively then remove source
115
+ console.log('mv failed for React.xcframework, falling back to cp -R');
116
+ const cpResult = spawnSync('cp', ['-R', xcfwPath, dest], {
117
+ stdio: 'inherit',
118
+ });
119
+ if (cpResult.status !== 0) {
120
+ throw new Error(`cp fallback failed with exit code ${cpResult.status}`);
147
121
  }
148
122
  }
149
-
150
- // The podspec prepare_command flattens ReactNativeHeaders' headers into a
151
- // top-level Headers/ dir, but it does not re-run on a config swap. Mirror
152
- // it here: re-flatten the headers (identical across slices) and drop the
153
- // now-redundant xcframework so $(PODS_ROOT)/React-Core-prebuilt/Headers
154
- // keeps resolving <react/...>, <yoga/...>, etc.
155
- //
156
- // Fail closed when the swapped-in tarball lacks ReactNativeHeaders: the
157
- // directory purge above already deleted the previous Headers/, so
158
- // continuing silently would leave the injected -fmodule-map-file flag
159
- // dangling and break every <react/...> include only on a config switch —
160
- // with no pointer to the version-skewed artifact that caused it.
161
- const rnhXcfw = path.join(finalLocation, 'ReactNativeHeaders.xcframework');
162
- if (!fs.existsSync(rnhXcfw)) {
163
- throw new Error(
164
- `ReactNativeHeaders.xcframework not found in the extracted tarball at ${finalLocation}. ` +
165
- 'The downloaded artifact predates the headers-spec layout (or is incomplete); ' +
166
- 'use a prebuilt tarball matching this react-native version.',
167
- );
168
- }
169
- const slice = fs
170
- .readdirSync(rnhXcfw, {withFileTypes: true})
171
- .find(
172
- dirent =>
173
- dirent.isDirectory() &&
174
- fs.existsSync(path.join(rnhXcfw, dirent.name.toString(), 'Headers')),
175
- );
176
- if (!slice) {
177
- throw new Error(
178
- `No slice with a Headers directory found inside ${rnhXcfw}.`,
179
- );
180
- }
181
- const headersDest = path.join(finalLocation, 'Headers');
182
- fs.rmSync(headersDest, {force: true, recursive: true});
183
- const cpHeaders = spawnSync(
184
- 'cp',
185
- ['-R', path.join(rnhXcfw, slice.name.toString(), 'Headers'), headersDest],
186
- {stdio: 'inherit'},
187
- );
188
- if (cpHeaders.status !== 0) {
189
- throw new Error(
190
- `Flattening ReactNativeHeaders failed with exit code ${cpHeaders.status}`,
191
- );
192
- }
193
- fs.rmSync(rnhXcfw, {force: true, recursive: true});
194
123
  } finally {
195
- // Clean up temp directory
196
124
  fs.rmSync(tmpDir, {force: true, recursive: true});
197
-
198
- // Restore Expo-generated modulemap after directory replacement.
199
- // Runs in finally so it is not skipped if mv/cp partially fails.
200
- if (savedModulemap != null) {
201
- const restoredPath = path.join(finalLocation, useFrameworksModulemapName);
202
- fs.writeFileSync(restoredPath, savedModulemap);
203
- console.log('Restored', useFrameworksModulemapName);
204
- }
205
125
  }
206
126
  }
207
127
 
@@ -227,29 +147,33 @@ function main(
227
147
  console.log('Done replacing React Native prebuilt');
228
148
  }
229
149
 
230
- // This script is executed in the Pods folder, which is usually not synched to Github, so it should be ok
231
- const argv = yargs
232
- .option('c', {
233
- alias: 'configuration',
234
- description:
235
- 'Configuration to use to download the right React-Core prebuilt version. Allowed values are "Debug" and "Release".',
236
- })
237
- .option('r', {
238
- alias: 'reactNativeVersion',
239
- description:
240
- 'The Version of React Native associated with the React-Core prebuilt tarball.',
241
- })
242
- .option('p', {
243
- alias: 'podsRoot',
244
- description: 'The path to the Pods root folder',
245
- })
246
- .usage('Usage: $0 -c Debug -r <version> -p <path/to/react-native>').argv;
247
-
248
- // $FlowFixMe[prop-missing]
249
- const configuration = argv.configuration;
250
- // $FlowFixMe[prop-missing]
251
- const version = argv.reactNativeVersion;
252
- // $FlowFixMe[prop-missing]
253
- const podsRoot = argv.podsRoot;
254
-
255
- main(configuration, version, podsRoot);
150
+ if (require.main === module) {
151
+ // This script is executed in the Pods folder, which is usually not synched to Github, so it should be ok
152
+ const argv = yargs
153
+ .option('c', {
154
+ alias: 'configuration',
155
+ description:
156
+ 'Configuration to use to download the right React-Core prebuilt version. Allowed values are "Debug" and "Release".',
157
+ })
158
+ .option('r', {
159
+ alias: 'reactNativeVersion',
160
+ description:
161
+ 'The Version of React Native associated with the React-Core prebuilt tarball.',
162
+ })
163
+ .option('p', {
164
+ alias: 'podsRoot',
165
+ description: 'The path to the Pods root folder',
166
+ })
167
+ .usage('Usage: $0 -c Debug -r <version> -p <path/to/react-native>').argv;
168
+
169
+ // $FlowFixMe[prop-missing]
170
+ const configuration = argv.configuration;
171
+ // $FlowFixMe[prop-missing]
172
+ const version = argv.reactNativeVersion;
173
+ // $FlowFixMe[prop-missing]
174
+ const podsRoot = argv.podsRoot;
175
+
176
+ main(configuration, version, podsRoot);
177
+ }
178
+
179
+ module.exports = {replaceRNCoreConfiguration};
@@ -55,6 +55,7 @@
55
55
  * must contain debug/ and release/ cache slots.
56
56
  * [advanced] --download <auto|skip|force> Artifact policy (default: auto).
57
57
  * [advanced] --skip-codegen Skip the react-native codegen step.
58
+ * [advanced] --config-command <json> Override the autolinking config command.
58
59
  *
59
60
  * Steps performed (add/update):
60
61
  * 1. react-native codegen → build/generated/ios/ + install SPM codegen template
@@ -83,14 +84,19 @@ const {
83
84
  } = require('./spm/generate-spm-autolinking');
84
85
  const {
85
86
  generateAutolinkingConfig,
87
+ parseConfigCommandJson,
88
+ readEnvConfigCommand,
89
+ resolveEnvConfigCommand,
86
90
  } = require('./spm/generate-spm-autolinking-config');
87
91
  const {main: generatePackage} = require('./spm/generate-spm-package');
88
92
  const {findSourcePath} = require('./spm/generate-spm-package');
89
93
  const {
94
+ SPM_INJECTED_MARKER,
90
95
  cleanupDanglingJavaScriptCoreRef,
91
96
  cleanupLeftoverPodsGroup,
92
97
  findInjectedXcodeproj,
93
98
  injectSpmIntoExistingXcodeproj,
99
+ readPinnedConfigCommand,
94
100
  removeSpmInjection,
95
101
  } = require('./spm/generate-spm-xcodeproj');
96
102
  const {scaffoldAll} = require('./spm/scaffold-package-swift');
@@ -187,6 +193,11 @@ function parseArgs(argv /*: Array<string> */) /*: SetupArgs */ {
187
193
  default: false,
188
194
  describe: '[advanced] Skip the react-native codegen step',
189
195
  })
196
+ .option('config-command', {
197
+ type: 'string',
198
+ describe:
199
+ '[advanced] JSON array of the argv used to generate autolinking.json, overriding the default @react-native-community/cli config command. Also settable via RCT_SPM_AUTOLINKING_CONFIG_COMMAND. Either way `add`/`update` remembers the value in .spm-injected.json, so later runs and Xcode builds reuse it. Example: \'["npx","expo-modules-autolinking","react-native-config","--json","--platform","ios"]\'',
200
+ })
190
201
  .usage(
191
202
  'Usage: $0 [action] [options]\n\nSets up Swift Package Manager support in a React Native app.',
192
203
  )
@@ -214,6 +225,10 @@ function parseArgs(argv /*: Array<string> */) /*: SetupArgs */ {
214
225
  version: parsed.version ?? null,
215
226
  artifacts: parsed.artifacts ?? null,
216
227
  skipCodegen: parsed['skip-codegen'],
228
+ configCommand:
229
+ parsed['config-command'] != null
230
+ ? parseConfigCommandJson(parsed['config-command'], '--config-command')
231
+ : null,
217
232
  downloadPolicy: parsed.download,
218
233
  productName: parsed['product-name'] ?? null,
219
234
  xcodeprojPath: parsed.xcodeproj ?? null,
@@ -833,6 +848,7 @@ async function setupXcodeproj(
833
848
  // (injectSpmIntoExistingXcodeproj preserves it — see
834
849
  // generate-spm-xcodeproj.js).
835
850
  artifactsVersionOverride: args.version ?? null,
851
+ configCommand: resolveConfigCommandToPin(args),
836
852
  });
837
853
  if (result.status !== 'injected') {
838
854
  logError(`SPM injection failed: ${result.reason}`);
@@ -892,6 +908,88 @@ function logNextSteps(
892
908
  log('To remove SPM later: `npx react-native spm deinit`');
893
909
  }
894
910
 
911
+ // The autolinking config command for this run: an explicit `--config-command`
912
+ // first, then the value a previous `add`/`update` pinned into the injection
913
+ // marker. undefined means "no explicit command", which is what makes
914
+ // generateAutolinkingConfig fall back to RCT_SPM_AUTOLINKING_CONFIG_COMMAND and
915
+ // then to the built-in default — so the pin has to be WITHHELD while the env
916
+ // var is set, or a stale pin would outrank a developer's env override.
917
+ function resolveExplicitConfigCommand(
918
+ args /*: SetupArgs */,
919
+ appRoot /*: string */,
920
+ ) /*: Array<string> | void */ {
921
+ if (args.configCommand != null) {
922
+ return args.configCommand;
923
+ }
924
+ if (readEnvConfigCommand() != null) {
925
+ return undefined;
926
+ }
927
+ const pinned = readPinnedConfigCommand(appRoot);
928
+ if (pinned == null) {
929
+ return undefined;
930
+ }
931
+ log(
932
+ `Autolinking config command (pinned in ${SPM_INJECTED_MARKER}): ` +
933
+ pinned.join(' '),
934
+ );
935
+ return pinned;
936
+ }
937
+
938
+ // The command to record in the injection marker. The env var is resolved here
939
+ // too, because the Xcode build phase inherits neither the flag nor the shell
940
+ // that set it — an env-only override that went unpinned would leave the build
941
+ // re-deriving autolinking.json with the default command. null pins nothing and
942
+ // preserves any earlier pin. An invalid env value throws, as the flag does,
943
+ // though `add` has already failed closed on it by this point.
944
+ function resolveConfigCommandToPin(
945
+ args /*: SetupArgs */,
946
+ ) /*: ?Array<string> */ {
947
+ return args.configCommand ?? resolveEnvConfigCommand();
948
+ }
949
+
950
+ // Generate autolinking.json, failing closed on a config-command error.
951
+ //
952
+ // generateAutolinkingConfig throws ONLY when the config command itself fails —
953
+ // a non-zero exit, unparseable output, or a config missing
954
+ // project.ios.sourceDir. Swallowing that (the old behavior) let the run proceed
955
+ // and emit an empty Autolinked package, which only surfaced much later as an
956
+ // inscrutable `unable to resolve module dependency` at build time. Instead we
957
+ // set process.exitCode = 2 (a hard Xcode build-phase error, matching the
958
+ // RemoteVersionError path) and return null so the caller stops.
959
+ //
960
+ // A genuinely native-module-free app does NOT reach the error path: its command
961
+ // exits 0 with valid, empty-dependency JSON, so generateAutolinkingConfig
962
+ // returns normally and the empty-package path downstream stays valid.
963
+ function generateAutolinkingConfigOrFailClosed(
964
+ opts /*: {
965
+ projectRoot: string,
966
+ configCommand?: Array<string>,
967
+ generate?: typeof generateAutolinkingConfig,
968
+ } */,
969
+ ) /*: ?AutolinkingConfigResult */ {
970
+ const generate = opts.generate ?? generateAutolinkingConfig;
971
+ try {
972
+ return generate({
973
+ projectRoot: opts.projectRoot,
974
+ configCommand: opts.configCommand,
975
+ });
976
+ } catch (e) {
977
+ logError(
978
+ `Failed to generate autolinking.json: ${e.message}\n` +
979
+ 'The autolinking config command failed. If this app replaces ' +
980
+ '@react-native-community/cli autolinking (e.g. an Expo app), set ' +
981
+ 'RCT_SPM_AUTOLINKING_CONFIG_COMMAND (or pass --config-command) to a ' +
982
+ 'JSON argv array whose command prints the React Native CLI config, ' +
983
+ 'e.g. \'["npx","expo-modules-autolinking","react-native-config",' +
984
+ '"--json","--platform","ios"]\'. An earlier `add`/`update` may also ' +
985
+ `have pinned a command in ${SPM_INJECTED_MARKER}; re-run with ` +
986
+ '--config-command to replace a stale one.',
987
+ );
988
+ process.exitCode = 2;
989
+ return null;
990
+ }
991
+ }
992
+
895
993
  async function main(argv /*:: ?: Array<string> */) /*: Promise<void> */ {
896
994
  let appRoot = process.cwd();
897
995
  const projectRoot = findProjectRoot(appRoot);
@@ -981,16 +1079,16 @@ async function main(argv /*:: ?: Array<string> */) /*: Promise<void> */ {
981
1079
  let autolinkingConfigResult /*: ?AutolinkingConfigResult */ = null;
982
1080
  if (needsCliConfig) {
983
1081
  log('Generating autolinking.json (CLI config)...');
984
- try {
985
- autolinkingConfigResult = generateAutolinkingConfig({projectRoot});
986
- log(
987
- `Wrote ${path.relative(appRoot, autolinkingConfigResult.outputPath)}`,
988
- );
989
- } catch (e) {
990
- logError(
991
- `generate-spm-autolinking-config failed: ${e.message}. External native modules may not be discovered.`,
992
- );
1082
+ autolinkingConfigResult = generateAutolinkingConfigOrFailClosed({
1083
+ projectRoot,
1084
+ configCommand: resolveExplicitConfigCommand(args, appRoot),
1085
+ });
1086
+ if (autolinkingConfigResult == null) {
1087
+ // Fail closed: the config command errored and the helper already set
1088
+ // process.exitCode = 2. Stop rather than emit an empty Autolinked package.
1089
+ return;
993
1090
  }
1091
+ log(`Wrote ${path.relative(appRoot, autolinkingConfigResult.outputPath)}`);
994
1092
  }
995
1093
  const reactNativeRoot = resolveReactNativeRoot(
996
1094
  autolinkingConfigResult,
@@ -1159,7 +1257,11 @@ module.exports = {
1159
1257
  main,
1160
1258
  detectStandardRnLayoutRedirect,
1161
1259
  findInjectedXcodeproj,
1260
+ generateAutolinkingConfigOrFailClosed,
1261
+ parseArgs,
1162
1262
  resolveAction,
1263
+ resolveConfigCommandToPin,
1264
+ resolveExplicitConfigCommand,
1163
1265
  shouldAutoDeintegrate,
1164
1266
  ensureBothArtifactFlavors,
1165
1267
  };
@@ -137,6 +137,40 @@ accepts kebab-case equivalents (e.g. `--skip-codegen`).
137
137
  | `--artifacts <path>` | [advanced] Local artifact root containing complete `debug/` and `release/` cache slots |
138
138
  | `--download <auto\|skip\|force>` | [advanced] Artifact download policy (default: auto) |
139
139
  | `--skipCodegen` | [advanced] Skip the codegen step |
140
+ | `--configCommand <json>` | [advanced] JSON array of the argv used to generate `autolinking.json`, overriding the default `@react-native-community/cli config` command. Also settable via the `RCT_SPM_AUTOLINKING_CONFIG_COMMAND` env var. Either way the value is remembered, so you pass it once. Example: `'["npx","expo-modules-autolinking","react-native-config","--json","--platform","ios"]'` |
141
+
142
+ ### The autolinking config command is remembered
143
+
144
+ An app that replaces `@react-native-community/cli` autolinking (an Expo app,
145
+ for example) has to tell `spm` how to produce `autolinking.json`. Pass the
146
+ command once, on `add` or `update`:
147
+
148
+ ```bash
149
+ npx react-native spm add --configCommand '["npx","expo-modules-autolinking","react-native-config","--json","--platform","ios"]'
150
+ ```
151
+
152
+ Every action that needs `autolinking.json` — `add`, `update`, `scaffold`, and
153
+ the build-time `sync` — resolves the command in this order:
154
+
155
+ 1. `--configCommand`
156
+ 2. `RCT_SPM_AUTOLINKING_CONFIG_COMMAND`
157
+ 3. the `configCommand` pinned in `MyApp.xcodeproj/.spm-injected.json` by an
158
+ earlier `add`/`update`
159
+ 4. the default `@react-native-community/cli config`
160
+
161
+ `add`/`update` pin whichever of the first two routes supplied the command,
162
+ validated as an argv array; a later run that passes neither keeps the existing
163
+ pin, and passing `--configCommand` again replaces it. The pin exists because
164
+ the **Sync SPM Autolinking** build phase inherits neither your flag nor the
165
+ shell that exported the env var — without it, a successful `add` is followed by
166
+ failing builds, because the phase re-derives `autolinking.json` with the
167
+ default command. A pin never shadows the env var, so an override in your shell
168
+ still takes effect, and a pin that no longer parses is ignored in favor of the
169
+ default.
170
+
171
+ `deinit` deletes `.spm-injected.json`, and the pin with it. A later `add`
172
+ therefore falls back to the default command unless you pass `--configCommand`
173
+ (or export the env var) again.
140
174
 
141
175
  ### Debug/Release flavor is automatic
142
176
 
@@ -161,7 +195,7 @@ package graph, or require a second build.
161
195
  | Path | Commit? | Why |
162
196
  |------|---------|-----|
163
197
  | `MyApp.xcodeproj/` | Yes | Your project, with SwiftPM injected in place. Holds your signing, capabilities, Build Phases — `add` only adds SwiftPM refs/settings, additively. |
164
- | `MyApp.xcodeproj/.spm-injected.json` | Yes | Marker recording every edit `add` made, so `deinit` can surgically reverse it and re-runs stay idempotent. |
198
+ | `MyApp.xcodeproj/.spm-injected.json` | Yes | Marker recording every edit `add` made, so `deinit` can surgically reverse it and re-runs stay idempotent. Also pins settings later runs and Xcode builds must reuse, such as the [autolinking config command](#the-autolinking-config-command-is-remembered). |
165
199
  | `build/generated/` | No | Codegen/autolinking output; regenerated |
166
200
  | `build/xcframeworks/` | No | Symlinks to the machine-local artifact cache |
167
201
  | `Package.resolved` | No | SwiftPM resolution file; machine-specific |
@@ -335,6 +369,7 @@ across apps; refresh it with `react-native spm update --download force`.
335
369
  | "not contained in target" | Re-run setup (regenerates file-level symlinks) |
336
370
  | Codegen fails | Use `--skipCodegen` to iterate on other parts |
337
371
  | "SPM sync failed" warning | Check Xcode build log for details; node may not be in PATH — ensure `with-environment.sh` is present |
372
+ | "Sync SPM Autolinking" build phase fails: `'npx --no-install @react-native-community/cli config' exited with status 1` | This app replaces `@react-native-community/cli` autolinking (e.g. an Expo app). Re-run `spm add`/`update` with `--configCommand` (or with `RCT_SPM_AUTOLINKING_CONFIG_COMMAND` exported) so the working command is pinned for the build phase to reuse — see [The autolinking config command is remembered](#the-autolinking-config-command-is-remembered). |
338
373
  | Autolinking not updating on build | Touch `package.json` to force a sync, or delete `build/generated/autolinking/.spm-sync-stamp` |
339
374
  | Stale SwiftPM state or corrupted build | `rm -rf build/ .build/`, then `react-native spm update`, then reopen Xcode |
340
375
  | Want to revert to CocoaPods | `react-native spm deinit`, then `pod install` |