feat(upstream): Add upstream proxy/cache subsystem and integrate per-protocol upstreams
This commit is contained in:
220
ts/maven/classes.mavenupstream.ts
Normal file
220
ts/maven/classes.mavenupstream.ts
Normal file
@@ -0,0 +1,220 @@
|
||||
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}`;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user