diff --git a/.github/scripts/__tests__/resolve-hermes-url.test.mjs b/.github/scripts/__tests__/resolve-hermes-url.test.mjs
new file mode 100644
index 00000000000..6b368d24779
--- /dev/null
+++ b/.github/scripts/__tests__/resolve-hermes-url.test.mjs
@@ -0,0 +1,56 @@
+import assert from 'node:assert/strict';
+import {readFileSync} from 'node:fs';
+import {createRequire, stripTypeScriptTypes} from 'node:module';
+import {test} from 'node:test';
+import {runInNewContext} from 'node:vm';
+
+const callerSource = readFileSync(new URL('../resolve-hermes.mts', import.meta.url), 'utf8');
+const helperSource = readFileSync(new URL(
+ '../../../packages/react-native/scripts/ios-prebuild/utils.js', import.meta.url,
+), 'utf8');
+// Execute the actual downloader without the CLI dispatch or the zx dependency.
+const downloaderSource = stripTypeScriptTypes(callerSource.slice(
+ callerSource.indexOf('async function downloadUpstreamHermesTarball('),
+ callerSource.indexOf('/**\n * Extracts an upstream Hermes tarball'),
+));
+
+for (const buildType of ['Debug', 'Release']) {
+ for (const metadataAvailable of [true, false]) {
+ test(`${buildType}: ${metadataAvailable ? 'resolves nightly metadata' : 'missing metadata keeps release fallback'}`, async () => {
+ const version = buildType === 'Debug' ? '123.4.5' : '987.6.5';
+ const flavor = buildType.toLowerCase();
+ const snapshotRoot = `https://central.sonatype.com/repository/maven-snapshots/com/facebook/hermes/hermes-ios/${version}-SNAPSHOT`;
+ const metadataUrl = `${snapshotRoot}/maven-metadata.xml`;
+ const releaseUrl = `https://repo1.maven.org/maven2/com/facebook/hermes/hermes-ios/${version}/hermes-ios-${version}-hermes-ios-${flavor}.tar.gz`;
+ const nightlyUrl = `${snapshotRoot}/hermes-ios-${version}-20260915.123456-7-hermes-ios-${flavor}.tar.gz`;
+ const requests = [];
+ const fetch = async url => {
+ requests.push(url);
+ if (url === metadataUrl && metadataAvailable) {
+ return {
+ ok: true,
+ text: async () => '20260915.1234567',
+ };
+ }
+ return {ok: false, status: 404, statusText: 'Not Found'};
+ };
+ const module = {exports: {}};
+ runInNewContext(helperSource, {
+ module,
+ require: createRequire(import.meta.url),
+ fetch,
+ });
+ const download = runInNewContext(`${downloaderSource}\ndownloadUpstreamHermesTarball`, {
+ computeNightlyTarballURL: module.exports.computeNightlyTarballURL,
+ resolveHermesArtifactVersion: () => version,
+ fetch,
+ echo: () => {},
+ });
+
+ assert.equal(await download(buildType), null);
+ assert.deepEqual(requests, metadataAvailable
+ ? [metadataUrl, releaseUrl, nightlyUrl]
+ : [metadataUrl, releaseUrl]);
+ });
+ }
+}
diff --git a/jest.config.js b/jest.config.js
index 5bc7bfdc30a..273b27feb34 100644
--- a/jest.config.js
+++ b/jest.config.js
@@ -51,9 +51,6 @@ module.exports = {
},
// [macOS
moduleNameMapper: {
- // `resolver.js` strips `exports`; match this before the broad fork mapper.
- '^react-native/setup-env$':
- '/packages/react-native/src/setup-env.js',
// These mappers allow out-of-tree platforms tests to resolve RN imports
'^react-native/setup-env$':
'/packages/react-native/src/setup-env.js',