feat(core): Add Cargo and Composer registries with storage, auth and helpers

This commit is contained in:
2025-11-21 09:13:02 +00:00
parent 92d27d8b15
commit 8d48627301
19 changed files with 1869 additions and 56 deletions

View File

@@ -392,4 +392,202 @@ export class RegistryStorage implements IStorageBackend {
const groupPath = groupId.replace(/\./g, '/');
return `maven/metadata/${groupPath}/${artifactId}/maven-metadata.xml`;
}
// ========================================================================
// CARGO-SPECIFIC HELPERS
// ========================================================================
/**
* Get Cargo config.json
*/
public async getCargoConfig(): Promise<any | null> {
const data = await this.getObject('cargo/config.json');
return data ? JSON.parse(data.toString('utf-8')) : null;
}
/**
* Store Cargo config.json
*/
public async putCargoConfig(config: any): Promise<void> {
const data = Buffer.from(JSON.stringify(config, null, 2), 'utf-8');
return this.putObject('cargo/config.json', data, { 'Content-Type': 'application/json' });
}
/**
* Get Cargo index file (newline-delimited JSON)
*/
public async getCargoIndex(crateName: string): Promise<any[] | null> {
const path = this.getCargoIndexPath(crateName);
const data = await this.getObject(path);
if (!data) return null;
// Parse newline-delimited JSON
const lines = data.toString('utf-8').split('\n').filter(line => line.trim());
return lines.map(line => JSON.parse(line));
}
/**
* Store Cargo index file
*/
public async putCargoIndex(crateName: string, entries: any[]): Promise<void> {
const path = this.getCargoIndexPath(crateName);
// Convert to newline-delimited JSON
const data = Buffer.from(entries.map(e => JSON.stringify(e)).join('\n') + '\n', 'utf-8');
return this.putObject(path, data, { 'Content-Type': 'text/plain' });
}
/**
* Get Cargo .crate file
*/
public async getCargoCrate(crateName: string, version: string): Promise<Buffer | null> {
const path = this.getCargoCratePath(crateName, version);
return this.getObject(path);
}
/**
* Store Cargo .crate file
*/
public async putCargoCrate(
crateName: string,
version: string,
crateFile: Buffer
): Promise<void> {
const path = this.getCargoCratePath(crateName, version);
return this.putObject(path, crateFile, { 'Content-Type': 'application/gzip' });
}
/**
* Check if Cargo crate exists
*/
public async cargoCrateExists(crateName: string, version: string): Promise<boolean> {
const path = this.getCargoCratePath(crateName, version);
return this.objectExists(path);
}
/**
* Delete Cargo crate (for cleanup, not for unpublishing)
*/
public async deleteCargoCrate(crateName: string, version: string): Promise<void> {
const path = this.getCargoCratePath(crateName, version);
return this.deleteObject(path);
}
// ========================================================================
// CARGO PATH HELPERS
// ========================================================================
private getCargoIndexPath(crateName: string): string {
const lower = crateName.toLowerCase();
const len = lower.length;
if (len === 1) {
return `cargo/index/1/${lower}`;
} else if (len === 2) {
return `cargo/index/2/${lower}`;
} else if (len === 3) {
return `cargo/index/3/${lower.charAt(0)}/${lower}`;
} else {
// 4+ characters: {first-two}/{second-two}/{name}
const prefix1 = lower.substring(0, 2);
const prefix2 = lower.substring(2, 4);
return `cargo/index/${prefix1}/${prefix2}/${lower}`;
}
}
private getCargoCratePath(crateName: string, version: string): string {
return `cargo/crates/${crateName}/${crateName}-${version}.crate`;
}
// ========================================================================
// COMPOSER-SPECIFIC HELPERS
// ========================================================================
/**
* Get Composer package metadata
*/
public async getComposerPackageMetadata(vendorPackage: string): Promise<any | null> {
const path = this.getComposerMetadataPath(vendorPackage);
const data = await this.getObject(path);
return data ? JSON.parse(data.toString('utf-8')) : null;
}
/**
* Store Composer package metadata
*/
public async putComposerPackageMetadata(vendorPackage: string, metadata: any): Promise<void> {
const path = this.getComposerMetadataPath(vendorPackage);
const data = Buffer.from(JSON.stringify(metadata, null, 2), 'utf-8');
return this.putObject(path, data, { 'Content-Type': 'application/json' });
}
/**
* Get Composer package ZIP
*/
public async getComposerPackageZip(vendorPackage: string, reference: string): Promise<Buffer | null> {
const path = this.getComposerZipPath(vendorPackage, reference);
return this.getObject(path);
}
/**
* Store Composer package ZIP
*/
public async putComposerPackageZip(vendorPackage: string, reference: string, zipData: Buffer): Promise<void> {
const path = this.getComposerZipPath(vendorPackage, reference);
return this.putObject(path, zipData, { 'Content-Type': 'application/zip' });
}
/**
* Check if Composer package metadata exists
*/
public async composerPackageMetadataExists(vendorPackage: string): Promise<boolean> {
const path = this.getComposerMetadataPath(vendorPackage);
return this.objectExists(path);
}
/**
* Delete Composer package metadata
*/
public async deleteComposerPackageMetadata(vendorPackage: string): Promise<void> {
const path = this.getComposerMetadataPath(vendorPackage);
return this.deleteObject(path);
}
/**
* Delete Composer package ZIP
*/
public async deleteComposerPackageZip(vendorPackage: string, reference: string): Promise<void> {
const path = this.getComposerZipPath(vendorPackage, reference);
return this.deleteObject(path);
}
/**
* List all Composer packages
*/
public async listComposerPackages(): Promise<string[]> {
const prefix = 'composer/packages/';
const objects = await this.listObjects(prefix);
const packages = new Set<string>();
// Extract vendor/package from paths like: composer/packages/vendor/package/metadata.json
for (const obj of objects) {
const match = obj.match(/^composer\/packages\/([^\/]+\/[^\/]+)\/metadata\.json$/);
if (match) {
packages.add(match[1]);
}
}
return Array.from(packages).sort();
}
// ========================================================================
// COMPOSER PATH HELPERS
// ========================================================================
private getComposerMetadataPath(vendorPackage: string): string {
return `composer/packages/${vendorPackage}/metadata.json`;
}
private getComposerZipPath(vendorPackage: string, reference: string): string {
return `composer/packages/${vendorPackage}/${reference}.zip`;
}
}