From ecaac38bc166cdf567ad1c42c5eed089b42c8b73 Mon Sep 17 00:00:00 2001 From: Riccardo Cipolleschi Date: Fri, 27 Feb 2026 10:38:00 +0000 Subject: [PATCH] Fix silent tar extraction failure on EdenFS in replace-rncore-version Extract tarball to a temporary directory on a regular filesystem first, then move results into the final location. This avoids EdenFS silently producing incomplete tar extractions. Also adds exit code checking and extraction verification. Adapted from D94625297 for the current main branch. --- .../scripts/replace-rncore-version.js | 87 +++++++++++++++---- 1 file changed, 72 insertions(+), 15 deletions(-) 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 */) {