feat(core): Add PyPI and RubyGems registries, integrate into SmartRegistry, extend storage and auth
This commit is contained in:
@@ -828,4 +828,240 @@ export class RegistryStorage implements IStorageBackend {
|
||||
private getPypiPackageFilePath(packageName: string, filename: string): string {
|
||||
return `pypi/packages/${packageName}/${filename}`;
|
||||
}
|
||||
|
||||
// ========================================================================
|
||||
// RUBYGEMS STORAGE METHODS
|
||||
// ========================================================================
|
||||
|
||||
/**
|
||||
* Get RubyGems versions file (compact index)
|
||||
*/
|
||||
public async getRubyGemsVersions(): Promise<string | null> {
|
||||
const path = this.getRubyGemsVersionsPath();
|
||||
const data = await this.getObject(path);
|
||||
return data ? data.toString('utf-8') : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Store RubyGems versions file (compact index)
|
||||
*/
|
||||
public async putRubyGemsVersions(content: string): Promise<void> {
|
||||
const path = this.getRubyGemsVersionsPath();
|
||||
const data = Buffer.from(content, 'utf-8');
|
||||
return this.putObject(path, data, { 'Content-Type': 'text/plain; charset=utf-8' });
|
||||
}
|
||||
|
||||
/**
|
||||
* Get RubyGems info file for a gem (compact index)
|
||||
*/
|
||||
public async getRubyGemsInfo(gemName: string): Promise<string | null> {
|
||||
const path = this.getRubyGemsInfoPath(gemName);
|
||||
const data = await this.getObject(path);
|
||||
return data ? data.toString('utf-8') : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Store RubyGems info file for a gem (compact index)
|
||||
*/
|
||||
public async putRubyGemsInfo(gemName: string, content: string): Promise<void> {
|
||||
const path = this.getRubyGemsInfoPath(gemName);
|
||||
const data = Buffer.from(content, 'utf-8');
|
||||
return this.putObject(path, data, { 'Content-Type': 'text/plain; charset=utf-8' });
|
||||
}
|
||||
|
||||
/**
|
||||
* Get RubyGems names file
|
||||
*/
|
||||
public async getRubyGemsNames(): Promise<string | null> {
|
||||
const path = this.getRubyGemsNamesPath();
|
||||
const data = await this.getObject(path);
|
||||
return data ? data.toString('utf-8') : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Store RubyGems names file
|
||||
*/
|
||||
public async putRubyGemsNames(content: string): Promise<void> {
|
||||
const path = this.getRubyGemsNamesPath();
|
||||
const data = Buffer.from(content, 'utf-8');
|
||||
return this.putObject(path, data, { 'Content-Type': 'text/plain; charset=utf-8' });
|
||||
}
|
||||
|
||||
/**
|
||||
* Get RubyGems .gem file
|
||||
*/
|
||||
public async getRubyGemsGem(gemName: string, version: string, platform?: string): Promise<Buffer | null> {
|
||||
const path = this.getRubyGemsGemPath(gemName, version, platform);
|
||||
return this.getObject(path);
|
||||
}
|
||||
|
||||
/**
|
||||
* Store RubyGems .gem file
|
||||
*/
|
||||
public async putRubyGemsGem(
|
||||
gemName: string,
|
||||
version: string,
|
||||
data: Buffer,
|
||||
platform?: string
|
||||
): Promise<void> {
|
||||
const path = this.getRubyGemsGemPath(gemName, version, platform);
|
||||
return this.putObject(path, data, { 'Content-Type': 'application/octet-stream' });
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if RubyGems .gem file exists
|
||||
*/
|
||||
public async rubyGemsGemExists(gemName: string, version: string, platform?: string): Promise<boolean> {
|
||||
const path = this.getRubyGemsGemPath(gemName, version, platform);
|
||||
return this.objectExists(path);
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete RubyGems .gem file
|
||||
*/
|
||||
public async deleteRubyGemsGem(gemName: string, version: string, platform?: string): Promise<void> {
|
||||
const path = this.getRubyGemsGemPath(gemName, version, platform);
|
||||
return this.deleteObject(path);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get RubyGems metadata
|
||||
*/
|
||||
public async getRubyGemsMetadata(gemName: string): Promise<any | null> {
|
||||
const path = this.getRubyGemsMetadataPath(gemName);
|
||||
const data = await this.getObject(path);
|
||||
return data ? JSON.parse(data.toString('utf-8')) : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Store RubyGems metadata
|
||||
*/
|
||||
public async putRubyGemsMetadata(gemName: string, metadata: any): Promise<void> {
|
||||
const path = this.getRubyGemsMetadataPath(gemName);
|
||||
const data = Buffer.from(JSON.stringify(metadata, null, 2), 'utf-8');
|
||||
return this.putObject(path, data, { 'Content-Type': 'application/json' });
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if RubyGems metadata exists
|
||||
*/
|
||||
public async rubyGemsMetadataExists(gemName: string): Promise<boolean> {
|
||||
const path = this.getRubyGemsMetadataPath(gemName);
|
||||
return this.objectExists(path);
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete RubyGems metadata
|
||||
*/
|
||||
public async deleteRubyGemsMetadata(gemName: string): Promise<void> {
|
||||
const path = this.getRubyGemsMetadataPath(gemName);
|
||||
return this.deleteObject(path);
|
||||
}
|
||||
|
||||
/**
|
||||
* List all RubyGems
|
||||
*/
|
||||
public async listRubyGems(): Promise<string[]> {
|
||||
const prefix = 'rubygems/metadata/';
|
||||
const objects = await this.listObjects(prefix);
|
||||
const gems = new Set<string>();
|
||||
|
||||
// Extract gem names from paths like: rubygems/metadata/gem-name/metadata.json
|
||||
for (const obj of objects) {
|
||||
const match = obj.match(/^rubygems\/metadata\/([^\/]+)\/metadata\.json$/);
|
||||
if (match) {
|
||||
gems.add(match[1]);
|
||||
}
|
||||
}
|
||||
|
||||
return Array.from(gems).sort();
|
||||
}
|
||||
|
||||
/**
|
||||
* List all versions of a RubyGem
|
||||
*/
|
||||
public async listRubyGemsVersions(gemName: string): Promise<string[]> {
|
||||
const prefix = `rubygems/gems/`;
|
||||
const objects = await this.listObjects(prefix);
|
||||
const versions = new Set<string>();
|
||||
|
||||
// Extract versions from filenames: gem-name-version[-platform].gem
|
||||
const gemPrefix = `${gemName}-`;
|
||||
for (const obj of objects) {
|
||||
const filename = obj.split('/').pop();
|
||||
if (!filename || !filename.startsWith(gemPrefix) || !filename.endsWith('.gem')) continue;
|
||||
|
||||
// Remove gem name prefix and .gem suffix
|
||||
const versionPart = filename.substring(gemPrefix.length, filename.length - 4);
|
||||
|
||||
// Split on last hyphen to separate version from platform
|
||||
const lastHyphen = versionPart.lastIndexOf('-');
|
||||
const version = lastHyphen > 0 ? versionPart.substring(0, lastHyphen) : versionPart;
|
||||
|
||||
versions.add(version);
|
||||
}
|
||||
|
||||
return Array.from(versions).sort();
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete entire RubyGem (all versions and files)
|
||||
*/
|
||||
public async deleteRubyGem(gemName: string): Promise<void> {
|
||||
// Delete metadata
|
||||
await this.deleteRubyGemsMetadata(gemName);
|
||||
|
||||
// Delete all gem files
|
||||
const prefix = `rubygems/gems/`;
|
||||
const objects = await this.listObjects(prefix);
|
||||
const gemPrefix = `${gemName}-`;
|
||||
|
||||
for (const obj of objects) {
|
||||
const filename = obj.split('/').pop();
|
||||
if (filename && filename.startsWith(gemPrefix) && filename.endsWith('.gem')) {
|
||||
await this.deleteObject(obj);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete specific version of a RubyGem
|
||||
*/
|
||||
public async deleteRubyGemsVersion(gemName: string, version: string, platform?: string): Promise<void> {
|
||||
// Delete gem file
|
||||
await this.deleteRubyGemsGem(gemName, version, platform);
|
||||
|
||||
// Update metadata to remove this version
|
||||
const metadata = await this.getRubyGemsMetadata(gemName);
|
||||
if (metadata && metadata.versions) {
|
||||
const versionKey = platform ? `${version}-${platform}` : version;
|
||||
delete metadata.versions[versionKey];
|
||||
await this.putRubyGemsMetadata(gemName, metadata);
|
||||
}
|
||||
}
|
||||
|
||||
// ========================================================================
|
||||
// RUBYGEMS PATH HELPERS
|
||||
// ========================================================================
|
||||
|
||||
private getRubyGemsVersionsPath(): string {
|
||||
return 'rubygems/versions';
|
||||
}
|
||||
|
||||
private getRubyGemsInfoPath(gemName: string): string {
|
||||
return `rubygems/info/${gemName}`;
|
||||
}
|
||||
|
||||
private getRubyGemsNamesPath(): string {
|
||||
return 'rubygems/names';
|
||||
}
|
||||
|
||||
private getRubyGemsGemPath(gemName: string, version: string, platform?: string): string {
|
||||
const filename = platform ? `${gemName}-${version}-${platform}.gem` : `${gemName}-${version}.gem`;
|
||||
return `rubygems/gems/${filename}`;
|
||||
}
|
||||
|
||||
private getRubyGemsMetadataPath(gemName: string): string {
|
||||
return `rubygems/metadata/${gemName}/metadata.json`;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user