221 lines
5.4 KiB
TypeScript
221 lines
5.4 KiB
TypeScript
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<Buffer | null> {
|
|
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<string | null> {
|
|
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<string | null> {
|
|
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}`;
|
|
}
|
|
}
|