Compare commits

...

2 Commits

Author SHA1 Message Date
jkunz 8b3da7dece v2.9.2
Default (tags) / security (push) Failing after 1s
Default (tags) / test (push) Failing after 1s
Default (tags) / release (push) Has been skipped
Default (tags) / metadata (push) Has been skipped
2026-04-30 09:25:26 +00:00
jkunz 45e24ecff3 fix(core,testing): improve type safety and update tests for latest tstest and storage APIs 2026-04-30 09:25:26 +00:00
19 changed files with 1395 additions and 1415 deletions
+9
View File
@@ -1,5 +1,14 @@
# Changelog # 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) ## 2026-04-16 - 2.9.1 - fix(license)
add missing MIT license file to repository add missing MIT license file to repository
+14 -14
View File
@@ -1,6 +1,6 @@
{ {
"name": "@push.rocks/smartregistry", "name": "@push.rocks/smartregistry",
"version": "2.9.1", "version": "2.9.2",
"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",
@@ -10,17 +10,19 @@
"license": "MIT", "license": "MIT",
"scripts": { "scripts": {
"test": "(tstest test/ --verbose --logfile --timeout 240)", "test": "(tstest test/ --verbose --logfile --timeout 240)",
"build": "(tsbuild --allowimplicitany)", "build": "(tsbuild)",
"buildDocs": "(tsdoc)" "buildDocs": "(tsdoc)"
}, },
"devDependencies": { "devDependencies": {
"@git.zone/tsbuild": "^4.4.0", "@git.zone/tsbuild": "^4.4.0",
"@git.zone/tsbundle": "^2.10.0", "@git.zone/tsbundle": "^2.10.0",
"@git.zone/tsrun": "^2.0.2", "@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/smartarchive": "^5.2.1",
"@push.rocks/smartstorage": "^6.3.2", "@push.rocks/smartstorage": "^6.4.1",
"@types/node": "^25.5.0" "@types/adm-zip": "^0.5.8",
"@types/lodash.clonedeep": "^4.5.9",
"@types/node": "^25.6.0"
}, },
"repository": { "repository": {
"type": "git", "type": "git",
@@ -40,20 +42,18 @@
"assets/**/*", "assets/**/*",
"cli.js", "cli.js",
".smartconfig.json", ".smartconfig.json",
"license",
"readme.md" "readme.md"
], ],
"pnpm": {
"overrides": {}
},
"dependencies": { "dependencies": {
"@push.rocks/qenv": "^6.1.3", "@push.rocks/qenv": "^6.1.3",
"@push.rocks/smartbucket": "^4.5.1", "@push.rocks/smartbucket": "^4.6.1",
"@push.rocks/smartlog": "^3.2.1", "@push.rocks/smartlog": "^3.2.2",
"@push.rocks/smartpath": "^6.0.0", "@push.rocks/smartpath": "^6.0.0",
"@push.rocks/smartrequest": "^5.0.1", "@push.rocks/smartrequest": "^5.0.1",
"@tsclass/tsclass": "^9.5.0", "@tsclass/tsclass": "^9.5.1",
"adm-zip": "^0.5.16", "adm-zip": "^0.5.17",
"minimatch": "^10.2.4" "minimatch": "^10.2.5"
}, },
"packageManager": "pnpm@10.18.1+sha512.77a884a165cbba2d8d1c19e3b4880eee6d2fcabd0d879121e282196b80042351d5eb3ca0935fa599da1dc51265cc68816ad2bddd2a2de5ea9fdf92adbec7cd34" "packageManager": "pnpm@10.28.2"
} }
+1263 -1338
View File
File diff suppressed because it is too large Load Diff
+31 -31
View File
@@ -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 { RegistryStorage } from '../ts/core/classes.registrystorage.js';
import { CargoRegistry } from '../ts/cargo/classes.cargoregistry.js'; import { CargoRegistry } from '../ts/cargo/classes.cargoregistry.js';
import { AuthManager } from '../ts/core/classes.authmanager.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); const getPath = (storage as any).getCargoIndexPath.bind(storage);
// 1-character names // 1-character names
expect(getPath('a')).to.equal('cargo/index/1/a'); expect(getPath('a')).toEqual('cargo/index/1/a');
expect(getPath('z')).to.equal('cargo/index/1/z'); expect(getPath('z')).toEqual('cargo/index/1/z');
// 2-character names // 2-character names
expect(getPath('io')).to.equal('cargo/index/2/io'); expect(getPath('io')).toEqual('cargo/index/2/io');
expect(getPath('ab')).to.equal('cargo/index/2/ab'); expect(getPath('ab')).toEqual('cargo/index/2/ab');
// 3-character names // 3-character names
expect(getPath('axo')).to.equal('cargo/index/3/a/axo'); expect(getPath('axo')).toEqual('cargo/index/3/a/axo');
expect(getPath('foo')).to.equal('cargo/index/3/f/foo'); expect(getPath('foo')).toEqual('cargo/index/3/f/foo');
// 4+ character names // 4+ character names
expect(getPath('serde')).to.equal('cargo/index/se/rd/serde'); expect(getPath('serde')).toEqual('cargo/index/se/rd/serde');
expect(getPath('tokio')).to.equal('cargo/index/to/ki/tokio'); expect(getPath('tokio')).toEqual('cargo/index/to/ki/tokio');
expect(getPath('my-crate')).to.equal('cargo/index/my/--/my-crate'); expect(getPath('my-crate')).toEqual('cargo/index/my/--/my-crate');
}); });
// Test crate file path calculation // Test crate file path calculation
@@ -46,9 +46,9 @@ tap.test('should calculate correct crate file paths', async () => {
// Access private method for testing // Access private method for testing
const getPath = (storage as any).getCargoCratePath.bind(storage); 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('serde', '1.0.0')).toEqual('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('tokio', '1.28.0')).toEqual('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('my-crate', '0.1.0')).toEqual('cargo/crates/my-crate/my-crate-0.1.0.crate');
}); });
// Test crate name validation // Test crate name validation
@@ -73,28 +73,28 @@ tap.test('should validate crate names correctly', async () => {
const validate = (registry as any).validateCrateName.bind(registry); const validate = (registry as any).validateCrateName.bind(registry);
// Valid names // Valid names
expect(validate('serde')).to.be.true; expect(validate('serde')).toBeTrue();
expect(validate('tokio')).to.be.true; expect(validate('tokio')).toBeTrue();
expect(validate('my-crate')).to.be.true; expect(validate('my-crate')).toBeTrue();
expect(validate('my_crate')).to.be.true; expect(validate('my_crate')).toBeTrue();
expect(validate('crate123')).to.be.true; expect(validate('crate123')).toBeTrue();
expect(validate('a')).to.be.true; expect(validate('a')).toBeTrue();
// Invalid names (uppercase not allowed) // Invalid names (uppercase not allowed)
expect(validate('Serde')).to.be.false; expect(validate('Serde')).toBeFalse();
expect(validate('MyCreate')).to.be.false; expect(validate('MyCreate')).toBeFalse();
// Invalid names (special characters) // Invalid names (special characters)
expect(validate('my.crate')).to.be.false; expect(validate('my.crate')).toBeFalse();
expect(validate('my@crate')).to.be.false; expect(validate('my@crate')).toBeFalse();
expect(validate('my crate')).to.be.false; expect(validate('my crate')).toBeFalse();
// Invalid names (too long) // Invalid names (too long)
const longName = 'a'.repeat(65); const longName = 'a'.repeat(65);
expect(validate(longName)).to.be.false; expect(validate(longName)).toBeFalse();
// Invalid names (empty) // Invalid names (empty)
expect(validate('')).to.be.false; expect(validate('')).toBeFalse();
}); });
// Test config.json response // Test config.json response
@@ -122,12 +122,12 @@ tap.test('should return valid config.json', async () => {
query: {}, query: {},
}); });
expect(response.status).to.equal(200); expect(response.status).toEqual(200);
expect(response.headers['Content-Type']).to.equal('application/json'); expect(response.headers['Content-Type']).toEqual('application/json');
const body = await streamToJson(response.body); const body = await streamToJson(response.body);
expect(body).to.be.an('object'); expect(body).toBeTypeOf('object');
expect(body.dl).to.include('/api/v1/crates/{crate}/{version}/download'); expect(body.dl).toInclude('/api/v1/crates/{crate}/{version}/download');
expect(body.api).to.equal('http://localhost:5000/cargo'); expect(body.api).toEqual('http://localhost:5000/cargo');
}); });
export default tap.start(); export default tap.start();
-3
View File
@@ -26,9 +26,6 @@ tap.test('setup: should create DefaultAuthProvider', async () => {
realm: 'https://auth.example.com/token', realm: 'https://auth.example.com/token',
service: 'test-registry', service: 'test-registry',
}, },
mavenTokens: { enabled: true },
cargoTokens: { enabled: true },
composerTokens: { enabled: true },
pypiTokens: { enabled: true }, pypiTokens: { enabled: true },
rubygemsTokens: { enabled: true }, rubygemsTokens: { enabled: true },
}; };
+3 -1
View File
@@ -4,7 +4,7 @@
*/ */
import { expect, tap } from '@git.zone/tstest/tapbundle'; 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 { SmartRegistry } from '../ts/index.js';
import { createTestRegistry, createTestTokens } from './helpers/registry.js'; import { createTestRegistry, createTestTokens } from './helpers/registry.js';
import type { IRequestContext, IResponse } from '../ts/core/interfaces.core.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 fs from 'fs';
import * as path from 'path'; import * as path from 'path';
const tapNodeTools = new TapNodeTools(tap);
// Test context // Test context
let registry: SmartRegistry; let registry: SmartRegistry;
let server: http.Server; let server: http.Server;
+4 -2
View File
@@ -4,7 +4,7 @@
*/ */
import { expect, tap } from '@git.zone/tstest/tapbundle'; 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 { SmartRegistry } from '../ts/index.js';
import { createTestRegistry, createTestTokens, createComposerZip, generateTestRunId } from './helpers/registry.js'; import { createTestRegistry, createTestTokens, createComposerZip, generateTestRunId } from './helpers/registry.js';
import type { IRequestContext, IResponse } from '../ts/core/interfaces.core.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 fs from 'fs';
import * as path from 'path'; import * as path from 'path';
const tapNodeTools = new TapNodeTools(tap);
// Test context // Test context
let registry: SmartRegistry; let registry: SmartRegistry;
let server: http.Server; let server: http.Server;
@@ -208,7 +210,7 @@ async function uploadComposerPackage(
'Content-Type': 'application/zip', 'Content-Type': 'application/zip',
Authorization: `Bearer ${token}`, Authorization: `Bearer ${token}`,
}, },
body: zipData, body: Uint8Array.from(zipData),
}); });
if (!response.ok) { if (!response.ok) {
+10 -1
View File
@@ -97,6 +97,9 @@ tap.test('NPM: should publish package to smarts3', async () => {
username: 'testuser', username: 'testuser',
password: 'testpass', password: 'testpass',
}); });
if (!userId) {
throw new Error('Expected test user authentication to succeed');
}
const token = await authManager.createNpmToken(userId, false); const token = await authManager.createNpmToken(userId, false);
const packageData = { const packageData = {
@@ -157,6 +160,9 @@ tap.test('OCI: should store blob in smarts3', async () => {
username: 'testuser', username: 'testuser',
password: 'testpass', password: 'testpass',
}); });
if (!userId) {
throw new Error('Expected test user authentication to succeed');
}
const token = await authManager.createOciToken( const token = await authManager.createOciToken(
userId, userId,
['oci:repository:test-image:push'], ['oci:repository:test-image:push'],
@@ -260,8 +266,11 @@ tap.test('Cargo: should store crate in smarts3', async () => {
// Verify stored // Verify stored
const retrievedIndex = await storage.getCargoIndex('test-crate'); const retrievedIndex = await storage.getCargoIndex('test-crate');
expect(retrievedIndex).toBeDefined(); expect(retrievedIndex).toBeDefined();
if (!retrievedIndex) {
throw new Error('Expected Cargo index to be stored');
}
expect(retrievedIndex.length).toEqual(1); expect(retrievedIndex.length).toEqual(1);
expect(retrievedIndex[0].name).toEqual('test-crate'); expect(retrievedIndex[0]?.name).toEqual('test-crate');
}); });
/** /**
+4 -2
View File
@@ -4,7 +4,7 @@
*/ */
import { expect, tap } from '@git.zone/tstest/tapbundle'; 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 { SmartRegistry } from '../ts/index.js';
import { createTestRegistry, createTestTokens, createTestPom, createTestJar } from './helpers/registry.js'; import { createTestRegistry, createTestTokens, createTestPom, createTestJar } from './helpers/registry.js';
import type { IRequestContext, IResponse } from '../ts/core/interfaces.core.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 fs from 'fs';
import * as path from 'path'; import * as path from 'path';
const tapNodeTools = new TapNodeTools(tap);
// Test context // Test context
let registry: SmartRegistry; let registry: SmartRegistry;
let server: http.Server; let server: http.Server;
@@ -270,7 +272,7 @@ tap.test('Maven CLI: should verify mvn is installed', async () => {
} catch (error) { } catch (error) {
console.log('Maven CLI not available, skipping native CLI tests'); console.log('Maven CLI not available, skipping native CLI tests');
// Skip remaining tests if Maven is not installed // 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; return;
} }
}); });
-1
View File
@@ -4,7 +4,6 @@
*/ */
import { expect, tap } from '@git.zone/tstest/tapbundle'; import { expect, tap } from '@git.zone/tstest/tapbundle';
import { tapNodeTools } from '@git.zone/tstest/tapbundle_serverside';
import { SmartRegistry } from '../ts/index.js'; import { SmartRegistry } from '../ts/index.js';
import { createTestRegistry, createTestTokens, cleanupS3Bucket } from './helpers/registry.js'; import { createTestRegistry, createTestTokens, cleanupS3Bucket } from './helpers/registry.js';
import type { IRequestContext, IResponse } from '../ts/core/interfaces.core.js'; import type { IRequestContext, IResponse } from '../ts/core/interfaces.core.js';
+3 -1
View File
@@ -4,7 +4,7 @@
*/ */
import { expect, tap } from '@git.zone/tstest/tapbundle'; 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 { SmartRegistry } from '../ts/index.js';
import type { IRequestContext, IResponse, IRegistryConfig } from '../ts/core/interfaces.core.js'; import type { IRequestContext, IResponse, IRegistryConfig } from '../ts/core/interfaces.core.js';
import * as qenv from '@push.rocks/qenv'; import * as qenv from '@push.rocks/qenv';
@@ -13,6 +13,8 @@ import * as url from 'url';
import * as fs from 'fs'; import * as fs from 'fs';
import * as path from 'path'; import * as path from 'path';
const tapNodeTools = new TapNodeTools(tap);
const testQenv = new qenv.Qenv('./', './.nogit'); const testQenv = new qenv.Qenv('./', './.nogit');
/** /**
+4 -2
View File
@@ -4,7 +4,7 @@
*/ */
import { expect, tap } from '@git.zone/tstest/tapbundle'; 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 { SmartRegistry } from '../ts/index.js';
import { createTestRegistry, createTestTokens, createPythonWheel, createPythonSdist, generateTestRunId } from './helpers/registry.js'; import { createTestRegistry, createTestTokens, createPythonWheel, createPythonSdist, generateTestRunId } from './helpers/registry.js';
import type { IRequestContext, IResponse } from '../ts/core/interfaces.core.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 fs from 'fs';
import * as path from 'path'; import * as path from 'path';
const tapNodeTools = new TapNodeTools(tap);
// Test context // Test context
let registry: SmartRegistry; let registry: SmartRegistry;
let server: http.Server; let server: http.Server;
@@ -291,7 +293,7 @@ tap.test('PyPI CLI: should verify twine is installed', async () => {
if (!hasPip && !hasTwine) { if (!hasPip && !hasTwine) {
console.log('Neither pip nor twine available, skipping native CLI tests'); 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; return;
} }
}); });
+1 -1
View File
@@ -62,7 +62,7 @@ tap.test('PyPI: should upload wheel file (POST /pypi/)', async () => {
formData.append('pyversion', 'py3'); formData.append('pyversion', 'py3');
formData.append('metadata_version', '2.1'); formData.append('metadata_version', '2.1');
formData.append('sha256_digest', hashes.sha256); 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({ const response = await registry.handleRequest({
method: 'POST', method: 'POST',
+3 -1
View File
@@ -4,7 +4,7 @@
*/ */
import { expect, tap } from '@git.zone/tstest/tapbundle'; 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 { SmartRegistry } from '../ts/index.js';
import { createTestRegistry, createTestTokens, createRubyGem } from './helpers/registry.js'; import { createTestRegistry, createTestTokens, createRubyGem } from './helpers/registry.js';
import type { IRequestContext, IResponse } from '../ts/core/interfaces.core.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 fs from 'fs';
import * as path from 'path'; import * as path from 'path';
const tapNodeTools = new TapNodeTools(tap);
// Test context // Test context
let registry: SmartRegistry; let registry: SmartRegistry;
let server: http.Server; let server: http.Server;
+13 -3
View File
@@ -89,6 +89,17 @@ tap.test('setup: should create S3 storage backend', async () => {
} }
return paths; 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(); expect(storageBackend).toBeTruthy();
@@ -584,9 +595,8 @@ tap.test('cleanup: should stop cache and clean up bucket', async () => {
// Clean up test bucket // Clean up test bucket
if (s3Bucket) { if (s3Bucket) {
try { try {
const files = await s3Bucket.fastList({}); for await (const filePath of s3Bucket.listAllObjects('')) {
for (const file of files) { await s3Bucket.fastRemove({ path: filePath });
await s3Bucket.fastRemove({ path: file.name });
} }
await smartBucket.removeBucket(bucketName); await smartBucket.removeBucket(bucketName);
} catch { } catch {
+21 -11
View File
@@ -9,9 +9,19 @@ import type {
IUpstreamProvider, IUpstreamProvider,
IUpstreamResolutionContext, IUpstreamResolutionContext,
IProtocolUpstreamConfig, IProtocolUpstreamConfig,
IUpstreamRegistryConfig,
} from '../ts/upstream/interfaces.upstream.js'; } from '../ts/upstream/interfaces.upstream.js';
import type { TRegistryProtocol } from '../ts/core/interfaces.core.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 // 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 () => { tap.test('StaticUpstreamProvider: should return config for configured protocol', async () => {
const npmConfig: IProtocolUpstreamConfig = { const npmConfig: IProtocolUpstreamConfig = {
enabled: true, 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({ const provider = new StaticUpstreamProvider({
@@ -43,7 +53,7 @@ tap.test('StaticUpstreamProvider: should return null for unconfigured protocol',
const provider = new StaticUpstreamProvider({ const provider = new StaticUpstreamProvider({
npm: { npm: {
enabled: true, 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({ const provider = new StaticUpstreamProvider({
npm: { npm: {
enabled: true, enabled: true,
upstreams: [{ id: 'npmjs', url: 'https://registry.npmjs.org', priority: 1, enabled: true }], upstreams: [createUpstream('npmjs', 'https://registry.npmjs.org')],
}, },
oci: { oci: {
enabled: true, enabled: true,
upstreams: [{ id: 'dockerhub', url: 'https://registry-1.docker.io', priority: 1, enabled: true }], upstreams: [createUpstream('dockerhub', 'https://registry-1.docker.io')],
}, },
maven: { maven: {
enabled: true, 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({ trackingProvider = createTrackingUpstreamProvider({
npm: { npm: {
enabled: true, 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 // Internal packages go to private registry
return { return {
enabled: true, 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 // Everything else goes to public registry
return { return {
enabled: true, 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') { if (context.actor?.orgId === 'enterprise-org') {
return { return {
enabled: true, enabled: true,
upstreams: [{ id: 'enterprise', url: 'https://enterprise.registry.com', priority: 1, enabled: true }], upstreams: [createUpstream('enterprise', 'https://enterprise.registry.com')],
}; };
} }
return { return {
enabled: true, 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 { return {
enabled: true, enabled: true,
upstreams: [{ id: 'public', url: 'https://registry.npmjs.org', priority: 1, enabled: true }], upstreams: [createUpstream('public', 'https://registry.npmjs.org')],
}; };
}, },
}; };
+1 -1
View File
@@ -3,6 +3,6 @@
*/ */
export const commitinfo = { export const commitinfo = {
name: '@push.rocks/smartregistry', name: '@push.rocks/smartregistry',
version: '2.9.1', 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' description: 'A composable TypeScript library implementing OCI, NPM, Maven, Cargo, Composer, PyPI, and RubyGems registries for building unified container and package registries'
} }
+6 -1
View File
@@ -6,7 +6,12 @@ import type { IRequestContext, IResponse, IAuthToken, IRequestActor } from './in
*/ */
export abstract class BaseRegistry { export abstract class BaseRegistry {
protected getHeader(contextOrHeaders: IRequestContext | Record<string, string>, name: string): string | undefined { 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) { if (headers[name] !== undefined) {
return headers[name]; return headers[name];
} }
+5 -1
View File
@@ -4,7 +4,11 @@
"module": "NodeNext", "module": "NodeNext",
"moduleResolution": "NodeNext", "moduleResolution": "NodeNext",
"esModuleInterop": true, "esModuleInterop": true,
"verbatimModuleSyntax": true "verbatimModuleSyntax": true,
"noImplicitAny": true,
"types": [
"node"
]
}, },
"exclude": ["dist_*/**/*.d.ts"] "exclude": ["dist_*/**/*.d.ts"]
} }