@@ -59,7 +59,7 @@ function replaceRNCoreConfiguration(
5959 configuration /*: string */ ,
6060 version /*: string */ ,
6161 podsRoot /*: string */ ,
62- ) {
62+ ) /*: void */ {
6363 // Filename comes from rncore.rb
6464 const tarballURLPath = `${ podsRoot } /ReactNativeCore-artifacts/reactnative-core-${ version . toLowerCase ( ) } -${ configuration . toLowerCase ( ) } .tar.gz` ;
6565
@@ -73,18 +73,6 @@ function replaceRNCoreConfiguration(
7373 const tmpExtractDir = path . join ( tmpDir , 'React-Core-prebuilt' ) ;
7474 fs . mkdirSync ( tmpExtractDir , { recursive : true } ) ;
7575
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-
8876 try {
8977 console . log ( 'Extracting the tarball to temp dir' , tarballURLPath ) ;
9078 const result = spawnSync (
@@ -110,98 +98,30 @@ function replaceRNCoreConfiguration(
11098 ) ;
11199 }
112100
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 } ` ) ;
147121 }
148122 }
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 } ) ;
194123 } finally {
195- // Clean up temp directory
196124 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- }
205125 }
206126}
207127
@@ -252,4 +172,8 @@ const version = argv.reactNativeVersion;
252172// $FlowFixMe[prop-missing]
253173const podsRoot = argv . podsRoot ;
254174
255- main ( configuration , version , podsRoot ) ;
175+ if ( require . main === module ) {
176+ main ( configuration , version , podsRoot ) ;
177+ }
178+
179+ module . exports = { replaceRNCoreConfiguration} ;
0 commit comments