Files
react-native/packages/react-native/scripts/ios-prebuild/reactNativeDependencies.js
T
Riccardo CipolleschiandMoti Zilberman 31fdd536ca Add the ENTERPRISE_REPOSITORY env var to let user consume artifacts from their personal maven mirror (#52514)
Summary:
Pull Request resolved: https://github.com/facebook/react-native/pull/52514

As per title, this change add the `ENTERPRISE_REPOSITORY` env variable so that users can use their owm maven mirror to consume artifacts rather than the official url.

This is helpful as:
- we can reduce the traffic toward maven central
- companies can speed up their builds by relying on local/closer replicas

## Changelog:
[iOS][Added] - Add the `ENTERPRISE_REPOSITORY` env variable to cocopaods infra

Reviewed By: cortinico

Differential Revision: D78011424

fbshipit-source-id: 24b83b4866095d7fe3318362afc5075b99b146e7
2025-07-14 11:43:20 +01:00

375 lines
12 KiB
JavaScript

/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow
* @format
*/
/*:: import type {BuildFlavor} from './types'; */
const {computeNightlyTarballURL, createLogger} = require('./utils');
const {execSync} = require('child_process');
const fs = require('fs');
const path = require('path');
const stream = require('stream');
const {promisify} = require('util');
const pipeline = promisify(stream.pipeline);
const dependencyLog = createLogger('ReactNativeDependencies');
/**
* Downloads ReactNativeDependencies artifacts from the specified version and build type. If you want to specify a specific
* version of ReactNativeDependencies, use the RN_DEP_VERSION environment variable. The path to the artifacts will be inside
* the packages/react-native/third-party folder.
*/
async function prepareReactNativeDependenciesArtifactsAsync(
version /*:string*/,
buildType /*: BuildFlavor */,
) /*: Promise<string> */ {
dependencyLog(`Preparing ReactNativeDependencies...`);
// Create artifacts folder
const artifactsPath /*: string*/ = path.resolve(process.cwd(), 'third-party');
// Ensure that the artifacts folder exists
fs.mkdirSync(artifactsPath, {recursive: true});
// Path for keeping track of the current version in the artifacts folder
const versionFilePath = path.join(artifactsPath, 'version.txt');
// Resolve the version from the environment variable or use the default version
let resolvedVersion = process.env.RN_DEP_VERSION ?? version;
if (resolvedVersion === 'nightly') {
dependencyLog('Using latest nightly tarball');
const rnVersion = await getNightlyVersionFromNPM();
resolvedVersion = rnVersion;
}
// Check if the ReactNativeDependencies artifacts are already downloaded
if (
checkExistingVersion(
versionFilePath,
resolvedVersion,
buildType,
artifactsPath,
)
) {
return artifactsPath;
}
const sourceType = await reactNativeDependenciesSourceType(
resolvedVersion,
buildType,
);
const localPath = await resolveSourceFromSourceType(
sourceType,
resolvedVersion,
buildType,
artifactsPath,
);
// Extract the tar.gz
const tmpPath = '/tmp/react-native-dependencies';
fs.mkdirSync(tmpPath, {recursive: true});
execSync(`tar -xzf "${localPath}" -C "${tmpPath}"`, {
stdio: 'inherit',
});
const xcframeworkSource = path.join(
tmpPath,
'packages',
'react-native',
'third-party',
'ReactNativeDependencies.xcframework',
);
// Copy the extracted files to the artifacts folder
execSync(`cp -R "${xcframeworkSource}" "${artifactsPath}"`, {
stdio: 'inherit',
});
// Delete the tarball after extraction
if (!process.env.HERMES_ENGINE_TARBALL_PATH) {
fs.unlinkSync(localPath);
}
return artifactsPath;
}
async function getNightlyVersionFromNPM() /*: Promise<string> */ {
const npmResponse /*: Response */ = await fetch(
'https://registry.npmjs.org/react-native/nightly',
);
if (!npmResponse.ok) {
throw new Error(
`Couldn't get an answer from NPM: ${npmResponse.status} ${npmResponse.statusText}`,
);
}
const json = await npmResponse.json();
const latestNightly = json.version;
dependencyLog(`Using version ${latestNightly}`);
return latestNightly;
}
/*::
type ReactNativeDependenciesEngineSourceType =
| 'download_prebuild_tarball'
| 'download_prebuilt_nightly_tarball'
*/
const ReactNativeDependenciesEngineSourceTypes = {
DOWNLOAD_PREBUILD_TARBALL: 'download_prebuild_tarball',
DOWNLOAD_PREBUILT_NIGHTLY_TARBALL: 'download_prebuilt_nightly_tarball',
} /*:: as const */;
/**
* Checks if the ReactNativeDependencies artifacts are already downloaded and up to date with the specified version.
* Returns true if the artifacts are up to date, false otherwise.
*/
function checkExistingVersion(
versionFilePath /*: string */,
version /*: string */,
buildType /*: BuildFlavor */,
artifactsPath /*: string */,
) {
const resolvedVersion = `${version}-${buildType}`;
const rndepXCFramework = path.join(
artifactsPath,
'ReactNativeDependencies.xcframework',
);
if (fs.existsSync(rndepXCFramework)) {
if (fs.existsSync(versionFilePath)) {
const versionFileContent = fs.readFileSync(versionFilePath, 'utf8');
dependencyLog(`Version found on disk: ${versionFileContent}`);
if (versionFileContent.trim() === resolvedVersion) {
dependencyLog(
`ReactNativeDependencies artifacts already downloaded and up to date: ${artifactsPath}`,
);
return true;
}
} else {
dependencyLog(
`React Native Dependencies found on disk at: ${artifactsPath}.\nNo version file has been found. We are going to use it anyway, but there might be some unexpected behaviors.`,
);
}
} else {
dependencyLog('React Native Dependencies not found on disk');
}
// If the version file does not exist or the version does not match, delete the artifacts folder
fs.rmSync(artifactsPath, {recursive: true, force: true});
// Lets create the version.txt file
fs.mkdirSync(artifactsPath, {recursive: true});
fs.writeFileSync(versionFilePath, resolvedVersion, 'utf8');
dependencyLog(
`ReactNativeDependencies artifacts folder created: ${artifactsPath} with version: ${resolvedVersion}`,
);
return false;
}
function getTarballUrl(
version /*: string */,
buildType /*: BuildFlavor */,
) /*: string */ {
// You can use the `ENTERPRISE_REPOSITORY` ariable to customise the base url from which artifacts will be downloaded.
// The mirror's structure must be the same of the Maven repo the react-native core team publishes on Maven Central.
const mavenRepoUrl =
process.env.ENTERPRISE_REPOSITORY ?? 'https://repo1.maven.org/maven2';
const namespace = 'com/facebook/react';
return `${mavenRepoUrl}/${namespace}/react-native-artifacts/${version}/react-native-artifacts-${version}-reactnative-dependencies-${buildType.toLowerCase()}.tar.gz`;
}
async function getNightlyTarballUrl(
version /*: string */,
buildType /*: BuildFlavor */,
) /*: Promise<string> */ {
const coordinate = 'react-native-artifacts';
const artifactName = `reactnative-dependencies-${buildType.toLowerCase()}.tar.gz`;
return await computeNightlyTarballURL(
version,
buildType,
coordinate,
artifactName,
);
}
/**
* Checks if a ReactNativeDependencies artifact exists at the given URL using fetch instead of curl
*/
async function reactNativeDependenciesArtifactExists(
tarballUrl /*: string */,
) /*: Promise<boolean> */ {
try {
const response /*: Response */ = await fetch(tarballUrl, {
method: 'HEAD',
});
return response.status === 200;
} catch (e) {
return false;
}
}
/**
* Determines the source type for ReactNativeDependencies based on availability
*/
async function reactNativeDependenciesSourceType(
version /*: string */,
buildType /*: BuildFlavor */,
) /*: Promise<ReactNativeDependenciesEngineSourceType> */ {
const tarballUrl = getTarballUrl(version, buildType);
if (await reactNativeDependenciesArtifactExists(tarballUrl)) {
dependencyLog(`Using download prebuild ${buildType} tarball`);
return ReactNativeDependenciesEngineSourceTypes.DOWNLOAD_PREBUILD_TARBALL;
}
// For nightly tarball, we need to resolve redirects first
const nightlyUrl = await getNightlyTarballUrl(version, buildType);
if (await reactNativeDependenciesArtifactExists(nightlyUrl)) {
dependencyLog('Using download prebuild nightly tarball');
return ReactNativeDependenciesEngineSourceTypes.DOWNLOAD_PREBUILT_NIGHTLY_TARBALL;
}
dependencyLog(
'Using download prebuild nightly tarball - this is a fallback and might not work.',
);
return ReactNativeDependenciesEngineSourceTypes.DOWNLOAD_PREBUILT_NIGHTLY_TARBALL;
}
async function resolveSourceFromSourceType(
sourceType /*: ReactNativeDependenciesEngineSourceType */,
version /*: string */,
buildType /*: BuildFlavor */,
artifactsPath /*: string*/,
) /*: Promise<string> */ {
switch (sourceType) {
case ReactNativeDependenciesEngineSourceTypes.DOWNLOAD_PREBUILD_TARBALL:
return downloadPrebuildTarball(version, buildType, artifactsPath);
case ReactNativeDependenciesEngineSourceTypes.DOWNLOAD_PREBUILT_NIGHTLY_TARBALL:
return downloadPrebuiltNightlyTarball(version, buildType, artifactsPath);
default:
abort(
`[ReactNativeDependencies] Unsupported or invalid source type provided: ${sourceType}`,
);
return '';
}
}
async function downloadPrebuildTarball(
version /*: string */,
buildType /*: BuildFlavor */,
artifactsPath /*: string*/,
) /*: Promise<string> */ {
const url = getTarballUrl(version, buildType);
dependencyLog(`Using release tarball from URL: ${url}`);
return downloadStableReactNativeDependencies(
version,
buildType,
artifactsPath,
);
}
async function downloadPrebuiltNightlyTarball(
version /*: string */,
buildType /*: BuildFlavor */,
artifactsPath /*: string*/,
) /*: Promise<string> */ {
const url = await getNightlyTarballUrl(version, buildType);
dependencyLog(`Using nightly tarball from URL: ${url}`);
return downloadReactNativeDependenciesTarball(
url,
version,
buildType,
artifactsPath,
);
}
async function downloadStableReactNativeDependencies(
version /*: string */,
buildType /*: BuildFlavor */,
artifactsPath /*: string */,
) /*: Promise<string> */ {
const tarballUrl = getTarballUrl(version, buildType);
return downloadReactNativeDependenciesTarball(
tarballUrl,
version,
buildType,
artifactsPath,
);
}
/**
* Downloads a ReactNativeDependencies tarball using fetch instead of curl
*/
async function downloadReactNativeDependenciesTarball(
tarballUrl /*: string */,
version /*: string */,
buildType /*: BuildFlavor */,
artifactsPath /*: string */,
) /*: Promise<string> */ {
const destPath = buildType
? `${artifactsPath}/reactnative-dependencies-${version}-${buildType}.tar.gz`
: `${artifactsPath}/reactnative-dependencies-${version}.tar.gz`;
if (!fs.existsSync(destPath)) {
const tmpFile = `${artifactsPath}/reactnative-dependencies.download`;
try {
fs.mkdirSync(artifactsPath, {recursive: true});
dependencyLog(
`Downloading ReactNativeDependencies tarball from ${tarballUrl}`,
);
const response /*: Response */ = await fetch(tarballUrl);
if (!response.ok) {
throw new Error(
`Failed to download: ${response.status} ${response.statusText}`,
);
}
// Create a write stream to the temporary file
const fileStream = fs.createWriteStream(tmpFile);
// Use Node.js stream pipeline to safely pipe the response body to the file
if (response.body) {
await pipeline(response.body, fileStream);
} else {
// For older fetch implementations that don't support response.body as a stream
const buffer = await response.arrayBuffer();
fs.writeFileSync(tmpFile, Buffer.from(buffer));
}
// Move the temporary file to the destination path
fs.renameSync(tmpFile, destPath);
} catch (e) {
// Clean up the temporary file if it exists
if (fs.existsSync(tmpFile)) {
fs.unlinkSync(tmpFile);
}
abort(
`Failed to download ReactNativeDependencies tarball from ${tarballUrl}: ${e.message}`,
);
}
}
return destPath;
}
function abort(message /*: string */) {
dependencyLog(message, 'error');
throw new Error(message);
}
module.exports = {
prepareReactNativeDependenciesArtifactsAsync,
};