Compare commits
2 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 26ddf1a59f | |||
| 5acd1d6166 |
@@ -1,5 +1,13 @@
|
|||||||
# Changelog
|
# Changelog
|
||||||
|
|
||||||
|
## 2026-03-24 - 2.8.1 - fix(registry)
|
||||||
|
align OCI and RubyGems API behavior and improve npm search result ordering
|
||||||
|
|
||||||
|
- handle OCI version checks on /v2 and /v2/ endpoints
|
||||||
|
- return RubyGems versions JSON in the expected flat array format and update unyank coverage to use the HTTP endpoint
|
||||||
|
- prioritize exact and prefix matches in npm search results
|
||||||
|
- update documentation to reflect full upstream proxy support
|
||||||
|
|
||||||
## 2026-03-24 - 2.8.0 - feat(core,storage,oci,registry-config)
|
## 2026-03-24 - 2.8.0 - feat(core,storage,oci,registry-config)
|
||||||
add streaming response support and configurable registry URLs across protocols
|
add streaming response support and configurable registry URLs across protocols
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@push.rocks/smartregistry",
|
"name": "@push.rocks/smartregistry",
|
||||||
"version": "2.8.0",
|
"version": "2.8.1",
|
||||||
"private": false,
|
"private": false,
|
||||||
"description": "A composable TypeScript library implementing OCI, NPM, Maven, Cargo, Composer, PyPI, and RubyGems registries for building unified container and package registries",
|
"description": "A composable TypeScript library implementing OCI, NPM, Maven, Cargo, Composer, PyPI, and RubyGems registries for building unified container and package registries",
|
||||||
"main": "dist_ts/index.js",
|
"main": "dist_ts/index.js",
|
||||||
|
|||||||
@@ -41,7 +41,7 @@ For reporting bugs, issues, or security vulnerabilities, please visit [community
|
|||||||
| Metadata API | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
|
| Metadata API | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
|
||||||
| Token Auth | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
|
| Token Auth | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
|
||||||
| Checksum Verification | ✅ | ✅ | ✅ | ✅ | — | ✅ | ✅ |
|
| Checksum Verification | ✅ | ✅ | ✅ | ✅ | — | ✅ | ✅ |
|
||||||
| Upstream Proxy | ✅ | ✅ | — | — | — | — | — |
|
| Upstream Proxy | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
|
||||||
|
|
||||||
### 🌐 Upstream Proxy & Caching
|
### 🌐 Upstream Proxy & Caching
|
||||||
- **Multi-Upstream Support**: Configure multiple upstream registries per protocol with priority ordering
|
- **Multi-Upstream Support**: Configure multiple upstream registries per protocol with priority ordering
|
||||||
|
|||||||
@@ -148,11 +148,16 @@ async function runGemCommand(
|
|||||||
cwd: string,
|
cwd: string,
|
||||||
includeAuth: boolean = true
|
includeAuth: boolean = true
|
||||||
): Promise<{ stdout: string; stderr: string; exitCode: number }> {
|
): Promise<{ stdout: string; stderr: string; exitCode: number }> {
|
||||||
|
// When not including auth, use a temp HOME without credentials
|
||||||
|
const effectiveHome = includeAuth ? gemHome : path.join(gemHome, 'noauth');
|
||||||
|
if (!includeAuth) {
|
||||||
|
fs.mkdirSync(effectiveHome, { recursive: true });
|
||||||
|
}
|
||||||
|
|
||||||
// Prepare environment variables
|
// Prepare environment variables
|
||||||
const envVars = [
|
const envVars = [
|
||||||
`HOME="${gemHome}"`,
|
`HOME="${effectiveHome}"`,
|
||||||
`GEM_HOME="${gemHome}"`,
|
`GEM_HOME="${gemHome}"`,
|
||||||
includeAuth ? '' : 'RUBYGEMS_API_KEY=""',
|
|
||||||
].filter(Boolean).join(' ');
|
].filter(Boolean).join(' ');
|
||||||
|
|
||||||
// Build command with cd to correct directory and environment variables
|
// Build command with cd to correct directory and environment variables
|
||||||
@@ -360,31 +365,33 @@ tap.test('RubyGems CLI: should unyank a version', async () => {
|
|||||||
const gemName = 'test-gem-cli';
|
const gemName = 'test-gem-cli';
|
||||||
const version = '1.0.0';
|
const version = '1.0.0';
|
||||||
|
|
||||||
const result = await runGemCommand(
|
// Use PUT /api/v1/gems/unyank via HTTP API (gem yank --undo removed in Ruby 4.0)
|
||||||
`gem yank ${gemName} -v ${version} --undo --host ${registryUrl}/rubygems`,
|
const response = await fetch(
|
||||||
testDir
|
`${registryUrl}/rubygems/api/v1/gems/unyank?gem_name=${gemName}&version=${version}`,
|
||||||
|
{
|
||||||
|
method: 'PUT',
|
||||||
|
headers: {
|
||||||
|
'Authorization': rubygemsToken,
|
||||||
|
},
|
||||||
|
}
|
||||||
);
|
);
|
||||||
console.log('gem unyank output:', result.stdout);
|
console.log('gem unyank status:', response.status);
|
||||||
console.log('gem unyank stderr:', result.stderr);
|
|
||||||
|
|
||||||
expect(result.exitCode).toEqual(0);
|
expect(response.status).toEqual(200);
|
||||||
|
|
||||||
// Verify version is not yanked in /versions file
|
// Verify version is not yanked in /versions file
|
||||||
const response = await fetch(`${registryUrl}/rubygems/versions`);
|
const versionsResponse = await fetch(`${registryUrl}/rubygems/versions`);
|
||||||
const versionsData = await response.text();
|
const versionsData = await versionsResponse.text();
|
||||||
console.log('Versions after unyank:', versionsData);
|
console.log('Versions after unyank:', versionsData);
|
||||||
|
|
||||||
// Should not have '-' prefix anymore (or have both without prefix)
|
// Should not have '-' prefix anymore
|
||||||
// Check that we have the version without yank marker
|
|
||||||
const lines = versionsData.trim().split('\n');
|
const lines = versionsData.trim().split('\n');
|
||||||
const gemLine = lines.find(line => line.startsWith(gemName));
|
const gemLine = lines.find(line => line.startsWith(gemName));
|
||||||
|
|
||||||
if (gemLine) {
|
if (gemLine) {
|
||||||
// Parse format: "gemname version[,version...] md5"
|
|
||||||
const parts = gemLine.split(' ');
|
const parts = gemLine.split(' ');
|
||||||
const versions = parts[1];
|
const versions = parts[1];
|
||||||
|
|
||||||
// Should have 1.0.0 without '-' prefix
|
|
||||||
expect(versions).toContain('1.0.0');
|
expect(versions).toContain('1.0.0');
|
||||||
expect(versions).not.toContain('-1.0.0');
|
expect(versions).not.toContain('-1.0.0');
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -324,13 +324,9 @@ tap.test('RubyGems: should retrieve versions JSON (GET /rubygems/api/v1/versions
|
|||||||
expect(response.status).toEqual(200);
|
expect(response.status).toEqual(200);
|
||||||
expect(response.headers['Content-Type']).toEqual('application/json');
|
expect(response.headers['Content-Type']).toEqual('application/json');
|
||||||
const json = await streamToJson(response.body);
|
const json = await streamToJson(response.body);
|
||||||
expect(json).toBeTypeOf('object');
|
expect(json).toBeInstanceOf(Array);
|
||||||
|
expect(json.length).toBeGreaterThan(0);
|
||||||
expect(json).toHaveProperty('name');
|
expect(json[0]).toHaveProperty('number');
|
||||||
expect(json.name).toEqual(testGemName);
|
|
||||||
expect(json).toHaveProperty('versions');
|
|
||||||
expect(json.versions).toBeTypeOf('object');
|
|
||||||
expect(json.versions.length).toBeGreaterThan(0);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
tap.test('RubyGems: should retrieve dependencies JSON (GET /rubygems/api/v1/dependencies)', async () => {
|
tap.test('RubyGems: should retrieve dependencies JSON (GET /rubygems/api/v1/dependencies)', async () => {
|
||||||
|
|||||||
@@ -3,6 +3,6 @@
|
|||||||
*/
|
*/
|
||||||
export const commitinfo = {
|
export const commitinfo = {
|
||||||
name: '@push.rocks/smartregistry',
|
name: '@push.rocks/smartregistry',
|
||||||
version: '2.8.0',
|
version: '2.8.1',
|
||||||
description: 'A composable TypeScript library implementing OCI, NPM, Maven, Cargo, Composer, PyPI, and RubyGems registries for building unified container and package registries'
|
description: 'A composable TypeScript library implementing OCI, NPM, Maven, Cargo, Composer, PyPI, and RubyGems registries for building unified container and package registries'
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -749,6 +749,22 @@ export class NpmRegistry extends BaseRegistry {
|
|||||||
this.logger.log('error', 'handleSearch failed', { error: (error as Error).message });
|
this.logger.log('error', 'handleSearch failed', { error: (error as Error).message });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Sort results by relevance: exact match first, then prefix match, then substring match
|
||||||
|
if (text) {
|
||||||
|
const lowerText = text.toLowerCase();
|
||||||
|
results.sort((a, b) => {
|
||||||
|
const aName = a.package.name.toLowerCase();
|
||||||
|
const bName = b.package.name.toLowerCase();
|
||||||
|
const aExact = aName === lowerText ? 0 : 1;
|
||||||
|
const bExact = bName === lowerText ? 0 : 1;
|
||||||
|
if (aExact !== bExact) return aExact - bExact;
|
||||||
|
const aPrefix = aName.startsWith(lowerText) ? 0 : 1;
|
||||||
|
const bPrefix = bName.startsWith(lowerText) ? 0 : 1;
|
||||||
|
if (aPrefix !== bPrefix) return aPrefix - bPrefix;
|
||||||
|
return aName.localeCompare(bName);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
// Apply pagination
|
// Apply pagination
|
||||||
const paginatedResults = results.slice(from, from + size);
|
const paginatedResults = results.slice(from, from + size);
|
||||||
|
|
||||||
|
|||||||
@@ -126,7 +126,8 @@ export class OciRegistry extends BaseRegistry {
|
|||||||
};
|
};
|
||||||
|
|
||||||
// Route to appropriate handler
|
// Route to appropriate handler
|
||||||
if (path === '/' || path === '') {
|
// OCI spec: GET /v2/ is the version check endpoint
|
||||||
|
if (path === '/' || path === '' || path === '/v2/' || path === '/v2') {
|
||||||
return this.handleVersionCheck();
|
return this.handleVersionCheck();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -254,14 +254,12 @@ export function generateVersionsJson(
|
|||||||
uploadTime?: string;
|
uploadTime?: string;
|
||||||
}>
|
}>
|
||||||
): any {
|
): any {
|
||||||
return {
|
// RubyGems.org API returns a flat array at /api/v1/versions/{gem}.json
|
||||||
name: gemName,
|
return versions.map(v => ({
|
||||||
versions: versions.map(v => ({
|
number: v.version,
|
||||||
number: v.version,
|
platform: v.platform || 'ruby',
|
||||||
platform: v.platform || 'ruby',
|
built_at: v.uploadTime,
|
||||||
built_at: v.uploadTime,
|
}));
|
||||||
})),
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
Reference in New Issue
Block a user