diff --git a/packages/react-native/scripts/replace-rncore-version.js b/packages/react-native/scripts/replace-rncore-version.js index b2bcad5d9141..98d4798c6609 100644 --- a/packages/react-native/scripts/replace-rncore-version.js +++ b/packages/react-native/scripts/replace-rncore-version.js @@ -12,6 +12,8 @@ const {spawnSync} = require('child_process'); const fs = require('fs'); +const os = require('os'); +const path = require('path'); const yargs = require('yargs'); const LAST_BUILD_FILENAME = 'React-Core-prebuilt/.last_build_configuration'; @@ -63,22 +65,77 @@ function replaceRNCoreConfiguration( const finalLocation = 'React-Core-prebuilt'; - // Delete all directories - not files, since we want to keep the React-VFS.yaml file - const dirs = fs - .readdirSync(finalLocation, {withFileTypes: true}) - .filter(dirent => dirent.isDirectory()); - for (const dirent of dirs) { - const direntName = - typeof dirent.name === 'string' ? dirent.name : dirent.name.toString(); - const dirPath = `${finalLocation}/${direntName}`; - console.log('Removing directory', dirPath); - fs.rmSync(dirPath, {force: true, recursive: true}); - } + // Extract to a temporary directory on a regular filesystem first, then move + // into the final location. This avoids issues with partial tar extraction on + // certain filesystems (e.g. EdenFS) where extracting directly can silently + // produce incomplete results. + const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'rncore-')); + const tmpExtractDir = path.join(tmpDir, 'React-Core-prebuilt'); + fs.mkdirSync(tmpExtractDir, {recursive: true}); + + try { + console.log('Extracting the tarball to temp dir', tarballURLPath); + const result = spawnSync( + 'tar', + ['-xf', tarballURLPath, '-C', tmpExtractDir], + { + stdio: 'inherit', + }, + ); + + if (result.status !== 0) { + throw new Error(`tar extraction failed with exit code ${result.status}`); + } - console.log('Extracting the tarball', tarballURLPath); - spawnSync('tar', ['-xf', tarballURLPath, '-C', finalLocation], { - stdio: 'inherit', - }); + // Verify extraction produced the expected xcframework structure + const xcfwPath = path.join(tmpExtractDir, 'React.xcframework'); + const modulemapPath = path.join(xcfwPath, 'Modules', 'module.modulemap'); + if (!fs.existsSync(modulemapPath)) { + throw new Error( + `Extraction verification failed: ${modulemapPath} not found`, + ); + } + + // Delete all directories in finalLocation - not files, since we want to + // keep the React-VFS.yaml file + const dirs = fs + .readdirSync(finalLocation, {withFileTypes: true}) + .filter(dirent => dirent.isDirectory()); + for (const dirent of dirs) { + const direntName = + typeof dirent.name === 'string' ? dirent.name : dirent.name.toString(); + const dirPath = `${finalLocation}/${direntName}`; + console.log('Removing directory', dirPath); + fs.rmSync(dirPath, {force: true, recursive: true}); + } + + // Move extracted directories from temp to final location + const extractedEntries = fs + .readdirSync(tmpExtractDir, {withFileTypes: true}) + .filter(dirent => dirent.isDirectory()); + for (const dirent of extractedEntries) { + const direntName = + typeof dirent.name === 'string' ? dirent.name : dirent.name.toString(); + const src = path.join(tmpExtractDir, direntName); + const dst = path.join(finalLocation, direntName); + const mvResult = spawnSync('mv', [src, dst], {stdio: 'inherit'}); + if (mvResult.status !== 0) { + // Fallback: copy recursively then remove source + console.log(`mv failed for ${direntName}, falling back to cp -R`); + const cpResult = spawnSync('cp', ['-R', src, dst], { + stdio: 'inherit', + }); + if (cpResult.status !== 0) { + throw new Error( + `cp fallback failed with exit code ${cpResult.status}`, + ); + } + } + } + } finally { + // Clean up temp directory + fs.rmSync(tmpDir, {force: true, recursive: true}); + } } function updateLastBuildConfiguration(configuration /*: string */) {