From 8f8310601fc69bc478b007e79be0956f2ee1523f Mon Sep 17 00:00:00 2001 From: Dawid Malecki Date: Wed, 9 Sep 2026 12:56:14 +0200 Subject: [PATCH 1/2] fix probing maven central for artifacts with 1000.0.0 version --- .../facebook/react/utils/DependencyUtils.kt | 27 ++++++++-- .../react/utils/DependencyUtilsTest.kt | 23 ++++++++ .../__tests__/maven_mirror_flag-test.rb | 18 +++++++ .../react-native/scripts/cocoapods/rncore.rb | 8 +++ .../scripts/cocoapods/rndependencies.rb | 8 +++ .../react-native/scripts/cocoapods/utils.rb | 8 +++ .../ios-prebuild/__tests__/utils-test.js | 52 +++++++++++++++++++ .../scripts/ios-prebuild/hermes.js | 11 +++- .../ios-prebuild/reactNativeDependencies.js | 6 ++- .../scripts/ios-prebuild/utils.js | 13 +++++ .../__tests__/download-spm-artifacts-test.js | 22 ++++++++ .../scripts/spm/download-spm-artifacts.js | 22 ++++++++ .../sdks/hermes-engine/hermes-utils.rb | 7 +++ 13 files changed, 217 insertions(+), 8 deletions(-) create mode 100644 packages/react-native/scripts/ios-prebuild/__tests__/utils-test.js diff --git a/packages/gradle-plugin/react-native-gradle-plugin/src/main/kotlin/com/facebook/react/utils/DependencyUtils.kt b/packages/gradle-plugin/react-native-gradle-plugin/src/main/kotlin/com/facebook/react/utils/DependencyUtils.kt index 4b4ffd0c1dd2..89cf96f756af 100644 --- a/packages/gradle-plugin/react-native-gradle-plugin/src/main/kotlin/com/facebook/react/utils/DependencyUtils.kt +++ b/packages/gradle-plugin/react-native-gradle-plugin/src/main/kotlin/com/facebook/react/utils/DependencyUtils.kt @@ -31,6 +31,7 @@ import org.gradle.api.artifacts.repositories.MavenArtifactRepository internal object DependencyUtils { private const val REACT_NATIVE_MAVEN_MIRROR_URL = "https://repo.reactnative.dev/maven2" private const val REACT_NATIVE_MAVEN_MIRROR_ENABLED_ENV = "RCT_REACT_NATIVE_MAVEN_MIRROR_ENABLED" + private const val UNPUBLISHED_MAVEN_VERSION = "1000.0.0" internal data class Coordinates( val versionString: String, @@ -135,6 +136,11 @@ internal object DependencyUtils { coordinates: Coordinates, ) { if (coordinates.versionString.isBlank() || coordinates.hermesVersionString.isBlank()) return + + val shouldConfigureReact = coordinates.versionString.isMavenArtifactVersionPublished() + val shouldConfigureHermes = coordinates.hermesVersionString.isMavenArtifactVersionPublished() + if (!shouldConfigureReact && !shouldConfigureHermes) return + project.rootProject.allprojects { eachProject -> eachProject.configurations.all { configuration -> // Here we set a dependencySubstitution for both react-native and hermes-engine as those @@ -146,10 +152,15 @@ internal object DependencyUtils { it.substitute(it.module(module)).using(it.module(dest)).because(reason) } } - configuration.resolutionStrategy.force( - "${coordinates.reactGroupString}:react-android:${coordinates.versionString}", - ) - if (!(eachProject.findProperty(INTERNAL_USE_HERMES_NIGHTLY) as? String).toBoolean()) { + if (shouldConfigureReact) { + configuration.resolutionStrategy.force( + "${coordinates.reactGroupString}:react-android:${coordinates.versionString}", + ) + } + if ( + shouldConfigureHermes && + !(eachProject.findProperty(INTERNAL_USE_HERMES_NIGHTLY) as? String).toBoolean() + ) { // Contributors only: The hermes-engine version is forced only if the user has // not opted into using nightlies for local development. configuration.resolutionStrategy.force( @@ -212,7 +223,10 @@ internal object DependencyUtils { ), ) } - return dependencySubstitution + // 1000.0.0 identifies a source checkout on main and is never published to Maven. + return dependencySubstitution.filterNot { (_, destination, _) -> + !destination.substringAfterLast(':').isMavenArtifactVersionPublished() + } } fun readVersionAndGroupStrings( @@ -303,6 +317,9 @@ internal object DependencyUtils { internal fun String.isNightly(): Boolean = this.startsWith("0.0.0") || "-nightly-" in this + internal fun String.isMavenArtifactVersionPublished(): Boolean = + isNotBlank() && this != UNPUBLISHED_MAVEN_VERSION + internal fun Project.exclusiveEnterpriseRepository() = when { hasProperty(SCOPED_EXCLUSIVE_ENTEPRISE_REPOSITORY) -> diff --git a/packages/gradle-plugin/react-native-gradle-plugin/src/test/kotlin/com/facebook/react/utils/DependencyUtilsTest.kt b/packages/gradle-plugin/react-native-gradle-plugin/src/test/kotlin/com/facebook/react/utils/DependencyUtilsTest.kt index fed57c2025c3..ff1a11f91ba8 100644 --- a/packages/gradle-plugin/react-native-gradle-plugin/src/test/kotlin/com/facebook/react/utils/DependencyUtilsTest.kt +++ b/packages/gradle-plugin/react-native-gradle-plugin/src/test/kotlin/com/facebook/react/utils/DependencyUtilsTest.kt @@ -12,6 +12,7 @@ import com.facebook.react.utils.DependencyUtils.configureDependencies import com.facebook.react.utils.DependencyUtils.configureRepositories import com.facebook.react.utils.DependencyUtils.exclusiveEnterpriseRepository import com.facebook.react.utils.DependencyUtils.getDependencySubstitutions +import com.facebook.react.utils.DependencyUtils.isMavenArtifactVersionPublished import com.facebook.react.utils.DependencyUtils.isNightly import com.facebook.react.utils.DependencyUtils.isReactNativeMavenMirrorEnabled import com.facebook.react.utils.DependencyUtils.mavenRepoFromURI @@ -414,6 +415,22 @@ class DependencyUtilsTest { assertThat(project.configurations.first().resolutionStrategy.forcedModules.isEmpty()).isTrue() } + @Test + fun configureDependencies_withUnpublishedVersion_doesNotRequestReactNativeArtifacts() { + val project = createProject() + + configureDependencies(project, DependencyUtils.Coordinates("1000.0.0", "4.5.6")) + + val forcedModules = project.configurations.first().resolutionStrategy.forcedModules + assertThat(forcedModules.none { it.toString().contains(":1000.0.0") }).isTrue() + assertThat(forcedModules.any { it.toString() == "com.facebook.hermes:hermes-android:4.5.6" }) + .isTrue() + + val dependencySubstitutions = + getDependencySubstitutions(DependencyUtils.Coordinates("1000.0.0", "4.5.6")) + assertThat(dependencySubstitutions.none { it.second.contains(":1000.0.0") }).isTrue() + } + @Test fun configureDependencies_withVersionString_appliesResolutionStrategy() { val project = createProject() @@ -577,6 +594,12 @@ class DependencyUtilsTest { assertThat(hermesVersionString).isEqualTo("1000.0.0") } + @Test + fun isMavenArtifactVersionPublished_withMainVersion_returnsFalse() { + assertThat("1000.0.0".isMavenArtifactVersionPublished()).isFalse() + assertThat("0.88.0".isMavenArtifactVersionPublished()).isTrue() + } + @Test fun readVersionString_withNightlyVersionString_returnsSnapshotVersion() { val propertiesFile = diff --git a/packages/react-native/scripts/cocoapods/__tests__/maven_mirror_flag-test.rb b/packages/react-native/scripts/cocoapods/__tests__/maven_mirror_flag-test.rb index 9f75f25fd7ae..aabf1e5cd65c 100644 --- a/packages/react-native/scripts/cocoapods/__tests__/maven_mirror_flag-test.rb +++ b/packages/react-native/scripts/cocoapods/__tests__/maven_mirror_flag-test.rb @@ -5,6 +5,8 @@ require "test/unit" require_relative "../utils.rb" +require_relative "../rncore.rb" +require_relative "../rndependencies.rb" require_relative "../../../sdks/hermes-engine/hermes-utils.rb" class MavenMirrorFlagTests < Test::Unit::TestCase @@ -39,4 +41,20 @@ def test_mavenMirror_isDisabledWhenExplicitlySetToFalse assert_false(ReactNativePodsUtils.react_native_maven_mirror_enabled?) assert_false(react_native_maven_mirror_enabled?) end + + def test_unpublishedVersion_skipsAllArtifactLookups + assert_false(ReactNativePodsUtils.maven_artifact_version_published?('1000.0.0')) + assert_false(ReactNativePodsUtils.artifact_exists?('https://repo.reactnative.dev/maven2/example/1000.0.0/example.tar.gz')) + assert_false(ReactNativeCoreUtils.release_artifact_exists('1000.0.0')) + assert_false(ReactNativeCoreUtils.nightly_artifact_exists('1000.0.0')) + assert_false(ReactNativeDependenciesUtils.release_artifact_exists('1000.0.0')) + assert_false(ReactNativeDependenciesUtils.nightly_artifact_exists('1000.0.0')) + assert_false(release_artifact_exists('1000.0.0')) + assert_false(hermes_artifact_exists('https://repo.reactnative.dev/maven2/example/1000.0.0/example.tar.gz')) + end + + def test_releaseVersion_isPublished + assert_true(ReactNativePodsUtils.maven_artifact_version_published?('0.88.0')) + assert_true(ReactNativePodsUtils.maven_artifact_version_published?('1000.0.0-abcdef123')) + end end diff --git a/packages/react-native/scripts/cocoapods/rncore.rb b/packages/react-native/scripts/cocoapods/rncore.rb index e56ce29b8a15..9e88e1e7a463 100644 --- a/packages/react-native/scripts/cocoapods/rncore.rb +++ b/packages/react-native/scripts/cocoapods/rncore.rb @@ -350,6 +350,8 @@ def self.generate_plist_content(mappings) end def self.stable_tarball_url(version, build_type, dsyms = false) + return nil if !ReactNativePodsUtils.maven_artifact_version_published?(version) + candidates = stable_tarball_urls(version, build_type, dsyms) return candidates.find { |url| artifact_exists(url) } || candidates.first end @@ -367,6 +369,8 @@ def self.stable_tarball_urls(version, build_type, dsyms = false) end def self.nightly_tarball_url(version, configuration, dsyms = false) + return "" if !ReactNativePodsUtils.maven_artifact_version_published?(version) + artefact_coordinate = "react-native-artifacts" artefact_name = "reactnative-core-#{dsyms ? "dSYM-" : ""}#{configuration ? configuration : "debug"}.tar.gz" xml_url = "https://central.sonatype.com/repository/maven-snapshots/com/facebook/react/#{artefact_coordinate}/#{version}-SNAPSHOT/maven-metadata.xml" @@ -465,10 +469,14 @@ def self.download_rncore_tarball(react_native_path, tarball_url, version, config end def self.release_artifact_exists(version) + return false if !ReactNativePodsUtils.maven_artifact_version_published?(version) + return stable_tarball_urls(version, :debug).any? { |url| artifact_exists(url) } end def self.nightly_artifact_exists(version) + return false if !ReactNativePodsUtils.maven_artifact_version_published?(version) + return artifact_exists(nightly_tarball_url(version, :debug).gsub("\\", "")) end diff --git a/packages/react-native/scripts/cocoapods/rndependencies.rb b/packages/react-native/scripts/cocoapods/rndependencies.rb index c38755263963..7dfbfc4f535a 100644 --- a/packages/react-native/scripts/cocoapods/rndependencies.rb +++ b/packages/react-native/scripts/cocoapods/rndependencies.rb @@ -232,6 +232,8 @@ def self.podspec_source_download_prebuild_release_tarball() end def self.release_tarball_url(version, build_type) + return nil if !ReactNativePodsUtils.maven_artifact_version_published?(version) + candidates = release_tarball_urls(version, build_type) return candidates.find { |url| artifact_exists(url) } || candidates.first end @@ -250,6 +252,8 @@ def self.release_tarball_urls(version, build_type) end def self.nightly_tarball_url(version, build_type) + return "" if !ReactNativePodsUtils.maven_artifact_version_published?(version) + artifact_coordinate = "react-native-artifacts" artifact_name = "reactnative-dependencies-#{build_type.to_s}.tar.gz" xml_url = "https://central.sonatype.com/repository/maven-snapshots/com/facebook/react/#{artifact_coordinate}/#{version}-SNAPSHOT/maven-metadata.xml" @@ -373,10 +377,14 @@ def self.download_rndeps_tarball(react_native_path, tarball_url, version, config end def self.release_artifact_exists(version) + return false if !ReactNativePodsUtils.maven_artifact_version_published?(version) + return release_tarball_urls(version, :debug).any? { |url| artifact_exists(url) } end def self.nightly_artifact_exists(version) + return false if !ReactNativePodsUtils.maven_artifact_version_published?(version) + return artifact_exists(nightly_tarball_url(version, :debug).gsub("\\", "")) end diff --git a/packages/react-native/scripts/cocoapods/utils.rb b/packages/react-native/scripts/cocoapods/utils.rb index a31cab7442f6..5a2613c31021 100644 --- a/packages/react-native/scripts/cocoapods/utils.rb +++ b/packages/react-native/scripts/cocoapods/utils.rb @@ -15,6 +15,7 @@ class ReactNativePodsUtils MAVEN_CENTRAL_REPOSITORY = "https://repo1.maven.org/maven2" REACT_NATIVE_MAVEN_MIRROR_REPOSITORY = "https://repo.reactnative.dev/maven2" + UNPUBLISHED_MAVEN_VERSION = "1000.0.0" # Opt-in removal of the legacy TurboModule and component interop layers. Both are # off by default and will become the default in a future React Native release. @@ -49,6 +50,11 @@ def self.react_native_maven_mirror_enabled?() value.downcase != "false" && value != "0" end + def self.maven_artifact_version_published?(version) + # 1000.0.0 identifies a source checkout on main and is never published to Maven. + return version != UNPUBLISHED_MAVEN_VERSION + end + def self.warn_if_not_on_arm64 if SysctlChecker.new().call_sysctl_arm64() == 1 && !Environment.new().ruby_platform().include?('arm64') Pod::UI.warn 'Do not use "pod install" from inside Rosetta2 (x86_64 emulation on arm64).' @@ -833,6 +839,8 @@ def self.resolve_use_frameworks(spec, header_mappings_dir: nil, module_name: nil # (DNS failure, no route, ...) the probe is left uncached so that a # transient hiccup doesn't permanently mark the artifact as missing. def self.artifact_exists?(tarball_url) + return false if tarball_url.include?("/#{UNPUBLISHED_MAVEN_VERSION}/") + unless @@artifact_exists_cache.key?(tarball_url) # -L is used to follow redirects, useful for the nightlies # The url is wrapped in quotes to avoid escaping & and ?. diff --git a/packages/react-native/scripts/ios-prebuild/__tests__/utils-test.js b/packages/react-native/scripts/ios-prebuild/__tests__/utils-test.js new file mode 100644 index 000000000000..7d443bd217b1 --- /dev/null +++ b/packages/react-native/scripts/ios-prebuild/__tests__/utils-test.js @@ -0,0 +1,52 @@ +/** + * 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. + * + * @format + * @noflow + */ + +'use strict'; + +const { + computeNightlyTarballURL, + isMavenArtifactVersionPublished, +} = require('../utils'); + +describe('isMavenArtifactVersionPublished', () => { + it('rejects the unpublished main version', () => { + expect(isMavenArtifactVersionPublished('1000.0.0')).toBe(false); + }); + + it.each([ + '0.88.0', + '0.89.0-nightly-20260909-abcdef123', + '1000.0.0-abcdef123', + ])('accepts published artifact version %s', version => { + expect(isMavenArtifactVersionPublished(version)).toBe(true); + }); +}); + +describe('computeNightlyTarballURL', () => { + it('does not query snapshot metadata for the unpublished main version', async () => { + const originalFetch = globalThis.fetch; + globalThis.fetch = jest.fn(); + + try { + await expect( + computeNightlyTarballURL( + '1000.0.0', + 'Debug', + 'react', + 'react-native-artifacts', + 'reactnative-dependencies-debug.tar.gz', + ), + ).rejects.toThrow(/artifacts are not published/); + expect(globalThis.fetch).not.toHaveBeenCalled(); + } finally { + globalThis.fetch = originalFetch; + } + }); +}); diff --git a/packages/react-native/scripts/ios-prebuild/hermes.js b/packages/react-native/scripts/ios-prebuild/hermes.js index 7e20ddf4f978..9587373097d2 100644 --- a/packages/react-native/scripts/ios-prebuild/hermes.js +++ b/packages/react-native/scripts/ios-prebuild/hermes.js @@ -8,7 +8,11 @@ * @format */ -const {createLogger, getMavenRepositoryUrls} = require('./utils'); +const { + createLogger, + getMavenRepositoryUrls, + isMavenArtifactVersionPublished, +} = require('./utils'); const {execSync} = require('node:child_process'); const fs = require('node:fs'); const path = require('node:path'); @@ -62,7 +66,10 @@ async function prepareHermesArtifactsAsync( // Resolve the version from the environment variable or use the default version let resolvedVersion = process.env.HERMES_VERSION ?? 'latest-v1'; - if (resolvedVersion === 'latest-v1') { + if ( + resolvedVersion === 'latest-v1' || + !isMavenArtifactVersionPublished(resolvedVersion) + ) { // TODO: rename 'latest-v1' to 'latest' once V1 is the only Hermes on npm hermesLog('Using latest-v1 tarball'); const hermesVersion = await getLatestHermesVersionFromNPM(); diff --git a/packages/react-native/scripts/ios-prebuild/reactNativeDependencies.js b/packages/react-native/scripts/ios-prebuild/reactNativeDependencies.js index bcc325ee0ea6..1321afc97f28 100644 --- a/packages/react-native/scripts/ios-prebuild/reactNativeDependencies.js +++ b/packages/react-native/scripts/ios-prebuild/reactNativeDependencies.js @@ -14,6 +14,7 @@ const { computeNightlyTarballURL, createLogger, getMavenRepositoryUrls, + isMavenArtifactVersionPublished, } = require('./utils'); const {execSync} = require('node:child_process'); const fs = require('node:fs'); @@ -49,7 +50,10 @@ async function prepareReactNativeDependenciesArtifactsAsync( // Resolve the version from the environment variable or use the default version let resolvedVersion = process.env.RN_DEP_VERSION ?? version; - if (resolvedVersion === 'nightly') { + if ( + resolvedVersion === 'nightly' || + !isMavenArtifactVersionPublished(resolvedVersion) + ) { dependencyLog('Using latest nightly tarball'); const rnVersion = await getNightlyVersionFromNPM(); resolvedVersion = rnVersion; diff --git a/packages/react-native/scripts/ios-prebuild/utils.js b/packages/react-native/scripts/ios-prebuild/utils.js index 036b398f750d..13e2aefe2213 100644 --- a/packages/react-native/scripts/ios-prebuild/utils.js +++ b/packages/react-native/scripts/ios-prebuild/utils.js @@ -17,6 +17,7 @@ const path = require('node:path'); const MAVEN_CENTRAL_REPOSITORY = 'https://repo1.maven.org/maven2'; const REACT_NATIVE_MAVEN_MIRROR_REPOSITORY = 'https://repo.reactnative.dev/maven2'; +const UNPUBLISHED_MAVEN_VERSION = '1000.0.0'; /** * Creates a folder if it does not exist @@ -96,6 +97,12 @@ async function computeNightlyTarballURL( artifactCoordinate /*: string */, artifactName /*: string */, ) /*: Promise */ { + if (!isMavenArtifactVersionPublished(version)) { + throw new Error( + `Maven artifacts are not published for the development version ${version}`, + ); + } + const xmlUrl = `https://central.sonatype.com/repository/maven-snapshots/com/facebook/${subGroup}/${artifactCoordinate}/${version}-SNAPSHOT/maven-metadata.xml`; const response = await fetch(xmlUrl); @@ -156,6 +163,11 @@ function isReactNativeMavenMirrorEnabled() /*: boolean */ { return value.toLowerCase() !== 'false' && value !== '0'; } +function isMavenArtifactVersionPublished(version /*: string */) /*: boolean */ { + // 1000.0.0 identifies a source checkout on main and is never published to Maven. + return version !== UNPUBLISHED_MAVEN_VERSION; +} + module.exports = { createFolderIfNotExists, findFirst, @@ -163,4 +175,5 @@ module.exports = { createLogger, computeNightlyTarballURL, getMavenRepositoryUrls, + isMavenArtifactVersionPublished, }; diff --git a/packages/react-native/scripts/spm/__tests__/download-spm-artifacts-test.js b/packages/react-native/scripts/spm/__tests__/download-spm-artifacts-test.js index 3faaf5e8b087..5e433d5d20aa 100644 --- a/packages/react-native/scripts/spm/__tests__/download-spm-artifacts-test.js +++ b/packages/react-native/scripts/spm/__tests__/download-spm-artifacts-test.js @@ -18,6 +18,7 @@ const { formatBytes, formatSpeed, hermesReleaseUrls, + isMavenArtifactVersionPublished, mavenRepositoryUrls, reactNativeMavenMirrorEnabled, resolveCacheSlotVersion, @@ -377,6 +378,13 @@ describe('mavenRepositoryUrls', () => { // --------------------------------------------------------------------------- describe('release URL builders', () => { + it('does not create Maven URLs for the unpublished main version', () => { + expect(isMavenArtifactVersionPublished('1000.0.0')).toBe(false); + expect(rnCoreReleaseUrls('1000.0.0', 'debug')).toEqual([]); + expect(rnDepsReleaseUrls('1000.0.0', 'debug')).toEqual([]); + expect(hermesReleaseUrls('1000.0.0', 'debug')).toEqual([]); + }); + it('rnCoreReleaseUrls builds a candidate per repository for the reactnative-core classifier', () => { const suffix = '/com/facebook/react/react-native-artifacts/0.85.0/' + @@ -558,6 +566,20 @@ describe('resolveSnapshotUrl', () => { ); }); + it('does not query snapshot metadata for the unpublished main version', async () => { + globalThis.fetch = jest.fn(); + + await expect( + resolveSnapshotUrl( + '1000.0.0', + 'react', + 'react-native-artifacts', + 'reactnative-core-debug.tar.gz', + ), + ).rejects.toThrow(/artifacts are not published/); + expect(globalThis.fetch).not.toHaveBeenCalled(); + }); + it('throws when the metadata request fails', async () => { globalThis.fetch = routerFetch({ 'maven-metadata.xml': {ok: false, status: 500}, diff --git a/packages/react-native/scripts/spm/download-spm-artifacts.js b/packages/react-native/scripts/spm/download-spm-artifacts.js index f84dc017b60b..0914e3ba9c37 100644 --- a/packages/react-native/scripts/spm/download-spm-artifacts.js +++ b/packages/react-native/scripts/spm/download-spm-artifacts.js @@ -141,9 +141,15 @@ function parseArgs(argv /*: Array */) /*: DownloadArgs */ { const MAVEN_CENTRAL_REPOSITORY = 'https://repo1.maven.org/maven2'; const REACT_NATIVE_MAVEN_MIRROR_REPOSITORY = 'https://repo.reactnative.dev/maven2'; +const UNPUBLISHED_MAVEN_VERSION = '1000.0.0'; const MAVEN_SNAPSHOT = 'https://central.sonatype.com/repository/maven-snapshots'; +function isMavenArtifactVersionPublished(version /*: string */) /*: boolean */ { + // 1000.0.0 identifies a source checkout on main and is never published to Maven. + return version !== UNPUBLISHED_MAVEN_VERSION; +} + /** * The mirror is ON unless RCT_REACT_NATIVE_MAVEN_MIRROR_ENABLED is * explicitly "false"/"0". @@ -177,6 +183,9 @@ function rnCoreReleaseUrls( version /*: string */, flavor /*: string */, ) /*: Array */ { + if (!isMavenArtifactVersionPublished(version)) { + return []; + } return mavenRepositoryUrls().map( repository => `${repository}/com/facebook/react/react-native-artifacts/${version}/` + @@ -187,6 +196,9 @@ function rnDepsReleaseUrls( version /*: string */, flavor /*: string */, ) /*: Array */ { + if (!isMavenArtifactVersionPublished(version)) { + return []; + } return mavenRepositoryUrls().map( repository => `${repository}/com/facebook/react/react-native-artifacts/${version}/` + @@ -197,6 +209,9 @@ function hermesReleaseUrls( version /*: string */, flavor /*: string */, ) /*: Array */ { + if (!isMavenArtifactVersionPublished(version)) { + return []; + } return mavenRepositoryUrls().map( repository => `${repository}/com/facebook/hermes/hermes-ios/${version}/` + @@ -219,6 +234,12 @@ async function resolveSnapshotUrl( coordinate /*: string */, artifactName /*: string */, ) /*: Promise */ { + if (!isMavenArtifactVersionPublished(version)) { + throw new Error( + `Maven artifacts are not published for the development version ${version}`, + ); + } + const metadataUrl = `${MAVEN_SNAPSHOT}/com/facebook/${subGroup}/${coordinate}/` + `${version}-SNAPSHOT/maven-metadata.xml`; @@ -1510,6 +1531,7 @@ module.exports = { validateArtifactsCache, // Exposed for unit tests (pure / fetch-stubbable helpers). mavenRepositoryUrls, + isMavenArtifactVersionPublished, reactNativeMavenMirrorEnabled, rnCoreReleaseUrls, rnDepsReleaseUrls, diff --git a/packages/react-native/sdks/hermes-engine/hermes-utils.rb b/packages/react-native/sdks/hermes-engine/hermes-utils.rb index 71ef0481bbd4..c0b68441dc87 100644 --- a/packages/react-native/sdks/hermes-engine/hermes-utils.rb +++ b/packages/react-native/sdks/hermes-engine/hermes-utils.rb @@ -9,6 +9,7 @@ ENV_BUILD_FROM_SOURCE = "RCT_BUILD_HERMES_FROM_SOURCE" MAVEN_CENTRAL_REPOSITORY = "https://repo1.maven.org/maven2" REACT_NATIVE_MAVEN_MIRROR_REPOSITORY = "https://repo.reactnative.dev/maven2" +UNPUBLISHED_HERMES_VERSION = "1000.0.0" # Memoized results of requests to the Maven repositories (mirror or central). # hermes-engine.podspec is evaluated several times during a single @@ -99,6 +100,8 @@ def force_build_from_stable_branch(react_native_path) end def release_artifact_exists(version) + return false if version == UNPUBLISHED_HERMES_VERSION + return release_tarball_urls(version, :debug).any? { |url| hermes_artifact_exists(url) } end @@ -200,6 +203,8 @@ def hermestag_file(react_native_path) end def release_tarball_url(version, build_type) + return nil if version == UNPUBLISHED_HERMES_VERSION + candidates = release_tarball_urls(version, build_type) return candidates.find { |url| hermes_artifact_exists(url) } || candidates.first end @@ -356,6 +361,8 @@ def resolve_url_redirects(url) # Parameters # - tarball_url: the URL of the Hermes artifact to probe def hermes_artifact_exists(tarball_url) + return false if tarball_url.include?("/#{UNPUBLISHED_HERMES_VERSION}/") + unless HERMES_ARTIFACT_EXISTS_CACHE.key?(tarball_url) # -L is used to follow redirects, useful for the nightlies # I also needed to wrap the url in quotes to avoid escaping & and ?. From 6e1169caf1f03c129a016c4dff76b59f6bff8ddc Mon Sep 17 00:00:00 2001 From: Dawid Malecki Date: Wed, 9 Sep 2026 17:36:45 +0200 Subject: [PATCH 2/2] fixes --- .../kotlin/com/facebook/react/ReactPlugin.kt | 2 +- .../facebook/react/utils/DependencyUtils.kt | 60 +++++++++++++------ .../react/utils/DependencyUtilsTest.kt | 54 ++++++++++++++++- .../__tests__/maven_mirror_flag-test.rb | 11 ++++ .../react-native/scripts/cocoapods/rncore.rb | 4 -- .../scripts/cocoapods/rndependencies.rb | 4 -- .../react-native/scripts/cocoapods/utils.rb | 3 +- .../scripts/ios-prebuild/hermes.js | 14 +++-- .../ios-prebuild/reactNativeDependencies.js | 14 +++-- .../__tests__/download-spm-artifacts-test.js | 22 +++++-- .../scripts/spm/download-spm-artifacts.js | 20 ++++--- .../sdks/hermes-engine/hermes-utils.rb | 7 +-- 12 files changed, 157 insertions(+), 58 deletions(-) diff --git a/packages/gradle-plugin/react-native-gradle-plugin/src/main/kotlin/com/facebook/react/ReactPlugin.kt b/packages/gradle-plugin/react-native-gradle-plugin/src/main/kotlin/com/facebook/react/ReactPlugin.kt index 890edb58bd86..f3b5072dce93 100644 --- a/packages/gradle-plugin/react-native-gradle-plugin/src/main/kotlin/com/facebook/react/ReactPlugin.kt +++ b/packages/gradle-plugin/react-native-gradle-plugin/src/main/kotlin/com/facebook/react/ReactPlugin.kt @@ -95,7 +95,7 @@ class ReactPlugin : Plugin { val versionAndGroupStrings = readVersionAndGroupStrings(project, propertiesFile, hermesVersionPropertiesFile) configureDependencies(project, versionAndGroupStrings) - configureRepositories(project, versionAndGroupStrings.isNightly) + configureRepositories(project, versionAndGroupStrings) } configureReactNativeNdk(project, extension) diff --git a/packages/gradle-plugin/react-native-gradle-plugin/src/main/kotlin/com/facebook/react/utils/DependencyUtils.kt b/packages/gradle-plugin/react-native-gradle-plugin/src/main/kotlin/com/facebook/react/utils/DependencyUtils.kt index 89cf96f756af..3b52d55d4659 100644 --- a/packages/gradle-plugin/react-native-gradle-plugin/src/main/kotlin/com/facebook/react/utils/DependencyUtils.kt +++ b/packages/gradle-plugin/react-native-gradle-plugin/src/main/kotlin/com/facebook/react/utils/DependencyUtils.kt @@ -125,6 +125,41 @@ internal object DependencyUtils { } } + /** + * Configures repositories without asking remote repositories for versions that are known to be + * unpublished. + */ + fun configureRepositories(project: Project, coordinates: Coordinates) { + configureRepositories(project, coordinates.isNightly) + + project.rootProject.allprojects { eachProject -> + eachProject.repositories.withType(MavenArtifactRepository::class.java).configureEach { repo -> + if (repo.url.scheme != "file") { + repo.content { content -> + if (!coordinates.versionString.isMavenArtifactVersionPublished()) { + setOf(DEFAULT_INTERNAL_REACT_PUBLISHING_GROUP, coordinates.reactGroupString) + .forEach { group -> + content.excludeVersion(group, "react-native", UNPUBLISHED_MAVEN_VERSION) + content.excludeVersion(group, "react-android", UNPUBLISHED_MAVEN_VERSION) + } + } + if (!coordinates.hermesVersionString.isMavenArtifactVersionPublished()) { + setOf( + DEFAULT_INTERNAL_REACT_PUBLISHING_GROUP, + DEFAULT_INTERNAL_HERMES_PUBLISHING_GROUP, + coordinates.hermesGroupString, + ) + .forEach { group -> + content.excludeVersion(group, "hermes-engine", UNPUBLISHED_MAVEN_VERSION) + content.excludeVersion(group, "hermes-android", UNPUBLISHED_MAVEN_VERSION) + } + } + } + } + } + } + } + /** * This method takes care of configuring the resolution strategy for both the app and all the 3rd * party libraries which are auto-linked. Specifically it takes care of: @@ -136,11 +171,6 @@ internal object DependencyUtils { coordinates: Coordinates, ) { if (coordinates.versionString.isBlank() || coordinates.hermesVersionString.isBlank()) return - - val shouldConfigureReact = coordinates.versionString.isMavenArtifactVersionPublished() - val shouldConfigureHermes = coordinates.hermesVersionString.isMavenArtifactVersionPublished() - if (!shouldConfigureReact && !shouldConfigureHermes) return - project.rootProject.allprojects { eachProject -> eachProject.configurations.all { configuration -> // Here we set a dependencySubstitution for both react-native and hermes-engine as those @@ -152,15 +182,10 @@ internal object DependencyUtils { it.substitute(it.module(module)).using(it.module(dest)).because(reason) } } - if (shouldConfigureReact) { - configuration.resolutionStrategy.force( - "${coordinates.reactGroupString}:react-android:${coordinates.versionString}", - ) - } - if ( - shouldConfigureHermes && - !(eachProject.findProperty(INTERNAL_USE_HERMES_NIGHTLY) as? String).toBoolean() - ) { + configuration.resolutionStrategy.force( + "${coordinates.reactGroupString}:react-android:${coordinates.versionString}", + ) + if (!(eachProject.findProperty(INTERNAL_USE_HERMES_NIGHTLY) as? String).toBoolean()) { // Contributors only: The hermes-engine version is forced only if the user has // not opted into using nightlies for local development. configuration.resolutionStrategy.force( @@ -223,10 +248,7 @@ internal object DependencyUtils { ), ) } - // 1000.0.0 identifies a source checkout on main and is never published to Maven. - return dependencySubstitution.filterNot { (_, destination, _) -> - !destination.substringAfterLast(':').isMavenArtifactVersionPublished() - } + return dependencySubstitution } fun readVersionAndGroupStrings( @@ -318,7 +340,7 @@ internal object DependencyUtils { internal fun String.isNightly(): Boolean = this.startsWith("0.0.0") || "-nightly-" in this internal fun String.isMavenArtifactVersionPublished(): Boolean = - isNotBlank() && this != UNPUBLISHED_MAVEN_VERSION + this != UNPUBLISHED_MAVEN_VERSION internal fun Project.exclusiveEnterpriseRepository() = when { diff --git a/packages/gradle-plugin/react-native-gradle-plugin/src/test/kotlin/com/facebook/react/utils/DependencyUtilsTest.kt b/packages/gradle-plugin/react-native-gradle-plugin/src/test/kotlin/com/facebook/react/utils/DependencyUtilsTest.kt index ff1a11f91ba8..17aca0a4afbf 100644 --- a/packages/gradle-plugin/react-native-gradle-plugin/src/test/kotlin/com/facebook/react/utils/DependencyUtilsTest.kt +++ b/packages/gradle-plugin/react-native-gradle-plugin/src/test/kotlin/com/facebook/react/utils/DependencyUtilsTest.kt @@ -19,7 +19,10 @@ import com.facebook.react.utils.DependencyUtils.mavenRepoFromURI import com.facebook.react.utils.DependencyUtils.mavenRepoFromUrl import com.facebook.react.utils.DependencyUtils.readVersionAndGroupStrings import com.facebook.react.utils.DependencyUtils.shouldAddJitPack +import com.sun.net.httpserver.HttpServer +import java.net.InetSocketAddress import java.net.URI +import java.util.concurrent.atomic.AtomicInteger import org.assertj.core.api.Assertions.assertThat import org.gradle.api.artifacts.repositories.MavenArtifactRepository import org.gradle.testfixtures.ProjectBuilder @@ -79,6 +82,41 @@ class DependencyUtilsTest { .isNotNull() } + @Test + fun configureRepositories_withUnpublishedVersion_doesNotQueryRemoteRepository() { + val requests = AtomicInteger() + val server = HttpServer.create(InetSocketAddress("127.0.0.1", 0), 0) + server.createContext("/") { exchange -> + requests.incrementAndGet() + exchange.sendResponseHeaders(404, -1) + exchange.close() + } + server.start() + + try { + val project = createProject() + project.extensions.extraProperties.set( + "exclusiveEnterpriseRepository", + "http://127.0.0.1:${server.address.port}", + ) + configureRepositories(project, DependencyUtils.Coordinates("1000.0.0", "4.5.6")) + (project.repositories.first() as MavenArtifactRepository).isAllowInsecureProtocol = true + + val published = project.configurations.create("published") + project.dependencies.add(published.name, "com.facebook.react:react-android:0.88.0") + assertThat(runCatching { published.resolve() }.isFailure).isTrue() + assertThat(requests.get()).isGreaterThan(0) + + requests.set(0) + val unpublished = project.configurations.create("unpublished") + project.dependencies.add(unpublished.name, "com.facebook.react:react-android:1000.0.0") + assertThat(runCatching { unpublished.resolve() }.isFailure).isTrue() + assertThat(requests.get()).isZero() + } finally { + server.stop(0) + } + } + @Test fun configureRepositories_containsGoogleRepo() { val repositoryURI = URI.create("https://dl.google.com/dl/android/maven2/") @@ -416,19 +454,29 @@ class DependencyUtilsTest { } @Test - fun configureDependencies_withUnpublishedVersion_doesNotRequestReactNativeArtifacts() { + fun configureDependencies_withUnpublishedVersion_preservesResolutionStrategy() { val project = createProject() configureDependencies(project, DependencyUtils.Coordinates("1000.0.0", "4.5.6")) val forcedModules = project.configurations.first().resolutionStrategy.forcedModules - assertThat(forcedModules.none { it.toString().contains(":1000.0.0") }).isTrue() + assertThat( + forcedModules.any { + it.toString() == "com.facebook.react:react-android:1000.0.0" + }, + ) + .isTrue() assertThat(forcedModules.any { it.toString() == "com.facebook.hermes:hermes-android:4.5.6" }) .isTrue() val dependencySubstitutions = getDependencySubstitutions(DependencyUtils.Coordinates("1000.0.0", "4.5.6")) - assertThat(dependencySubstitutions.none { it.second.contains(":1000.0.0") }).isTrue() + assertThat( + dependencySubstitutions.any { + it.second == "com.facebook.react:react-android:1000.0.0" + }, + ) + .isTrue() } @Test diff --git a/packages/react-native/scripts/cocoapods/__tests__/maven_mirror_flag-test.rb b/packages/react-native/scripts/cocoapods/__tests__/maven_mirror_flag-test.rb index aabf1e5cd65c..c5ed599ee714 100644 --- a/packages/react-native/scripts/cocoapods/__tests__/maven_mirror_flag-test.rb +++ b/packages/react-native/scripts/cocoapods/__tests__/maven_mirror_flag-test.rb @@ -45,12 +45,23 @@ def test_mavenMirror_isDisabledWhenExplicitlySetToFalse def test_unpublishedVersion_skipsAllArtifactLookups assert_false(ReactNativePodsUtils.maven_artifact_version_published?('1000.0.0')) assert_false(ReactNativePodsUtils.artifact_exists?('https://repo.reactnative.dev/maven2/example/1000.0.0/example.tar.gz')) + assert_false(ReactNativePodsUtils.artifact_exists?('https://central.sonatype.com/example/1000.0.0-SNAPSHOT/example.tar.gz')) assert_false(ReactNativeCoreUtils.release_artifact_exists('1000.0.0')) assert_false(ReactNativeCoreUtils.nightly_artifact_exists('1000.0.0')) assert_false(ReactNativeDependenciesUtils.release_artifact_exists('1000.0.0')) assert_false(ReactNativeDependenciesUtils.nightly_artifact_exists('1000.0.0')) assert_false(release_artifact_exists('1000.0.0')) assert_false(hermes_artifact_exists('https://repo.reactnative.dev/maven2/example/1000.0.0/example.tar.gz')) + + assert_equal( + ReactNativeCoreUtils.stable_tarball_urls('1000.0.0', :debug).first, + ReactNativeCoreUtils.stable_tarball_url('1000.0.0', :debug), + ) + assert_equal( + ReactNativeDependenciesUtils.release_tarball_urls('1000.0.0', :debug).first, + ReactNativeDependenciesUtils.release_tarball_url('1000.0.0', :debug), + ) + assert_equal(release_tarball_urls('1000.0.0', :debug).first, release_tarball_url('1000.0.0', :debug)) end def test_releaseVersion_isPublished diff --git a/packages/react-native/scripts/cocoapods/rncore.rb b/packages/react-native/scripts/cocoapods/rncore.rb index 9e88e1e7a463..f588ad771f0b 100644 --- a/packages/react-native/scripts/cocoapods/rncore.rb +++ b/packages/react-native/scripts/cocoapods/rncore.rb @@ -350,8 +350,6 @@ def self.generate_plist_content(mappings) end def self.stable_tarball_url(version, build_type, dsyms = false) - return nil if !ReactNativePodsUtils.maven_artifact_version_published?(version) - candidates = stable_tarball_urls(version, build_type, dsyms) return candidates.find { |url| artifact_exists(url) } || candidates.first end @@ -469,8 +467,6 @@ def self.download_rncore_tarball(react_native_path, tarball_url, version, config end def self.release_artifact_exists(version) - return false if !ReactNativePodsUtils.maven_artifact_version_published?(version) - return stable_tarball_urls(version, :debug).any? { |url| artifact_exists(url) } end diff --git a/packages/react-native/scripts/cocoapods/rndependencies.rb b/packages/react-native/scripts/cocoapods/rndependencies.rb index 7dfbfc4f535a..eb4bde3b3cfe 100644 --- a/packages/react-native/scripts/cocoapods/rndependencies.rb +++ b/packages/react-native/scripts/cocoapods/rndependencies.rb @@ -232,8 +232,6 @@ def self.podspec_source_download_prebuild_release_tarball() end def self.release_tarball_url(version, build_type) - return nil if !ReactNativePodsUtils.maven_artifact_version_published?(version) - candidates = release_tarball_urls(version, build_type) return candidates.find { |url| artifact_exists(url) } || candidates.first end @@ -377,8 +375,6 @@ def self.download_rndeps_tarball(react_native_path, tarball_url, version, config end def self.release_artifact_exists(version) - return false if !ReactNativePodsUtils.maven_artifact_version_published?(version) - return release_tarball_urls(version, :debug).any? { |url| artifact_exists(url) } end diff --git a/packages/react-native/scripts/cocoapods/utils.rb b/packages/react-native/scripts/cocoapods/utils.rb index 5a2613c31021..3038897c8545 100644 --- a/packages/react-native/scripts/cocoapods/utils.rb +++ b/packages/react-native/scripts/cocoapods/utils.rb @@ -839,7 +839,8 @@ def self.resolve_use_frameworks(spec, header_mappings_dir: nil, module_name: nil # (DNS failure, no route, ...) the probe is left uncached so that a # transient hiccup doesn't permanently mark the artifact as missing. def self.artifact_exists?(tarball_url) - return false if tarball_url.include?("/#{UNPUBLISHED_MAVEN_VERSION}/") + unpublished_version = Regexp.escape(UNPUBLISHED_MAVEN_VERSION) + return false if tarball_url.match?(%r{/#{unpublished_version}(?:-SNAPSHOT)?/}) unless @@artifact_exists_cache.key?(tarball_url) # -L is used to follow redirects, useful for the nightlies diff --git a/packages/react-native/scripts/ios-prebuild/hermes.js b/packages/react-native/scripts/ios-prebuild/hermes.js index 9587373097d2..8956337fd3f2 100644 --- a/packages/react-native/scripts/ios-prebuild/hermes.js +++ b/packages/react-native/scripts/ios-prebuild/hermes.js @@ -66,10 +66,7 @@ async function prepareHermesArtifactsAsync( // Resolve the version from the environment variable or use the default version let resolvedVersion = process.env.HERMES_VERSION ?? 'latest-v1'; - if ( - resolvedVersion === 'latest-v1' || - !isMavenArtifactVersionPublished(resolvedVersion) - ) { + if (resolvedVersion === 'latest-v1') { // TODO: rename 'latest-v1' to 'latest' once V1 is the only Hermes on npm hermesLog('Using latest-v1 tarball'); const hermesVersion = await getLatestHermesVersionFromNPM(); @@ -211,6 +208,10 @@ async function findExistingTarballUrl( version /*: string */, buildType /*: BuildFlavor */, ) /*: Promise */ { + if (!isMavenArtifactVersionPublished(version)) { + return null; + } + const candidates = getTarballUrls(version, buildType); for (const url of candidates) { if (await hermesArtifactExists(url)) { @@ -348,6 +349,11 @@ async function downloadHermesTarball( const tmpFile = `${artifactsPath}/hermes-ios.download`; try { fs.mkdirSync(artifactsPath, {recursive: true}); + if (!isMavenArtifactVersionPublished(version)) { + throw new Error( + `Maven artifacts are not published for the development version ${version}`, + ); + } hermesLog(`Downloading Hermes tarball from ${tarballUrl}`); const response /*: Response */ = await fetch(tarballUrl); diff --git a/packages/react-native/scripts/ios-prebuild/reactNativeDependencies.js b/packages/react-native/scripts/ios-prebuild/reactNativeDependencies.js index 1321afc97f28..50b279709373 100644 --- a/packages/react-native/scripts/ios-prebuild/reactNativeDependencies.js +++ b/packages/react-native/scripts/ios-prebuild/reactNativeDependencies.js @@ -50,10 +50,7 @@ async function prepareReactNativeDependenciesArtifactsAsync( // Resolve the version from the environment variable or use the default version let resolvedVersion = process.env.RN_DEP_VERSION ?? version; - if ( - resolvedVersion === 'nightly' || - !isMavenArtifactVersionPublished(resolvedVersion) - ) { + if (resolvedVersion === 'nightly') { dependencyLog('Using latest nightly tarball'); const rnVersion = await getNightlyVersionFromNPM(); resolvedVersion = rnVersion; @@ -240,6 +237,10 @@ async function findExistingTarballUrl( version /*: string */, buildType /*: BuildFlavor */, ) /*: Promise */ { + if (!isMavenArtifactVersionPublished(version)) { + return null; + } + const candidates = getTarballUrls(version, buildType); for (const url of candidates) { if (await reactNativeDependenciesArtifactExists(url)) { @@ -405,6 +406,11 @@ async function downloadReactNativeDependenciesTarball( const tmpFile = `${artifactsPath}/reactnative-dependencies.download`; try { fs.mkdirSync(artifactsPath, {recursive: true}); + if (!isMavenArtifactVersionPublished(version)) { + throw new Error( + `Maven artifacts are not published for the development version ${version}`, + ); + } dependencyLog( `Downloading ReactNativeDependencies tarball from ${tarballUrl}`, ); diff --git a/packages/react-native/scripts/spm/__tests__/download-spm-artifacts-test.js b/packages/react-native/scripts/spm/__tests__/download-spm-artifacts-test.js index 5e433d5d20aa..2a24dbff8c5d 100644 --- a/packages/react-native/scripts/spm/__tests__/download-spm-artifacts-test.js +++ b/packages/react-native/scripts/spm/__tests__/download-spm-artifacts-test.js @@ -378,11 +378,25 @@ describe('mavenRepositoryUrls', () => { // --------------------------------------------------------------------------- describe('release URL builders', () => { - it('does not create Maven URLs for the unpublished main version', () => { + it('preserves Maven URLs for the unpublished main version', () => { expect(isMavenArtifactVersionPublished('1000.0.0')).toBe(false); - expect(rnCoreReleaseUrls('1000.0.0', 'debug')).toEqual([]); - expect(rnDepsReleaseUrls('1000.0.0', 'debug')).toEqual([]); - expect(hermesReleaseUrls('1000.0.0', 'debug')).toEqual([]); + expect(rnCoreReleaseUrls('1000.0.0', 'debug')).toHaveLength(2); + expect(rnDepsReleaseUrls('1000.0.0', 'debug')).toHaveLength(2); + expect(hermesReleaseUrls('1000.0.0', 'debug')).toHaveLength(2); + }); + + it('does not probe Maven for the unpublished main version', async () => { + const originalFetch = globalThis.fetch; + globalThis.fetch = jest.fn(); + + try { + await expect( + exists(rnCoreReleaseUrls('1000.0.0', 'debug')[0]), + ).resolves.toBe(false); + expect(globalThis.fetch).not.toHaveBeenCalled(); + } finally { + globalThis.fetch = originalFetch; + } }); it('rnCoreReleaseUrls builds a candidate per repository for the reactnative-core classifier', () => { diff --git a/packages/react-native/scripts/spm/download-spm-artifacts.js b/packages/react-native/scripts/spm/download-spm-artifacts.js index 0914e3ba9c37..82cb1dfc4ea9 100644 --- a/packages/react-native/scripts/spm/download-spm-artifacts.js +++ b/packages/react-native/scripts/spm/download-spm-artifacts.js @@ -150,6 +150,13 @@ function isMavenArtifactVersionPublished(version /*: string */) /*: boolean */ { return version !== UNPUBLISHED_MAVEN_VERSION; } +function isMavenArtifactUrlPublished(url /*: string */) /*: boolean */ { + return ( + !url.includes(`/${UNPUBLISHED_MAVEN_VERSION}/`) && + !url.includes(`/${UNPUBLISHED_MAVEN_VERSION}-SNAPSHOT/`) + ); +} + /** * The mirror is ON unless RCT_REACT_NATIVE_MAVEN_MIRROR_ENABLED is * explicitly "false"/"0". @@ -183,9 +190,6 @@ function rnCoreReleaseUrls( version /*: string */, flavor /*: string */, ) /*: Array */ { - if (!isMavenArtifactVersionPublished(version)) { - return []; - } return mavenRepositoryUrls().map( repository => `${repository}/com/facebook/react/react-native-artifacts/${version}/` + @@ -196,9 +200,6 @@ function rnDepsReleaseUrls( version /*: string */, flavor /*: string */, ) /*: Array */ { - if (!isMavenArtifactVersionPublished(version)) { - return []; - } return mavenRepositoryUrls().map( repository => `${repository}/com/facebook/react/react-native-artifacts/${version}/` + @@ -209,9 +210,6 @@ function hermesReleaseUrls( version /*: string */, flavor /*: string */, ) /*: Array */ { - if (!isMavenArtifactVersionPublished(version)) { - return []; - } return mavenRepositoryUrls().map( repository => `${repository}/com/facebook/hermes/hermes-ios/${version}/` + @@ -374,6 +372,10 @@ async function resolveLatestV1Version() /*: Promise */ { } async function exists(url /*: string */) /*: Promise */ { + if (!isMavenArtifactUrlPublished(url)) { + return false; + } + try { // $FlowFixMe[incompatible-call] global fetch not in Flow stubs const res = await fetch(url, {method: 'HEAD'}); diff --git a/packages/react-native/sdks/hermes-engine/hermes-utils.rb b/packages/react-native/sdks/hermes-engine/hermes-utils.rb index c0b68441dc87..ee13fc7c9240 100644 --- a/packages/react-native/sdks/hermes-engine/hermes-utils.rb +++ b/packages/react-native/sdks/hermes-engine/hermes-utils.rb @@ -100,8 +100,6 @@ def force_build_from_stable_branch(react_native_path) end def release_artifact_exists(version) - return false if version == UNPUBLISHED_HERMES_VERSION - return release_tarball_urls(version, :debug).any? { |url| hermes_artifact_exists(url) } end @@ -203,8 +201,6 @@ def hermestag_file(react_native_path) end def release_tarball_url(version, build_type) - return nil if version == UNPUBLISHED_HERMES_VERSION - candidates = release_tarball_urls(version, build_type) return candidates.find { |url| hermes_artifact_exists(url) } || candidates.first end @@ -361,7 +357,8 @@ def resolve_url_redirects(url) # Parameters # - tarball_url: the URL of the Hermes artifact to probe def hermes_artifact_exists(tarball_url) - return false if tarball_url.include?("/#{UNPUBLISHED_HERMES_VERSION}/") + unpublished_version = Regexp.escape(UNPUBLISHED_HERMES_VERSION) + return false if tarball_url.match?(%r{/#{unpublished_version}(?:-SNAPSHOT)?/}) unless HERMES_ARTIFACT_EXISTS_CACHE.key?(tarball_url) # -L is used to follow redirects, useful for the nightlies