Skip to content

Commit c8d1b5e

Browse files
authored
feat: add fallback for google maven (CM-1299) (#4275)
Signed-off-by: Umberto Sgueglia <usgueglia@contractor.linuxfoundation.org>
1 parent 867c8ac commit c8d1b5e

3 files changed

Lines changed: 99 additions & 15 deletions

File tree

services/apps/packages_worker/src/maven/__tests__/registry.test.ts

Lines changed: 34 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,11 @@
11
import { describe, expect, it } from 'vitest'
22

3-
import { resolveRegistryBaseUrl, resolveRegistryPageUrl } from '../registry'
3+
import {
4+
MAVEN_CENTRAL_BASE_URL,
5+
resolveRegistryBaseUrl,
6+
resolveRegistryPageUrl,
7+
resolveRegistryPageUrlFromBase,
8+
} from '../registry'
49

510
describe('resolveRegistryBaseUrl', () => {
611
it('returns Google Maven for androidx namespace', () => {
@@ -15,8 +20,15 @@ describe('resolveRegistryBaseUrl', () => {
1520
)
1621
})
1722

18-
it('returns Maven Central for bare com.google.android (legacy SDK stubs on Central)', () => {
19-
expect(resolveRegistryBaseUrl('com.google.android')).toBe('https://repo1.maven.org/maven2')
23+
it('returns Google Maven for bare com.google.android (legacy Android SDK stubs)', () => {
24+
expect(resolveRegistryBaseUrl('com.google.android')).toBe(
25+
'https://dl.google.com/dl/android/maven2',
26+
)
27+
})
28+
29+
it('does not match com.google.androidx — prefix boundary must end at a dot', () => {
30+
// com.google.android prefix must not bleed into unrelated com.google.android* strings
31+
expect(resolveRegistryBaseUrl('com.google.androidx')).toBe(MAVEN_CENTRAL_BASE_URL)
2032
})
2133

2234
it('returns Google Maven for android.arch (pre-AndroidX Architecture Components)', () => {
@@ -163,3 +175,22 @@ describe('resolveRegistryPageUrl', () => {
163175
)
164176
})
165177
})
178+
179+
describe('resolveRegistryPageUrlFromBase', () => {
180+
it('returns Sonatype Central URL when resolvedBaseUrl is Maven Central', () => {
181+
expect(
182+
resolveRegistryPageUrlFromBase(
183+
'com.google.firebase',
184+
'firebase-admin',
185+
MAVEN_CENTRAL_BASE_URL,
186+
),
187+
).toBe('https://central.sonatype.com/artifact/com.google.firebase/firebase-admin')
188+
})
189+
190+
it('returns alternative registry page URL when resolvedBaseUrl is not Central', () => {
191+
const googleMavenUrl = 'https://dl.google.com/dl/android/maven2'
192+
expect(
193+
resolveRegistryPageUrlFromBase('com.google.firebase', 'firebase-analytics', googleMavenUrl),
194+
).toBe('https://maven.google.com/web/index.html#com.google.firebase:firebase-analytics')
195+
})
196+
})

services/apps/packages_worker/src/maven/registry.ts

Lines changed: 37 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
* metadata.xml / POM fetch logic works — only the base URL changes.
1212
*/
1313

14-
const MAVEN_CENTRAL_BASE_URL = 'https://repo1.maven.org/maven2'
14+
export const MAVEN_CENTRAL_BASE_URL = 'https://repo1.maven.org/maven2'
1515

1616
interface RegistryEntry {
1717
prefix: string
@@ -25,10 +25,10 @@ const ALTERNATIVE_REGISTRIES: RegistryEntry[] = [
2525
baseUrl: 'https://dl.google.com/dl/android/maven2',
2626
pageUrl: (g, a) => `https://maven.google.com/web/index.html#${g}:${a}`,
2727
},
28-
// com.google.android (bare, no sub-namespace) are legacy SDK stubs on Maven Central —
29-
// only sub-namespaces like com.google.android.gms.* live on Google Maven.
28+
// com.google.android — all artifacts (bare namespace and sub-namespaces like
29+
// com.google.android.gms.*) are on Google Maven, not Maven Central.
3030
{
31-
prefix: 'com.google.android.',
31+
prefix: 'com.google.android',
3232
baseUrl: 'https://dl.google.com/dl/android/maven2',
3333
pageUrl: (g, a) => `https://maven.google.com/web/index.html#${g}:${a}`,
3434
},
@@ -137,7 +137,9 @@ const ALTERNATIVE_REGISTRIES: RegistryEntry[] = [
137137
]
138138

139139
function findEntry(groupId: string): RegistryEntry | undefined {
140-
return ALTERNATIVE_REGISTRIES.find((r) => groupId.startsWith(r.prefix))
140+
return ALTERNATIVE_REGISTRIES.find(
141+
(r) => groupId === r.prefix || groupId.startsWith(r.prefix + '.'),
142+
)
141143
}
142144

143145
/**
@@ -154,6 +156,16 @@ export function resolveRegistryBaseUrl(groupId: string): string {
154156
return process.env.MAVEN_FETCHER_BASE_URL ?? MAVEN_CENTRAL_BASE_URL
155157
}
156158

159+
/**
160+
* Returns true when the given groupId maps to a non-Central registry.
161+
* Used in the enrichment loop to decide whether a Central fallback lookup is worth trying
162+
* (e.g. com.google.firebase:firebase-admin is a server-side Java SDK on Central, while
163+
* most com.google.firebase artifacts are Android SDKs on Google Maven).
164+
*/
165+
export function isAlternativeRegistry(groupId: string): boolean {
166+
return findEntry(groupId) !== undefined
167+
}
168+
157169
/**
158170
* Returns a human-browsable URL for the given artifact in its authoritative registry.
159171
*/
@@ -162,3 +174,23 @@ export function resolveRegistryPageUrl(groupId: string, artifactId: string): str
162174
if (entry) return entry.pageUrl(groupId, artifactId)
163175
return `https://central.sonatype.com/artifact/${groupId}/${artifactId}`
164176
}
177+
178+
/**
179+
* Like resolveRegistryPageUrl, but uses the resolved base URL to determine which
180+
* registry page to show. When the artifact was actually fetched from Maven Central
181+
* (e.g. via the Central fallback after the primary alternative registry 404'd), the
182+
* namespace-based routing would point at the wrong registry page — this overrides it.
183+
*/
184+
export function resolveRegistryPageUrlFromBase(
185+
groupId: string,
186+
artifactId: string,
187+
resolvedBaseUrl: string,
188+
): string {
189+
const isCentral =
190+
resolvedBaseUrl === MAVEN_CENTRAL_BASE_URL ||
191+
resolvedBaseUrl === process.env.MAVEN_FETCHER_BASE_URL
192+
if (isCentral) {
193+
return `https://central.sonatype.com/artifact/${groupId}/${artifactId}`
194+
}
195+
return resolveRegistryPageUrl(groupId, artifactId)
196+
}

services/apps/packages_worker/src/maven/runMavenEnrichmentLoop.ts

Lines changed: 28 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,13 @@ import { getMavenConfig } from '../config'
1818
import { extractArtifact, getPomCacheStats, normalizeScmUrl } from './extract'
1919
import { isMavenFetchError, resolveVersionsList } from './metadata'
2020
import { isPrerelease, parseRepoUrl } from './normalize'
21-
import { resolveRegistryBaseUrl, resolveRegistryPageUrl } from './registry'
21+
import {
22+
MAVEN_CENTRAL_BASE_URL,
23+
isAlternativeRegistry,
24+
resolveRegistryBaseUrl,
25+
resolveRegistryPageUrl,
26+
resolveRegistryPageUrlFromBase,
27+
} from './registry'
2228

2329
const log = getServiceChildLogger('maven')
2430

@@ -115,10 +121,25 @@ async function processCriticalPackage(qx: QueryExecutor, pkg: PackageRow, forceF
115121
return { status: 'skipped' }
116122
}
117123

118-
const baseUrl = resolveRegistryBaseUrl(groupId)
124+
let baseUrl = resolveRegistryBaseUrl(groupId)
119125

120126
// Phase 1: lightweight metadata fetch to get the current upstream version.
121-
const metadata = await resolveVersionsList(groupId, artifactId, baseUrl)
127+
let metadata = await resolveVersionsList(groupId, artifactId, baseUrl)
128+
129+
// If the primary (non-Central) registry returned NOT_FOUND, try Maven Central as a
130+
// fallback. This handles artifacts that share a namespace with non-Central packages but
131+
// are themselves published on Central — e.g. com.google.firebase:firebase-admin is the
132+
// server-side Java SDK on Central, while most com.google.firebase artifacts are Android
133+
// SDKs on Google Maven.
134+
if (isMavenFetchError(metadata) && metadata.kind === 'NOT_FOUND' && isAlternativeRegistry(groupId)) {
135+
const centralUrl = process.env.MAVEN_FETCHER_BASE_URL ?? MAVEN_CENTRAL_BASE_URL
136+
const centralMetadata = await resolveVersionsList(groupId, artifactId, centralUrl)
137+
if (!isMavenFetchError(centralMetadata)) {
138+
log.info({ groupId, artifactId }, 'Not found in primary registry — resolved via Maven Central fallback')
139+
baseUrl = centralUrl
140+
metadata = centralMetadata
141+
}
142+
}
122143

123144
if (isMavenFetchError(metadata)) {
124145
if (metadata.kind === 'NOT_FOUND') {
@@ -129,7 +150,7 @@ async function processCriticalPackage(qx: QueryExecutor, pkg: PackageRow, forceF
129150
name: artifactId,
130151
description: null,
131152
homepage: null,
132-
registryUrl: resolveRegistryPageUrl(groupId, artifactId),
153+
registryUrl: resolveRegistryPageUrlFromBase(groupId, artifactId, baseUrl),
133154
declaredRepositoryUrl: null,
134155
repositoryUrl: null,
135156
licenses: null,
@@ -164,7 +185,7 @@ async function processCriticalPackage(qx: QueryExecutor, pkg: PackageRow, forceF
164185
name: artifactId,
165186
description: null,
166187
homepage: null,
167-
registryUrl: resolveRegistryPageUrl(groupId, artifactId),
188+
registryUrl: resolveRegistryPageUrlFromBase(groupId, artifactId, baseUrl),
168189
declaredRepositoryUrl: null,
169190
repositoryUrl: null,
170191
licenses: null,
@@ -201,7 +222,7 @@ async function processCriticalPackage(qx: QueryExecutor, pkg: PackageRow, forceF
201222
name: artifactId,
202223
description: null,
203224
homepage: null,
204-
registryUrl: resolveRegistryPageUrl(groupId, artifactId),
225+
registryUrl: resolveRegistryPageUrlFromBase(groupId, artifactId, baseUrl),
205226
declaredRepositoryUrl: null,
206227
repositoryUrl: null,
207228
licenses: null,
@@ -227,7 +248,7 @@ async function processCriticalPackage(qx: QueryExecutor, pkg: PackageRow, forceF
227248
name: artifactId,
228249
description: result.description,
229250
homepage: result.homepageUrl,
230-
registryUrl: resolveRegistryPageUrl(groupId, artifactId),
251+
registryUrl: resolveRegistryPageUrlFromBase(groupId, artifactId, baseUrl),
231252
declaredRepositoryUrl: result.scmUrl,
232253
repositoryUrl,
233254
licenses: result.licenses.length > 0 ? result.licenses : null,

0 commit comments

Comments
 (0)