@@ -11,7 +11,6 @@ import { XMLParser } from 'fast-xml-parser'
1111export interface PomMaintainer {
1212 username : string | null
1313 displayName : string | null
14- /** Raw email from POM — hash with SHA-256 before storing (GDPR) */
1514 email : string | null
1615 url : string | null
1716 role : 'author' | 'maintainer'
@@ -55,7 +54,7 @@ interface PomPerson {
5554// ─── Config ───────────────────────────────────────────────────────────────────
5655
5756const MAVEN_REPO = 'https://repo1.maven.org/maven2'
58- const MAX_PARENT_HOPS = 5
57+ export const MAX_PARENT_HOPS = 7
5958const REQUEST_TIMEOUT_MS = 15_000
6059
6160const parser = new XMLParser ( {
@@ -80,8 +79,10 @@ async function getWithRetry(url: string): Promise<string> {
8079 const res = await axios . get < string > ( url , { responseType : 'text' , timeout : REQUEST_TIMEOUT_MS } )
8180 return res . data
8281 } catch ( err ) {
83- if ( axios . isAxiosError ( err ) && err . response ?. status === 429 ) {
84- if ( attempt < MAX_RETRIES ) {
82+ if ( axios . isAxiosError ( err ) ) {
83+ const status = err . response ?. status
84+ // 429 = explicit rate limit, 403 = CDN throttle (Maven Central uses both)
85+ if ( ( status === 429 || status === 403 ) && attempt < MAX_RETRIES ) {
8586 const delay = RETRY_BASE_MS * 2 ** attempt + Math . random ( ) * 500
8687 await sleep ( delay )
8788 continue
@@ -195,10 +196,65 @@ async function resolveWithInheritance(
195196 }
196197}
197198
198- // ─── Public entry point ─ ──────────────────────────────────────────────────────
199+ // ─── Public entry points ──────────────────────────────────────────────────────
199200
200201/**
201- * Fetches and resolves POM metadata for the given Maven artifact.
202+ * Fetches only the root POM without following the parent chain.
203+ * Faster than extractArtifact — use for non-critical packages where inherited
204+ * fields (licenses, SCM) may be missing but throughput matters more.
205+ */
206+ export async function extractArtifactDirect (
207+ groupId : string ,
208+ artifactId : string ,
209+ version : string ,
210+ log : ( msg : string ) => void = ( ) => undefined ,
211+ ) : Promise < PomExtractionResult > {
212+ const purl = `pkg:maven/${ groupId } /${ artifactId } @${ version } `
213+ const pom = await fetchPom ( groupId , artifactId , version , log )
214+
215+ if ( ! pom ) {
216+ return {
217+ groupId,
218+ artifactId,
219+ version,
220+ purl,
221+ description : null ,
222+ licenses : [ ] ,
223+ licensesRaw : null ,
224+ scmUrl : null ,
225+ homepageUrl : null ,
226+ developers : [ ] ,
227+ contributors : [ ] ,
228+ parentHops : 0 ,
229+ error : `POM not found: ${ buildPomUrl ( groupId , artifactId , version ) } ` ,
230+ }
231+ }
232+
233+ const licenses = extractLicenses ( pom )
234+ const scmUrl = extractStr ( pom . scm ?. url ?? pom . scm ?. connection )
235+ const developers = extractPersons ( pom . developers ?. developer , 'author' )
236+ const contributors = extractPersons ( pom . contributors ?. contributor , 'maintainer' )
237+
238+ return {
239+ groupId,
240+ artifactId,
241+ version,
242+ purl,
243+ description : extractStr ( pom . description ) ,
244+ licenses,
245+ licensesRaw : licenses . length > 0 ? licenses . join ( ', ' ) : null ,
246+ scmUrl,
247+ homepageUrl : extractStr ( pom . url ) ,
248+ developers,
249+ contributors,
250+ parentHops : 0 ,
251+ error : null ,
252+ }
253+ }
254+
255+ /**
256+ * Fetches and resolves POM metadata for the given Maven artifact, following
257+ * the parent chain to inherit licenses and SCM when not in the direct POM.
202258 * Always returns a result object; errors are captured in `result.error`.
203259 */
204260export async function extractArtifact (
0 commit comments