Compare commits
4 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 8b3da7dece | |||
| 45e24ecff3 | |||
| 2e2726a4de | |||
| 0e35256062 |
@@ -1,5 +1,19 @@
|
||||
# Changelog
|
||||
|
||||
## 2026-04-30 - 2.9.2 - fix(core,testing)
|
||||
improve type safety and update tests for latest tstest and storage APIs
|
||||
|
||||
- harden header extraction in BaseRegistry to safely handle request contexts with missing or invalid headers
|
||||
- migrate tests to the current tstest tapbundle APIs and assertion methods
|
||||
- adjust binary upload tests to use Uint8Array and add null guards for stricter TypeScript checks
|
||||
- update upstream and S3 cache test helpers to match newer registry and storage interfaces
|
||||
- enable stricter TypeScript compiler settings and refresh related dependency versions
|
||||
|
||||
## 2026-04-16 - 2.9.1 - fix(license)
|
||||
add missing MIT license file to repository
|
||||
|
||||
- Adds the project license file to align the repository contents with the package metadata license declaration.
|
||||
|
||||
## 2026-04-16 - 2.9.0 - feat(registry)
|
||||
add declarative protocol routing and request-scoped storage hook context across registries
|
||||
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
Copyright (c) 2025 Task Venture Capital GmbH (hello@task.vc)
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
+14
-14
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@push.rocks/smartregistry",
|
||||
"version": "2.9.0",
|
||||
"version": "2.9.2",
|
||||
"private": false,
|
||||
"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",
|
||||
@@ -10,17 +10,19 @@
|
||||
"license": "MIT",
|
||||
"scripts": {
|
||||
"test": "(tstest test/ --verbose --logfile --timeout 240)",
|
||||
"build": "(tsbuild --allowimplicitany)",
|
||||
"build": "(tsbuild)",
|
||||
"buildDocs": "(tsdoc)"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@git.zone/tsbuild": "^4.4.0",
|
||||
"@git.zone/tsbundle": "^2.10.0",
|
||||
"@git.zone/tsrun": "^2.0.2",
|
||||
"@git.zone/tstest": "^3.6.0",
|
||||
"@git.zone/tstest": "^3.6.3",
|
||||
"@push.rocks/smartarchive": "^5.2.1",
|
||||
"@push.rocks/smartstorage": "^6.3.2",
|
||||
"@types/node": "^25.5.0"
|
||||
"@push.rocks/smartstorage": "^6.4.1",
|
||||
"@types/adm-zip": "^0.5.8",
|
||||
"@types/lodash.clonedeep": "^4.5.9",
|
||||
"@types/node": "^25.6.0"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
@@ -40,20 +42,18 @@
|
||||
"assets/**/*",
|
||||
"cli.js",
|
||||
".smartconfig.json",
|
||||
"license",
|
||||
"readme.md"
|
||||
],
|
||||
"pnpm": {
|
||||
"overrides": {}
|
||||
},
|
||||
"dependencies": {
|
||||
"@push.rocks/qenv": "^6.1.3",
|
||||
"@push.rocks/smartbucket": "^4.5.1",
|
||||
"@push.rocks/smartlog": "^3.2.1",
|
||||
"@push.rocks/smartbucket": "^4.6.1",
|
||||
"@push.rocks/smartlog": "^3.2.2",
|
||||
"@push.rocks/smartpath": "^6.0.0",
|
||||
"@push.rocks/smartrequest": "^5.0.1",
|
||||
"@tsclass/tsclass": "^9.5.0",
|
||||
"adm-zip": "^0.5.16",
|
||||
"minimatch": "^10.2.4"
|
||||
"@tsclass/tsclass": "^9.5.1",
|
||||
"adm-zip": "^0.5.17",
|
||||
"minimatch": "^10.2.5"
|
||||
},
|
||||
"packageManager": "pnpm@10.18.1+sha512.77a884a165cbba2d8d1c19e3b4880eee6d2fcabd0d879121e282196b80042351d5eb3ca0935fa599da1dc51265cc68816ad2bddd2a2de5ea9fdf92adbec7cd34"
|
||||
"packageManager": "pnpm@10.28.2"
|
||||
}
|
||||
|
||||
Generated
+1263
-1338
File diff suppressed because it is too large
Load Diff
+31
-31
@@ -1,4 +1,4 @@
|
||||
import { tap, expect } from '@git.zone/tstest';
|
||||
import { tap, expect } from '@git.zone/tstest/tapbundle';
|
||||
import { RegistryStorage } from '../ts/core/classes.registrystorage.js';
|
||||
import { CargoRegistry } from '../ts/cargo/classes.cargoregistry.js';
|
||||
import { AuthManager } from '../ts/core/classes.authmanager.js';
|
||||
@@ -17,21 +17,21 @@ tap.test('should calculate correct index paths for different crate names', async
|
||||
const getPath = (storage as any).getCargoIndexPath.bind(storage);
|
||||
|
||||
// 1-character names
|
||||
expect(getPath('a')).to.equal('cargo/index/1/a');
|
||||
expect(getPath('z')).to.equal('cargo/index/1/z');
|
||||
expect(getPath('a')).toEqual('cargo/index/1/a');
|
||||
expect(getPath('z')).toEqual('cargo/index/1/z');
|
||||
|
||||
// 2-character names
|
||||
expect(getPath('io')).to.equal('cargo/index/2/io');
|
||||
expect(getPath('ab')).to.equal('cargo/index/2/ab');
|
||||
expect(getPath('io')).toEqual('cargo/index/2/io');
|
||||
expect(getPath('ab')).toEqual('cargo/index/2/ab');
|
||||
|
||||
// 3-character names
|
||||
expect(getPath('axo')).to.equal('cargo/index/3/a/axo');
|
||||
expect(getPath('foo')).to.equal('cargo/index/3/f/foo');
|
||||
expect(getPath('axo')).toEqual('cargo/index/3/a/axo');
|
||||
expect(getPath('foo')).toEqual('cargo/index/3/f/foo');
|
||||
|
||||
// 4+ character names
|
||||
expect(getPath('serde')).to.equal('cargo/index/se/rd/serde');
|
||||
expect(getPath('tokio')).to.equal('cargo/index/to/ki/tokio');
|
||||
expect(getPath('my-crate')).to.equal('cargo/index/my/--/my-crate');
|
||||
expect(getPath('serde')).toEqual('cargo/index/se/rd/serde');
|
||||
expect(getPath('tokio')).toEqual('cargo/index/to/ki/tokio');
|
||||
expect(getPath('my-crate')).toEqual('cargo/index/my/--/my-crate');
|
||||
});
|
||||
|
||||
// Test crate file path calculation
|
||||
@@ -46,9 +46,9 @@ tap.test('should calculate correct crate file paths', async () => {
|
||||
// Access private method for testing
|
||||
const getPath = (storage as any).getCargoCratePath.bind(storage);
|
||||
|
||||
expect(getPath('serde', '1.0.0')).to.equal('cargo/crates/serde/serde-1.0.0.crate');
|
||||
expect(getPath('tokio', '1.28.0')).to.equal('cargo/crates/tokio/tokio-1.28.0.crate');
|
||||
expect(getPath('my-crate', '0.1.0')).to.equal('cargo/crates/my-crate/my-crate-0.1.0.crate');
|
||||
expect(getPath('serde', '1.0.0')).toEqual('cargo/crates/serde/serde-1.0.0.crate');
|
||||
expect(getPath('tokio', '1.28.0')).toEqual('cargo/crates/tokio/tokio-1.28.0.crate');
|
||||
expect(getPath('my-crate', '0.1.0')).toEqual('cargo/crates/my-crate/my-crate-0.1.0.crate');
|
||||
});
|
||||
|
||||
// Test crate name validation
|
||||
@@ -73,28 +73,28 @@ tap.test('should validate crate names correctly', async () => {
|
||||
const validate = (registry as any).validateCrateName.bind(registry);
|
||||
|
||||
// Valid names
|
||||
expect(validate('serde')).to.be.true;
|
||||
expect(validate('tokio')).to.be.true;
|
||||
expect(validate('my-crate')).to.be.true;
|
||||
expect(validate('my_crate')).to.be.true;
|
||||
expect(validate('crate123')).to.be.true;
|
||||
expect(validate('a')).to.be.true;
|
||||
expect(validate('serde')).toBeTrue();
|
||||
expect(validate('tokio')).toBeTrue();
|
||||
expect(validate('my-crate')).toBeTrue();
|
||||
expect(validate('my_crate')).toBeTrue();
|
||||
expect(validate('crate123')).toBeTrue();
|
||||
expect(validate('a')).toBeTrue();
|
||||
|
||||
// Invalid names (uppercase not allowed)
|
||||
expect(validate('Serde')).to.be.false;
|
||||
expect(validate('MyCreate')).to.be.false;
|
||||
expect(validate('Serde')).toBeFalse();
|
||||
expect(validate('MyCreate')).toBeFalse();
|
||||
|
||||
// Invalid names (special characters)
|
||||
expect(validate('my.crate')).to.be.false;
|
||||
expect(validate('my@crate')).to.be.false;
|
||||
expect(validate('my crate')).to.be.false;
|
||||
expect(validate('my.crate')).toBeFalse();
|
||||
expect(validate('my@crate')).toBeFalse();
|
||||
expect(validate('my crate')).toBeFalse();
|
||||
|
||||
// Invalid names (too long)
|
||||
const longName = 'a'.repeat(65);
|
||||
expect(validate(longName)).to.be.false;
|
||||
expect(validate(longName)).toBeFalse();
|
||||
|
||||
// Invalid names (empty)
|
||||
expect(validate('')).to.be.false;
|
||||
expect(validate('')).toBeFalse();
|
||||
});
|
||||
|
||||
// Test config.json response
|
||||
@@ -122,12 +122,12 @@ tap.test('should return valid config.json', async () => {
|
||||
query: {},
|
||||
});
|
||||
|
||||
expect(response.status).to.equal(200);
|
||||
expect(response.headers['Content-Type']).to.equal('application/json');
|
||||
expect(response.status).toEqual(200);
|
||||
expect(response.headers['Content-Type']).toEqual('application/json');
|
||||
const body = await streamToJson(response.body);
|
||||
expect(body).to.be.an('object');
|
||||
expect(body.dl).to.include('/api/v1/crates/{crate}/{version}/download');
|
||||
expect(body.api).to.equal('http://localhost:5000/cargo');
|
||||
expect(body).toBeTypeOf('object');
|
||||
expect(body.dl).toInclude('/api/v1/crates/{crate}/{version}/download');
|
||||
expect(body.api).toEqual('http://localhost:5000/cargo');
|
||||
});
|
||||
|
||||
export default tap.start();
|
||||
|
||||
@@ -26,9 +26,6 @@ tap.test('setup: should create DefaultAuthProvider', async () => {
|
||||
realm: 'https://auth.example.com/token',
|
||||
service: 'test-registry',
|
||||
},
|
||||
mavenTokens: { enabled: true },
|
||||
cargoTokens: { enabled: true },
|
||||
composerTokens: { enabled: true },
|
||||
pypiTokens: { enabled: true },
|
||||
rubygemsTokens: { enabled: true },
|
||||
};
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
*/
|
||||
|
||||
import { expect, tap } from '@git.zone/tstest/tapbundle';
|
||||
import { tapNodeTools } from '@git.zone/tstest/tapbundle_serverside';
|
||||
import { TapNodeTools } from '@git.zone/tstest/tapbundle_serverside';
|
||||
import { SmartRegistry } from '../ts/index.js';
|
||||
import { createTestRegistry, createTestTokens } from './helpers/registry.js';
|
||||
import type { IRequestContext, IResponse } from '../ts/core/interfaces.core.js';
|
||||
@@ -13,6 +13,8 @@ import * as url from 'url';
|
||||
import * as fs from 'fs';
|
||||
import * as path from 'path';
|
||||
|
||||
const tapNodeTools = new TapNodeTools(tap);
|
||||
|
||||
// Test context
|
||||
let registry: SmartRegistry;
|
||||
let server: http.Server;
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
*/
|
||||
|
||||
import { expect, tap } from '@git.zone/tstest/tapbundle';
|
||||
import { tapNodeTools } from '@git.zone/tstest/tapbundle_serverside';
|
||||
import { TapNodeTools } from '@git.zone/tstest/tapbundle_serverside';
|
||||
import { SmartRegistry } from '../ts/index.js';
|
||||
import { createTestRegistry, createTestTokens, createComposerZip, generateTestRunId } from './helpers/registry.js';
|
||||
import type { IRequestContext, IResponse } from '../ts/core/interfaces.core.js';
|
||||
@@ -13,6 +13,8 @@ import * as url from 'url';
|
||||
import * as fs from 'fs';
|
||||
import * as path from 'path';
|
||||
|
||||
const tapNodeTools = new TapNodeTools(tap);
|
||||
|
||||
// Test context
|
||||
let registry: SmartRegistry;
|
||||
let server: http.Server;
|
||||
@@ -208,7 +210,7 @@ async function uploadComposerPackage(
|
||||
'Content-Type': 'application/zip',
|
||||
Authorization: `Bearer ${token}`,
|
||||
},
|
||||
body: zipData,
|
||||
body: Uint8Array.from(zipData),
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
|
||||
@@ -97,6 +97,9 @@ tap.test('NPM: should publish package to smarts3', async () => {
|
||||
username: 'testuser',
|
||||
password: 'testpass',
|
||||
});
|
||||
if (!userId) {
|
||||
throw new Error('Expected test user authentication to succeed');
|
||||
}
|
||||
const token = await authManager.createNpmToken(userId, false);
|
||||
|
||||
const packageData = {
|
||||
@@ -157,6 +160,9 @@ tap.test('OCI: should store blob in smarts3', async () => {
|
||||
username: 'testuser',
|
||||
password: 'testpass',
|
||||
});
|
||||
if (!userId) {
|
||||
throw new Error('Expected test user authentication to succeed');
|
||||
}
|
||||
const token = await authManager.createOciToken(
|
||||
userId,
|
||||
['oci:repository:test-image:push'],
|
||||
@@ -260,8 +266,11 @@ tap.test('Cargo: should store crate in smarts3', async () => {
|
||||
// Verify stored
|
||||
const retrievedIndex = await storage.getCargoIndex('test-crate');
|
||||
expect(retrievedIndex).toBeDefined();
|
||||
if (!retrievedIndex) {
|
||||
throw new Error('Expected Cargo index to be stored');
|
||||
}
|
||||
expect(retrievedIndex.length).toEqual(1);
|
||||
expect(retrievedIndex[0].name).toEqual('test-crate');
|
||||
expect(retrievedIndex[0]?.name).toEqual('test-crate');
|
||||
});
|
||||
|
||||
/**
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
*/
|
||||
|
||||
import { expect, tap } from '@git.zone/tstest/tapbundle';
|
||||
import { tapNodeTools } from '@git.zone/tstest/tapbundle_serverside';
|
||||
import { TapNodeTools } from '@git.zone/tstest/tapbundle_serverside';
|
||||
import { SmartRegistry } from '../ts/index.js';
|
||||
import { createTestRegistry, createTestTokens, createTestPom, createTestJar } from './helpers/registry.js';
|
||||
import type { IRequestContext, IResponse } from '../ts/core/interfaces.core.js';
|
||||
@@ -13,6 +13,8 @@ import * as url from 'url';
|
||||
import * as fs from 'fs';
|
||||
import * as path from 'path';
|
||||
|
||||
const tapNodeTools = new TapNodeTools(tap);
|
||||
|
||||
// Test context
|
||||
let registry: SmartRegistry;
|
||||
let server: http.Server;
|
||||
@@ -270,7 +272,7 @@ tap.test('Maven CLI: should verify mvn is installed', async () => {
|
||||
} catch (error) {
|
||||
console.log('Maven CLI not available, skipping native CLI tests');
|
||||
// Skip remaining tests if Maven is not installed
|
||||
tap.skip.test('Maven CLI: remaining tests skipped - mvn not available');
|
||||
tap.skip.test('Maven CLI: remaining tests skipped - mvn not available', async () => {});
|
||||
return;
|
||||
}
|
||||
});
|
||||
|
||||
@@ -4,7 +4,6 @@
|
||||
*/
|
||||
|
||||
import { expect, tap } from '@git.zone/tstest/tapbundle';
|
||||
import { tapNodeTools } from '@git.zone/tstest/tapbundle_serverside';
|
||||
import { SmartRegistry } from '../ts/index.js';
|
||||
import { createTestRegistry, createTestTokens, cleanupS3Bucket } from './helpers/registry.js';
|
||||
import type { IRequestContext, IResponse } from '../ts/core/interfaces.core.js';
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
*/
|
||||
|
||||
import { expect, tap } from '@git.zone/tstest/tapbundle';
|
||||
import { tapNodeTools } from '@git.zone/tstest/tapbundle_serverside';
|
||||
import { TapNodeTools } from '@git.zone/tstest/tapbundle_serverside';
|
||||
import { SmartRegistry } from '../ts/index.js';
|
||||
import type { IRequestContext, IResponse, IRegistryConfig } from '../ts/core/interfaces.core.js';
|
||||
import * as qenv from '@push.rocks/qenv';
|
||||
@@ -13,6 +13,8 @@ import * as url from 'url';
|
||||
import * as fs from 'fs';
|
||||
import * as path from 'path';
|
||||
|
||||
const tapNodeTools = new TapNodeTools(tap);
|
||||
|
||||
const testQenv = new qenv.Qenv('./', './.nogit');
|
||||
|
||||
/**
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
*/
|
||||
|
||||
import { expect, tap } from '@git.zone/tstest/tapbundle';
|
||||
import { tapNodeTools } from '@git.zone/tstest/tapbundle_serverside';
|
||||
import { TapNodeTools } from '@git.zone/tstest/tapbundle_serverside';
|
||||
import { SmartRegistry } from '../ts/index.js';
|
||||
import { createTestRegistry, createTestTokens, createPythonWheel, createPythonSdist, generateTestRunId } from './helpers/registry.js';
|
||||
import type { IRequestContext, IResponse } from '../ts/core/interfaces.core.js';
|
||||
@@ -13,6 +13,8 @@ import * as url from 'url';
|
||||
import * as fs from 'fs';
|
||||
import * as path from 'path';
|
||||
|
||||
const tapNodeTools = new TapNodeTools(tap);
|
||||
|
||||
// Test context
|
||||
let registry: SmartRegistry;
|
||||
let server: http.Server;
|
||||
@@ -291,7 +293,7 @@ tap.test('PyPI CLI: should verify twine is installed', async () => {
|
||||
|
||||
if (!hasPip && !hasTwine) {
|
||||
console.log('Neither pip nor twine available, skipping native CLI tests');
|
||||
tap.skip.test('PyPI CLI: remaining tests skipped - no CLI tools available');
|
||||
tap.skip.test('PyPI CLI: remaining tests skipped - no CLI tools available', async () => {});
|
||||
return;
|
||||
}
|
||||
});
|
||||
|
||||
+1
-1
@@ -62,7 +62,7 @@ tap.test('PyPI: should upload wheel file (POST /pypi/)', async () => {
|
||||
formData.append('pyversion', 'py3');
|
||||
formData.append('metadata_version', '2.1');
|
||||
formData.append('sha256_digest', hashes.sha256);
|
||||
formData.append('content', new Blob([testWheelData]), filename);
|
||||
formData.append('content', new Blob([Uint8Array.from(testWheelData)]), filename);
|
||||
|
||||
const response = await registry.handleRequest({
|
||||
method: 'POST',
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
*/
|
||||
|
||||
import { expect, tap } from '@git.zone/tstest/tapbundle';
|
||||
import { tapNodeTools } from '@git.zone/tstest/tapbundle_serverside';
|
||||
import { TapNodeTools } from '@git.zone/tstest/tapbundle_serverside';
|
||||
import { SmartRegistry } from '../ts/index.js';
|
||||
import { createTestRegistry, createTestTokens, createRubyGem } from './helpers/registry.js';
|
||||
import type { IRequestContext, IResponse } from '../ts/core/interfaces.core.js';
|
||||
@@ -13,6 +13,8 @@ import * as url from 'url';
|
||||
import * as fs from 'fs';
|
||||
import * as path from 'path';
|
||||
|
||||
const tapNodeTools = new TapNodeTools(tap);
|
||||
|
||||
// Test context
|
||||
let registry: SmartRegistry;
|
||||
let server: http.Server;
|
||||
|
||||
@@ -89,6 +89,17 @@ tap.test('setup: should create S3 storage backend', async () => {
|
||||
}
|
||||
return paths;
|
||||
},
|
||||
objectExists: async (key: string): Promise<boolean> => {
|
||||
try {
|
||||
const data = await s3Bucket.fastGet({ path: key });
|
||||
return !!data;
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
},
|
||||
getMetadata: async (_key: string): Promise<Record<string, string> | null> => {
|
||||
return null;
|
||||
},
|
||||
};
|
||||
|
||||
expect(storageBackend).toBeTruthy();
|
||||
@@ -584,9 +595,8 @@ tap.test('cleanup: should stop cache and clean up bucket', async () => {
|
||||
// Clean up test bucket
|
||||
if (s3Bucket) {
|
||||
try {
|
||||
const files = await s3Bucket.fastList({});
|
||||
for (const file of files) {
|
||||
await s3Bucket.fastRemove({ path: file.name });
|
||||
for await (const filePath of s3Bucket.listAllObjects('')) {
|
||||
await s3Bucket.fastRemove({ path: filePath });
|
||||
}
|
||||
await smartBucket.removeBucket(bucketName);
|
||||
} catch {
|
||||
|
||||
@@ -9,9 +9,19 @@ import type {
|
||||
IUpstreamProvider,
|
||||
IUpstreamResolutionContext,
|
||||
IProtocolUpstreamConfig,
|
||||
IUpstreamRegistryConfig,
|
||||
} from '../ts/upstream/interfaces.upstream.js';
|
||||
import type { TRegistryProtocol } from '../ts/core/interfaces.core.js';
|
||||
|
||||
const createUpstream = (id: string, url: string): IUpstreamRegistryConfig => ({
|
||||
id,
|
||||
name: id,
|
||||
url,
|
||||
priority: 1,
|
||||
enabled: true,
|
||||
auth: { type: 'none' },
|
||||
});
|
||||
|
||||
// =============================================================================
|
||||
// StaticUpstreamProvider Tests
|
||||
// =============================================================================
|
||||
@@ -19,7 +29,7 @@ import type { TRegistryProtocol } from '../ts/core/interfaces.core.js';
|
||||
tap.test('StaticUpstreamProvider: should return config for configured protocol', async () => {
|
||||
const npmConfig: IProtocolUpstreamConfig = {
|
||||
enabled: true,
|
||||
upstreams: [{ id: 'npmjs', url: 'https://registry.npmjs.org', priority: 1, enabled: true }],
|
||||
upstreams: [createUpstream('npmjs', 'https://registry.npmjs.org')],
|
||||
};
|
||||
|
||||
const provider = new StaticUpstreamProvider({
|
||||
@@ -43,7 +53,7 @@ tap.test('StaticUpstreamProvider: should return null for unconfigured protocol',
|
||||
const provider = new StaticUpstreamProvider({
|
||||
npm: {
|
||||
enabled: true,
|
||||
upstreams: [{ id: 'npmjs', url: 'https://registry.npmjs.org', priority: 1, enabled: true }],
|
||||
upstreams: [createUpstream('npmjs', 'https://registry.npmjs.org')],
|
||||
},
|
||||
});
|
||||
|
||||
@@ -62,15 +72,15 @@ tap.test('StaticUpstreamProvider: should support multiple protocols', async () =
|
||||
const provider = new StaticUpstreamProvider({
|
||||
npm: {
|
||||
enabled: true,
|
||||
upstreams: [{ id: 'npmjs', url: 'https://registry.npmjs.org', priority: 1, enabled: true }],
|
||||
upstreams: [createUpstream('npmjs', 'https://registry.npmjs.org')],
|
||||
},
|
||||
oci: {
|
||||
enabled: true,
|
||||
upstreams: [{ id: 'dockerhub', url: 'https://registry-1.docker.io', priority: 1, enabled: true }],
|
||||
upstreams: [createUpstream('dockerhub', 'https://registry-1.docker.io')],
|
||||
},
|
||||
maven: {
|
||||
enabled: true,
|
||||
upstreams: [{ id: 'central', url: 'https://repo1.maven.org/maven2', priority: 1, enabled: true }],
|
||||
upstreams: [createUpstream('central', 'https://repo1.maven.org/maven2')],
|
||||
},
|
||||
});
|
||||
|
||||
@@ -113,7 +123,7 @@ tap.test('Provider Integration: should create registry with upstream provider',
|
||||
trackingProvider = createTrackingUpstreamProvider({
|
||||
npm: {
|
||||
enabled: true,
|
||||
upstreams: [{ id: 'test-npm', url: 'https://registry.npmjs.org', priority: 1, enabled: true }],
|
||||
upstreams: [createUpstream('test-npm', 'https://registry.npmjs.org')],
|
||||
},
|
||||
});
|
||||
|
||||
@@ -200,13 +210,13 @@ tap.test('Custom Provider: should support dynamic resolution based on context',
|
||||
// Internal packages go to private registry
|
||||
return {
|
||||
enabled: true,
|
||||
upstreams: [{ id: 'private', url: 'https://private.registry.com', priority: 1, enabled: true }],
|
||||
upstreams: [createUpstream('private', 'https://private.registry.com')],
|
||||
};
|
||||
}
|
||||
// Everything else goes to public registry
|
||||
return {
|
||||
enabled: true,
|
||||
upstreams: [{ id: 'public', url: 'https://registry.npmjs.org', priority: 1, enabled: true }],
|
||||
upstreams: [createUpstream('public', 'https://registry.npmjs.org')],
|
||||
};
|
||||
},
|
||||
};
|
||||
@@ -237,12 +247,12 @@ tap.test('Custom Provider: should support actor-based resolution', async () => {
|
||||
if (context.actor?.orgId === 'enterprise-org') {
|
||||
return {
|
||||
enabled: true,
|
||||
upstreams: [{ id: 'enterprise', url: 'https://enterprise.registry.com', priority: 1, enabled: true }],
|
||||
upstreams: [createUpstream('enterprise', 'https://enterprise.registry.com')],
|
||||
};
|
||||
}
|
||||
return {
|
||||
enabled: true,
|
||||
upstreams: [{ id: 'default', url: 'https://registry.npmjs.org', priority: 1, enabled: true }],
|
||||
upstreams: [createUpstream('default', 'https://registry.npmjs.org')],
|
||||
};
|
||||
},
|
||||
};
|
||||
@@ -277,7 +287,7 @@ tap.test('Custom Provider: should support disabling upstream for specific resour
|
||||
}
|
||||
return {
|
||||
enabled: true,
|
||||
upstreams: [{ id: 'public', url: 'https://registry.npmjs.org', priority: 1, enabled: true }],
|
||||
upstreams: [createUpstream('public', 'https://registry.npmjs.org')],
|
||||
};
|
||||
},
|
||||
};
|
||||
|
||||
@@ -3,6 +3,6 @@
|
||||
*/
|
||||
export const commitinfo = {
|
||||
name: '@push.rocks/smartregistry',
|
||||
version: '2.9.0',
|
||||
version: '2.9.2',
|
||||
description: 'A composable TypeScript library implementing OCI, NPM, Maven, Cargo, Composer, PyPI, and RubyGems registries for building unified container and package registries'
|
||||
}
|
||||
|
||||
@@ -6,7 +6,12 @@ import type { IRequestContext, IResponse, IAuthToken, IRequestActor } from './in
|
||||
*/
|
||||
export abstract class BaseRegistry {
|
||||
protected getHeader(contextOrHeaders: IRequestContext | Record<string, string>, name: string): string | undefined {
|
||||
const headers = 'headers' in contextOrHeaders ? contextOrHeaders.headers : contextOrHeaders;
|
||||
const headers: Record<string, string> =
|
||||
'headers' in contextOrHeaders &&
|
||||
typeof contextOrHeaders.headers === 'object' &&
|
||||
contextOrHeaders.headers !== null
|
||||
? contextOrHeaders.headers
|
||||
: contextOrHeaders as Record<string, string>;
|
||||
if (headers[name] !== undefined) {
|
||||
return headers[name];
|
||||
}
|
||||
|
||||
+5
-1
@@ -4,7 +4,11 @@
|
||||
"module": "NodeNext",
|
||||
"moduleResolution": "NodeNext",
|
||||
"esModuleInterop": true,
|
||||
"verbatimModuleSyntax": true
|
||||
"verbatimModuleSyntax": true,
|
||||
"noImplicitAny": true,
|
||||
"types": [
|
||||
"node"
|
||||
]
|
||||
},
|
||||
"exclude": ["dist_*/**/*.d.ts"]
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user