feat(core): Add PyPI and RubyGems registries, integrate into SmartRegistry, extend storage and auth
This commit is contained in:
@@ -6,7 +6,7 @@ import type { IRegistryConfig } from '../../ts/core/interfaces.core.js';
|
||||
const testQenv = new qenv.Qenv('./', './.nogit');
|
||||
|
||||
/**
|
||||
* Create a test SmartRegistry instance with OCI, NPM, Maven, and Composer enabled
|
||||
* Create a test SmartRegistry instance with all protocols enabled
|
||||
*/
|
||||
export async function createTestRegistry(): Promise<SmartRegistry> {
|
||||
// Read S3 config from env.json
|
||||
@@ -36,6 +36,12 @@ export async function createTestRegistry(): Promise<SmartRegistry> {
|
||||
realm: 'https://auth.example.com/token',
|
||||
service: 'test-registry',
|
||||
},
|
||||
pypiTokens: {
|
||||
enabled: true,
|
||||
},
|
||||
rubygemsTokens: {
|
||||
enabled: true,
|
||||
},
|
||||
},
|
||||
oci: {
|
||||
enabled: true,
|
||||
@@ -57,6 +63,14 @@ export async function createTestRegistry(): Promise<SmartRegistry> {
|
||||
enabled: true,
|
||||
basePath: '/cargo',
|
||||
},
|
||||
pypi: {
|
||||
enabled: true,
|
||||
basePath: '/pypi',
|
||||
},
|
||||
rubygems: {
|
||||
enabled: true,
|
||||
basePath: '/rubygems',
|
||||
},
|
||||
};
|
||||
|
||||
const registry = new SmartRegistry(config);
|
||||
@@ -100,7 +114,13 @@ export async function createTestTokens(registry: SmartRegistry) {
|
||||
// Create Cargo token with full access
|
||||
const cargoToken = await authManager.createCargoToken(userId, false);
|
||||
|
||||
return { npmToken, ociToken, mavenToken, composerToken, cargoToken, userId };
|
||||
// Create PyPI token with full access
|
||||
const pypiToken = await authManager.createPypiToken(userId, false);
|
||||
|
||||
// Create RubyGems token with full access
|
||||
const rubygemsToken = await authManager.createRubyGemsToken(userId, false);
|
||||
|
||||
return { npmToken, ociToken, mavenToken, composerToken, cargoToken, pypiToken, rubygemsToken, userId };
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -277,3 +297,257 @@ class TestClass
|
||||
|
||||
return zip.toBuffer();
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper to create a test Python wheel file (minimal ZIP structure)
|
||||
*/
|
||||
export async function createPythonWheel(
|
||||
packageName: string,
|
||||
version: string,
|
||||
pyVersion: string = 'py3'
|
||||
): Promise<Buffer> {
|
||||
const AdmZip = (await import('adm-zip')).default;
|
||||
const zip = new AdmZip();
|
||||
|
||||
const normalizedName = packageName.replace(/-/g, '_');
|
||||
const distInfoDir = `${normalizedName}-${version}.dist-info`;
|
||||
|
||||
// Create METADATA file
|
||||
const metadata = `Metadata-Version: 2.1
|
||||
Name: ${packageName}
|
||||
Version: ${version}
|
||||
Summary: Test Python package
|
||||
Home-page: https://example.com
|
||||
Author: Test Author
|
||||
Author-email: test@example.com
|
||||
License: MIT
|
||||
Platform: UNKNOWN
|
||||
Classifier: Programming Language :: Python :: 3
|
||||
Requires-Python: >=3.7
|
||||
Description-Content-Type: text/markdown
|
||||
|
||||
# ${packageName}
|
||||
|
||||
Test package for SmartRegistry
|
||||
`;
|
||||
|
||||
zip.addFile(`${distInfoDir}/METADATA`, Buffer.from(metadata, 'utf-8'));
|
||||
|
||||
// Create WHEEL file
|
||||
const wheelContent = `Wheel-Version: 1.0
|
||||
Generator: test 1.0.0
|
||||
Root-Is-Purelib: true
|
||||
Tag: ${pyVersion}-none-any
|
||||
`;
|
||||
|
||||
zip.addFile(`${distInfoDir}/WHEEL`, Buffer.from(wheelContent, 'utf-8'));
|
||||
|
||||
// Create RECORD file (empty for test)
|
||||
zip.addFile(`${distInfoDir}/RECORD`, Buffer.from('', 'utf-8'));
|
||||
|
||||
// Create top_level.txt
|
||||
zip.addFile(`${distInfoDir}/top_level.txt`, Buffer.from(normalizedName, 'utf-8'));
|
||||
|
||||
// Create a simple Python module
|
||||
const moduleContent = `"""${packageName} module"""
|
||||
|
||||
__version__ = "${version}"
|
||||
|
||||
def hello():
|
||||
return "Hello from ${packageName}!"
|
||||
`;
|
||||
|
||||
zip.addFile(`${normalizedName}/__init__.py`, Buffer.from(moduleContent, 'utf-8'));
|
||||
|
||||
return zip.toBuffer();
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper to create a test Python source distribution (sdist)
|
||||
*/
|
||||
export async function createPythonSdist(
|
||||
packageName: string,
|
||||
version: string
|
||||
): Promise<Buffer> {
|
||||
const tar = await import('tar-stream');
|
||||
const zlib = await import('zlib');
|
||||
const { Readable } = await import('stream');
|
||||
|
||||
const normalizedName = packageName.replace(/-/g, '_');
|
||||
const dirPrefix = `${packageName}-${version}`;
|
||||
|
||||
const pack = tar.pack();
|
||||
|
||||
// PKG-INFO
|
||||
const pkgInfo = `Metadata-Version: 2.1
|
||||
Name: ${packageName}
|
||||
Version: ${version}
|
||||
Summary: Test Python package
|
||||
Home-page: https://example.com
|
||||
Author: Test Author
|
||||
Author-email: test@example.com
|
||||
License: MIT
|
||||
`;
|
||||
|
||||
pack.entry({ name: `${dirPrefix}/PKG-INFO` }, pkgInfo);
|
||||
|
||||
// setup.py
|
||||
const setupPy = `from setuptools import setup, find_packages
|
||||
|
||||
setup(
|
||||
name="${packageName}",
|
||||
version="${version}",
|
||||
packages=find_packages(),
|
||||
python_requires=">=3.7",
|
||||
)
|
||||
`;
|
||||
|
||||
pack.entry({ name: `${dirPrefix}/setup.py` }, setupPy);
|
||||
|
||||
// Module file
|
||||
const moduleContent = `"""${packageName} module"""
|
||||
|
||||
__version__ = "${version}"
|
||||
|
||||
def hello():
|
||||
return "Hello from ${packageName}!"
|
||||
`;
|
||||
|
||||
pack.entry({ name: `${dirPrefix}/${normalizedName}/__init__.py` }, moduleContent);
|
||||
|
||||
pack.finalize();
|
||||
|
||||
// Convert to gzipped tar
|
||||
const chunks: Buffer[] = [];
|
||||
const gzip = zlib.createGzip();
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
pack.pipe(gzip);
|
||||
gzip.on('data', (chunk) => chunks.push(chunk));
|
||||
gzip.on('end', () => resolve(Buffer.concat(chunks)));
|
||||
gzip.on('error', reject);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper to calculate PyPI file hashes
|
||||
*/
|
||||
export function calculatePypiHashes(data: Buffer) {
|
||||
return {
|
||||
md5: crypto.createHash('md5').update(data).digest('hex'),
|
||||
sha256: crypto.createHash('sha256').update(data).digest('hex'),
|
||||
blake2b: crypto.createHash('blake2b512').update(data).digest('hex'),
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper to create a test RubyGem file (minimal tar.gz structure)
|
||||
*/
|
||||
export async function createRubyGem(
|
||||
gemName: string,
|
||||
version: string,
|
||||
platform: string = 'ruby'
|
||||
): Promise<Buffer> {
|
||||
const tar = await import('tar-stream');
|
||||
const zlib = await import('zlib');
|
||||
|
||||
const pack = tar.pack();
|
||||
|
||||
// Create metadata.gz (simplified)
|
||||
const metadataYaml = `--- !ruby/object:Gem::Specification
|
||||
name: ${gemName}
|
||||
version: !ruby/object:Gem::Version
|
||||
version: ${version}
|
||||
platform: ${platform}
|
||||
authors:
|
||||
- Test Author
|
||||
autorequire:
|
||||
bindir: bin
|
||||
cert_chain: []
|
||||
date: ${new Date().toISOString().split('T')[0]}
|
||||
dependencies: []
|
||||
description: Test RubyGem
|
||||
email: test@example.com
|
||||
executables: []
|
||||
extensions: []
|
||||
extra_rdoc_files: []
|
||||
files:
|
||||
- lib/${gemName}.rb
|
||||
homepage: https://example.com
|
||||
licenses:
|
||||
- MIT
|
||||
metadata: {}
|
||||
post_install_message:
|
||||
rdoc_options: []
|
||||
require_paths:
|
||||
- lib
|
||||
required_ruby_version: !ruby/object:Gem::Requirement
|
||||
requirements:
|
||||
- - ">="
|
||||
- !ruby/object:Gem::Version
|
||||
version: '2.7'
|
||||
required_rubygems_version: !ruby/object:Gem::Requirement
|
||||
requirements:
|
||||
- - ">="
|
||||
- !ruby/object:Gem::Version
|
||||
version: '0'
|
||||
requirements: []
|
||||
rubygems_version: 3.0.0
|
||||
signing_key:
|
||||
specification_version: 4
|
||||
summary: Test gem for SmartRegistry
|
||||
test_files: []
|
||||
`;
|
||||
|
||||
pack.entry({ name: 'metadata.gz' }, zlib.gzipSync(Buffer.from(metadataYaml, 'utf-8')));
|
||||
|
||||
// Create data.tar.gz (simplified)
|
||||
const dataPack = tar.pack();
|
||||
const libContent = `# ${gemName}
|
||||
|
||||
module ${gemName.charAt(0).toUpperCase() + gemName.slice(1).replace(/-/g, '')}
|
||||
VERSION = "${version}"
|
||||
|
||||
def self.hello
|
||||
"Hello from #{gemName}!"
|
||||
end
|
||||
end
|
||||
`;
|
||||
|
||||
dataPack.entry({ name: `lib/${gemName}.rb` }, libContent);
|
||||
dataPack.finalize();
|
||||
|
||||
const dataChunks: Buffer[] = [];
|
||||
const dataGzip = zlib.createGzip();
|
||||
dataPack.pipe(dataGzip);
|
||||
|
||||
await new Promise((resolve) => {
|
||||
dataGzip.on('data', (chunk) => dataChunks.push(chunk));
|
||||
dataGzip.on('end', resolve);
|
||||
});
|
||||
|
||||
pack.entry({ name: 'data.tar.gz' }, Buffer.concat(dataChunks));
|
||||
|
||||
pack.finalize();
|
||||
|
||||
// Convert to gzipped tar
|
||||
const chunks: Buffer[] = [];
|
||||
const gzip = zlib.createGzip();
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
pack.pipe(gzip);
|
||||
gzip.on('data', (chunk) => chunks.push(chunk));
|
||||
gzip.on('end', () => resolve(Buffer.concat(chunks)));
|
||||
gzip.on('error', reject);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper to calculate RubyGems checksums
|
||||
*/
|
||||
export function calculateRubyGemsChecksums(data: Buffer) {
|
||||
return {
|
||||
md5: crypto.createHash('md5').update(data).digest('hex'),
|
||||
sha256: crypto.createHash('sha256').update(data).digest('hex'),
|
||||
};
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user