feat(core): Add Cargo and Composer registries with storage, auth and helpers
This commit is contained in:
@@ -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`;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user