import * as plugins from '../plugins.js'; import { BaseUpstream } from '../upstream/classes.baseupstream.js'; import type { IProtocolUpstreamConfig, IUpstreamFetchContext, IUpstreamRegistryConfig, } from '../upstream/interfaces.upstream.js'; import type { IMavenCoordinate } from './interfaces.maven.js'; /** * Maven-specific upstream implementation. * * Handles: * - Artifact fetching (JAR, POM, WAR, etc.) * - Metadata fetching (maven-metadata.xml) * - Checksum files (.md5, .sha1, .sha256, .sha512) * - SNAPSHOT version handling * - Content-addressable caching for release artifacts */ export class MavenUpstream extends BaseUpstream { protected readonly protocolName = 'maven'; constructor( config: IProtocolUpstreamConfig, logger?: plugins.smartlog.Smartlog, ) { super(config, logger); } /** * Fetch an artifact from upstream registries. */ public async fetchArtifact( groupId: string, artifactId: string, version: string, extension: string, classifier?: string, ): Promise { const path = this.buildArtifactPath(groupId, artifactId, version, extension, classifier); const resource = `${groupId}:${artifactId}`; const context: IUpstreamFetchContext = { protocol: 'maven', resource, resourceType: 'artifact', path, method: 'GET', headers: {}, query: {}, }; const result = await this.fetch(context); if (!result || !result.success) { return null; } return Buffer.isBuffer(result.body) ? result.body : Buffer.from(result.body); } /** * Fetch maven-metadata.xml from upstream. */ public async fetchMetadata(groupId: string, artifactId: string, version?: string): Promise { const groupPath = groupId.replace(/\./g, '/'); let path: string; if (version) { // Version-level metadata (for SNAPSHOTs) path = `/${groupPath}/${artifactId}/${version}/maven-metadata.xml`; } else { // Artifact-level metadata (lists all versions) path = `/${groupPath}/${artifactId}/maven-metadata.xml`; } const resource = `${groupId}:${artifactId}`; const context: IUpstreamFetchContext = { protocol: 'maven', resource, resourceType: 'metadata', path, method: 'GET', headers: { 'accept': 'application/xml, text/xml', }, query: {}, }; const result = await this.fetch(context); if (!result || !result.success) { return null; } if (Buffer.isBuffer(result.body)) { return result.body.toString('utf8'); } return typeof result.body === 'string' ? result.body : null; } /** * Fetch a checksum file from upstream. */ public async fetchChecksum( groupId: string, artifactId: string, version: string, extension: string, checksumType: 'md5' | 'sha1' | 'sha256' | 'sha512', classifier?: string, ): Promise { const basePath = this.buildArtifactPath(groupId, artifactId, version, extension, classifier); const path = `${basePath}.${checksumType}`; const resource = `${groupId}:${artifactId}`; const context: IUpstreamFetchContext = { protocol: 'maven', resource, resourceType: 'checksum', path, method: 'GET', headers: { 'accept': 'text/plain', }, query: {}, }; const result = await this.fetch(context); if (!result || !result.success) { return null; } if (Buffer.isBuffer(result.body)) { return result.body.toString('utf8').trim(); } return typeof result.body === 'string' ? result.body.trim() : null; } /** * Check if an artifact exists in upstream (HEAD request). */ public async headArtifact( groupId: string, artifactId: string, version: string, extension: string, classifier?: string, ): Promise<{ exists: boolean; size?: number; lastModified?: string } | null> { const path = this.buildArtifactPath(groupId, artifactId, version, extension, classifier); const resource = `${groupId}:${artifactId}`; const context: IUpstreamFetchContext = { protocol: 'maven', resource, resourceType: 'artifact', path, method: 'HEAD', headers: {}, query: {}, }; const result = await this.fetch(context); if (!result) { return null; } if (!result.success) { return { exists: false }; } return { exists: true, size: result.headers['content-length'] ? parseInt(result.headers['content-length'], 10) : undefined, lastModified: result.headers['last-modified'], }; } /** * Build the path for a Maven artifact. */ private buildArtifactPath( groupId: string, artifactId: string, version: string, extension: string, classifier?: string, ): string { const groupPath = groupId.replace(/\./g, '/'); let filename = `${artifactId}-${version}`; if (classifier) { filename += `-${classifier}`; } filename += `.${extension}`; return `/${groupPath}/${artifactId}/${version}/${filename}`; } /** * Override URL building for Maven-specific handling. */ protected buildUpstreamUrl( upstream: IUpstreamRegistryConfig, context: IUpstreamFetchContext, ): string { let baseUrl = upstream.url; // Remove trailing slash if (baseUrl.endsWith('/')) { baseUrl = baseUrl.slice(0, -1); } return `${baseUrl}${context.path}`; } }