@push.rocks/smartregistry 2.6.0 → 2.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.smartconfig.json +24 -0
- package/dist_ts/00_commitinfo_data.js +1 -1
- package/dist_ts/cargo/classes.cargoregistry.d.ts +8 -3
- package/dist_ts/cargo/classes.cargoregistry.js +71 -33
- package/dist_ts/classes.smartregistry.js +48 -36
- package/dist_ts/composer/classes.composerregistry.d.ts +14 -3
- package/dist_ts/composer/classes.composerregistry.js +64 -28
- package/dist_ts/core/classes.registrystorage.d.ts +45 -0
- package/dist_ts/core/classes.registrystorage.js +116 -1
- package/dist_ts/core/helpers.stream.d.ts +20 -0
- package/dist_ts/core/helpers.stream.js +59 -0
- package/dist_ts/core/index.d.ts +1 -0
- package/dist_ts/core/index.js +3 -1
- package/dist_ts/core/interfaces.core.d.ts +28 -5
- package/dist_ts/maven/classes.mavenregistry.d.ts +14 -3
- package/dist_ts/maven/classes.mavenregistry.js +78 -27
- package/dist_ts/npm/classes.npmregistry.d.ts +14 -3
- package/dist_ts/npm/classes.npmregistry.js +104 -48
- package/dist_ts/oci/classes.ociregistry.d.ts +19 -3
- package/dist_ts/oci/classes.ociregistry.js +186 -73
- package/dist_ts/oci/classes.ociupstream.d.ts +5 -2
- package/dist_ts/oci/classes.ociupstream.js +17 -10
- package/dist_ts/oci/interfaces.oci.d.ts +4 -0
- package/dist_ts/pypi/classes.pypiregistry.d.ts +8 -3
- package/dist_ts/pypi/classes.pypiregistry.js +88 -50
- package/dist_ts/rubygems/classes.rubygemsregistry.d.ts +8 -3
- package/dist_ts/rubygems/classes.rubygemsregistry.js +61 -23
- package/dist_ts/rubygems/helpers.rubygems.js +3 -3
- package/dist_ts/upstream/classes.upstreamcache.js +2 -2
- package/dist_ts/upstream/interfaces.upstream.d.ts +72 -1
- package/dist_ts/upstream/interfaces.upstream.js +24 -1
- package/package.json +24 -20
- package/readme.md +354 -812
- package/ts/00_commitinfo_data.ts +1 -1
- package/ts/cargo/classes.cargoregistry.ts +84 -37
- package/ts/classes.smartregistry.ts +49 -35
- package/ts/composer/classes.composerregistry.ts +74 -30
- package/ts/core/classes.registrystorage.ts +133 -2
- package/ts/core/helpers.stream.ts +63 -0
- package/ts/core/index.ts +3 -0
- package/ts/core/interfaces.core.ts +29 -5
- package/ts/maven/classes.mavenregistry.ts +89 -28
- package/ts/npm/classes.npmregistry.ts +118 -49
- package/ts/oci/classes.ociregistry.ts +205 -77
- package/ts/oci/classes.ociupstream.ts +18 -8
- package/ts/oci/interfaces.oci.ts +4 -0
- package/ts/pypi/classes.pypiregistry.ts +100 -54
- package/ts/rubygems/classes.rubygemsregistry.ts +69 -24
- package/ts/rubygems/helpers.rubygems.ts +2 -2
- package/ts/upstream/classes.upstreamcache.ts +1 -1
- package/ts/upstream/interfaces.upstream.ts +82 -1
- package/npmextra.json +0 -18
|
@@ -34,8 +34,8 @@ import type {
|
|
|
34
34
|
* ```
|
|
35
35
|
*/
|
|
36
36
|
export class RegistryStorage implements IStorageBackend {
|
|
37
|
-
private smartBucket
|
|
38
|
-
private bucket
|
|
37
|
+
private smartBucket!: plugins.smartbucket.SmartBucket;
|
|
38
|
+
private bucket!: plugins.smartbucket.Bucket;
|
|
39
39
|
private bucketName: string;
|
|
40
40
|
private hooks?: IStorageHooks;
|
|
41
41
|
|
|
@@ -1266,4 +1266,135 @@ export class RegistryStorage implements IStorageBackend {
|
|
|
1266
1266
|
private getRubyGemsMetadataPath(gemName: string): string {
|
|
1267
1267
|
return `rubygems/metadata/${gemName}/metadata.json`;
|
|
1268
1268
|
}
|
|
1269
|
+
|
|
1270
|
+
// ========================================================================
|
|
1271
|
+
// STREAMING METHODS (Web Streams API)
|
|
1272
|
+
// ========================================================================
|
|
1273
|
+
|
|
1274
|
+
/**
|
|
1275
|
+
* Get an object as a ReadableStream. Returns null if not found.
|
|
1276
|
+
*/
|
|
1277
|
+
public async getObjectStream(key: string): Promise<{ stream: ReadableStream<Uint8Array>; size: number } | null> {
|
|
1278
|
+
try {
|
|
1279
|
+
const stat = await this.bucket.fastStat({ path: key });
|
|
1280
|
+
const size = stat.ContentLength ?? 0;
|
|
1281
|
+
const stream = await this.bucket.fastGetStream({ path: key }, 'webstream');
|
|
1282
|
+
|
|
1283
|
+
// Call afterGet hook (non-blocking)
|
|
1284
|
+
if (this.hooks?.afterGet) {
|
|
1285
|
+
const context = this.currentContext;
|
|
1286
|
+
if (context) {
|
|
1287
|
+
this.hooks.afterGet({
|
|
1288
|
+
operation: 'get',
|
|
1289
|
+
key,
|
|
1290
|
+
protocol: context.protocol,
|
|
1291
|
+
actor: context.actor,
|
|
1292
|
+
metadata: context.metadata,
|
|
1293
|
+
timestamp: new Date(),
|
|
1294
|
+
}).catch(() => {});
|
|
1295
|
+
}
|
|
1296
|
+
}
|
|
1297
|
+
|
|
1298
|
+
return { stream: stream as ReadableStream<Uint8Array>, size };
|
|
1299
|
+
} catch {
|
|
1300
|
+
return null;
|
|
1301
|
+
}
|
|
1302
|
+
}
|
|
1303
|
+
|
|
1304
|
+
/**
|
|
1305
|
+
* Store an object from a ReadableStream.
|
|
1306
|
+
*/
|
|
1307
|
+
public async putObjectStream(key: string, stream: ReadableStream<Uint8Array>): Promise<void> {
|
|
1308
|
+
if (this.hooks?.beforePut) {
|
|
1309
|
+
const context = this.currentContext;
|
|
1310
|
+
if (context) {
|
|
1311
|
+
const hookContext: IStorageHookContext = {
|
|
1312
|
+
operation: 'put',
|
|
1313
|
+
key,
|
|
1314
|
+
protocol: context.protocol,
|
|
1315
|
+
actor: context.actor,
|
|
1316
|
+
metadata: context.metadata,
|
|
1317
|
+
timestamp: new Date(),
|
|
1318
|
+
};
|
|
1319
|
+
const result = await this.hooks.beforePut(hookContext);
|
|
1320
|
+
if (!result.allowed) {
|
|
1321
|
+
throw new Error(result.reason || 'Storage operation denied by hook');
|
|
1322
|
+
}
|
|
1323
|
+
}
|
|
1324
|
+
}
|
|
1325
|
+
|
|
1326
|
+
// Convert WebStream to Node Readable at the S3 SDK boundary
|
|
1327
|
+
// AWS SDK v3 PutObjectCommand requires a Node.js Readable (not WebStream)
|
|
1328
|
+
const { Readable } = await import('stream');
|
|
1329
|
+
const nodeStream = Readable.fromWeb(stream as any);
|
|
1330
|
+
await this.bucket.fastPutStream({
|
|
1331
|
+
path: key,
|
|
1332
|
+
readableStream: nodeStream,
|
|
1333
|
+
overwrite: true,
|
|
1334
|
+
});
|
|
1335
|
+
|
|
1336
|
+
if (this.hooks?.afterPut) {
|
|
1337
|
+
const context = this.currentContext;
|
|
1338
|
+
if (context) {
|
|
1339
|
+
this.hooks.afterPut({
|
|
1340
|
+
operation: 'put',
|
|
1341
|
+
key,
|
|
1342
|
+
protocol: context.protocol,
|
|
1343
|
+
actor: context.actor,
|
|
1344
|
+
metadata: context.metadata,
|
|
1345
|
+
timestamp: new Date(),
|
|
1346
|
+
}).catch(() => {});
|
|
1347
|
+
}
|
|
1348
|
+
}
|
|
1349
|
+
}
|
|
1350
|
+
|
|
1351
|
+
/**
|
|
1352
|
+
* Get object size without reading data (S3 HEAD request).
|
|
1353
|
+
*/
|
|
1354
|
+
public async getObjectSize(key: string): Promise<number | null> {
|
|
1355
|
+
try {
|
|
1356
|
+
const stat = await this.bucket.fastStat({ path: key });
|
|
1357
|
+
return stat.ContentLength ?? null;
|
|
1358
|
+
} catch {
|
|
1359
|
+
return null;
|
|
1360
|
+
}
|
|
1361
|
+
}
|
|
1362
|
+
|
|
1363
|
+
// ---- Protocol-specific streaming wrappers ----
|
|
1364
|
+
|
|
1365
|
+
public async getOciBlobStream(digest: string): Promise<{ stream: ReadableStream<Uint8Array>; size: number } | null> {
|
|
1366
|
+
return this.getObjectStream(this.getOciBlobPath(digest));
|
|
1367
|
+
}
|
|
1368
|
+
|
|
1369
|
+
public async putOciBlobStream(digest: string, stream: ReadableStream<Uint8Array>): Promise<void> {
|
|
1370
|
+
return this.putObjectStream(this.getOciBlobPath(digest), stream);
|
|
1371
|
+
}
|
|
1372
|
+
|
|
1373
|
+
public async getOciBlobSize(digest: string): Promise<number | null> {
|
|
1374
|
+
return this.getObjectSize(this.getOciBlobPath(digest));
|
|
1375
|
+
}
|
|
1376
|
+
|
|
1377
|
+
public async getNpmTarballStream(packageName: string, version: string): Promise<{ stream: ReadableStream<Uint8Array>; size: number } | null> {
|
|
1378
|
+
return this.getObjectStream(this.getNpmTarballPath(packageName, version));
|
|
1379
|
+
}
|
|
1380
|
+
|
|
1381
|
+
public async getMavenArtifactStream(groupId: string, artifactId: string, version: string, filename: string): Promise<{ stream: ReadableStream<Uint8Array>; size: number } | null> {
|
|
1382
|
+
return this.getObjectStream(this.getMavenArtifactPath(groupId, artifactId, version, filename));
|
|
1383
|
+
}
|
|
1384
|
+
|
|
1385
|
+
public async getCargoCrateStream(crateName: string, version: string): Promise<{ stream: ReadableStream<Uint8Array>; size: number } | null> {
|
|
1386
|
+
return this.getObjectStream(this.getCargoCratePath(crateName, version));
|
|
1387
|
+
}
|
|
1388
|
+
|
|
1389
|
+
public async getComposerPackageZipStream(vendorPackage: string, reference: string): Promise<{ stream: ReadableStream<Uint8Array>; size: number } | null> {
|
|
1390
|
+
return this.getObjectStream(this.getComposerZipPath(vendorPackage, reference));
|
|
1391
|
+
}
|
|
1392
|
+
|
|
1393
|
+
public async getPypiPackageFileStream(packageName: string, filename: string): Promise<{ stream: ReadableStream<Uint8Array>; size: number } | null> {
|
|
1394
|
+
return this.getObjectStream(this.getPypiPackageFilePath(packageName, filename));
|
|
1395
|
+
}
|
|
1396
|
+
|
|
1397
|
+
public async getRubyGemsGemStream(gemName: string, version: string, platform?: string): Promise<{ stream: ReadableStream<Uint8Array>; size: number } | null> {
|
|
1398
|
+
return this.getObjectStream(this.getRubyGemsGemPath(gemName, version, platform));
|
|
1399
|
+
}
|
|
1269
1400
|
}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import * as crypto from 'crypto';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Convert Buffer, Uint8Array, string, or JSON object to a ReadableStream<Uint8Array>.
|
|
5
|
+
*/
|
|
6
|
+
export function toReadableStream(data: Buffer | Uint8Array | string | object): ReadableStream<Uint8Array> {
|
|
7
|
+
const buf = Buffer.isBuffer(data)
|
|
8
|
+
? data
|
|
9
|
+
: data instanceof Uint8Array
|
|
10
|
+
? Buffer.from(data)
|
|
11
|
+
: typeof data === 'string'
|
|
12
|
+
? Buffer.from(data, 'utf-8')
|
|
13
|
+
: Buffer.from(JSON.stringify(data), 'utf-8');
|
|
14
|
+
return new ReadableStream<Uint8Array>({
|
|
15
|
+
start(controller) {
|
|
16
|
+
controller.enqueue(new Uint8Array(buf));
|
|
17
|
+
controller.close();
|
|
18
|
+
},
|
|
19
|
+
});
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Consume a ReadableStream into a Buffer.
|
|
24
|
+
*/
|
|
25
|
+
export async function streamToBuffer(stream: ReadableStream<Uint8Array>): Promise<Buffer> {
|
|
26
|
+
const reader = stream.getReader();
|
|
27
|
+
const chunks: Uint8Array[] = [];
|
|
28
|
+
while (true) {
|
|
29
|
+
const { done, value } = await reader.read();
|
|
30
|
+
if (done) break;
|
|
31
|
+
if (value) chunks.push(value);
|
|
32
|
+
}
|
|
33
|
+
return Buffer.concat(chunks);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Consume a ReadableStream into a parsed JSON object.
|
|
38
|
+
*/
|
|
39
|
+
export async function streamToJson<T = any>(stream: ReadableStream<Uint8Array>): Promise<T> {
|
|
40
|
+
const buf = await streamToBuffer(stream);
|
|
41
|
+
return JSON.parse(buf.toString('utf-8'));
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Create a TransformStream that incrementally hashes data passing through.
|
|
46
|
+
* Data flows through unchanged; the digest is available after the stream completes.
|
|
47
|
+
*/
|
|
48
|
+
export function createHashTransform(algorithm: string = 'sha256'): {
|
|
49
|
+
transform: TransformStream<Uint8Array, Uint8Array>;
|
|
50
|
+
getDigest: () => string;
|
|
51
|
+
} {
|
|
52
|
+
const hash = crypto.createHash(algorithm);
|
|
53
|
+
const transform = new TransformStream<Uint8Array, Uint8Array>({
|
|
54
|
+
transform(chunk, controller) {
|
|
55
|
+
hash.update(chunk);
|
|
56
|
+
controller.enqueue(chunk);
|
|
57
|
+
},
|
|
58
|
+
});
|
|
59
|
+
return {
|
|
60
|
+
transform,
|
|
61
|
+
getDigest: () => hash.digest('hex'),
|
|
62
|
+
};
|
|
63
|
+
}
|
package/ts/core/index.ts
CHANGED
|
@@ -12,6 +12,9 @@ export { DefaultAuthProvider } from './classes.defaultauthprovider.js';
|
|
|
12
12
|
// Storage interfaces and hooks
|
|
13
13
|
export * from './interfaces.storage.js';
|
|
14
14
|
|
|
15
|
+
// Stream helpers
|
|
16
|
+
export { toReadableStream, streamToBuffer, streamToJson, createHashTransform } from './helpers.stream.js';
|
|
17
|
+
|
|
15
18
|
// Classes
|
|
16
19
|
export { BaseRegistry } from './classes.baseregistry.js';
|
|
17
20
|
export { RegistryStorage } from './classes.registrystorage.js';
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
5
|
import type * as plugins from '../plugins.js';
|
|
6
|
-
import type {
|
|
6
|
+
import type { IUpstreamProvider } from '../upstream/interfaces.upstream.js';
|
|
7
7
|
import type { IAuthProvider } from './interfaces.auth.js';
|
|
8
8
|
import type { IStorageHooks } from './interfaces.storage.js';
|
|
9
9
|
|
|
@@ -88,9 +88,8 @@ export interface IAuthConfig {
|
|
|
88
88
|
export interface IProtocolConfig {
|
|
89
89
|
enabled: boolean;
|
|
90
90
|
basePath: string;
|
|
91
|
+
registryUrl?: string;
|
|
91
92
|
features?: Record<string, boolean>;
|
|
92
|
-
/** Upstream registry configuration for proxying/caching */
|
|
93
|
-
upstream?: IProtocolUpstreamConfig;
|
|
94
93
|
}
|
|
95
94
|
|
|
96
95
|
/**
|
|
@@ -113,6 +112,13 @@ export interface IRegistryConfig {
|
|
|
113
112
|
*/
|
|
114
113
|
storageHooks?: IStorageHooks;
|
|
115
114
|
|
|
115
|
+
/**
|
|
116
|
+
* Dynamic upstream configuration provider.
|
|
117
|
+
* Called per-request to resolve which upstream registries to use.
|
|
118
|
+
* Use StaticUpstreamProvider for simple static configurations.
|
|
119
|
+
*/
|
|
120
|
+
upstreamProvider?: IUpstreamProvider;
|
|
121
|
+
|
|
116
122
|
oci?: IProtocolConfig;
|
|
117
123
|
npm?: IProtocolConfig;
|
|
118
124
|
maven?: IProtocolConfig;
|
|
@@ -155,6 +161,21 @@ export interface IStorageBackend {
|
|
|
155
161
|
* Get object metadata
|
|
156
162
|
*/
|
|
157
163
|
getMetadata(key: string): Promise<Record<string, string> | null>;
|
|
164
|
+
|
|
165
|
+
/**
|
|
166
|
+
* Get an object as a ReadableStream. Returns null if not found.
|
|
167
|
+
*/
|
|
168
|
+
getObjectStream?(key: string): Promise<{ stream: ReadableStream<Uint8Array>; size: number } | null>;
|
|
169
|
+
|
|
170
|
+
/**
|
|
171
|
+
* Store an object from a ReadableStream.
|
|
172
|
+
*/
|
|
173
|
+
putObjectStream?(key: string, stream: ReadableStream<Uint8Array>): Promise<void>;
|
|
174
|
+
|
|
175
|
+
/**
|
|
176
|
+
* Get object size without reading data (S3 HEAD request).
|
|
177
|
+
*/
|
|
178
|
+
getObjectSize?(key: string): Promise<number | null>;
|
|
158
179
|
}
|
|
159
180
|
|
|
160
181
|
/**
|
|
@@ -210,10 +231,13 @@ export interface IRequestContext {
|
|
|
210
231
|
}
|
|
211
232
|
|
|
212
233
|
/**
|
|
213
|
-
* Base response structure
|
|
234
|
+
* Base response structure.
|
|
235
|
+
* `body` is always a `ReadableStream<Uint8Array>` at the public API boundary.
|
|
236
|
+
* Internal handlers may return Buffer/string/object — the SmartRegistry orchestrator
|
|
237
|
+
* auto-wraps them via `toReadableStream()` before returning to the caller.
|
|
214
238
|
*/
|
|
215
239
|
export interface IResponse {
|
|
216
240
|
status: number;
|
|
217
241
|
headers: Record<string, string>;
|
|
218
|
-
body?: any;
|
|
242
|
+
body?: ReadableStream<Uint8Array> | any;
|
|
219
243
|
}
|
|
@@ -6,8 +6,8 @@
|
|
|
6
6
|
import { BaseRegistry } from '../core/classes.baseregistry.js';
|
|
7
7
|
import type { RegistryStorage } from '../core/classes.registrystorage.js';
|
|
8
8
|
import type { AuthManager } from '../core/classes.authmanager.js';
|
|
9
|
-
import type { IRequestContext, IResponse, IAuthToken } from '../core/interfaces.core.js';
|
|
10
|
-
import type {
|
|
9
|
+
import type { IRequestContext, IResponse, IAuthToken, IRequestActor } from '../core/interfaces.core.js';
|
|
10
|
+
import type { IUpstreamProvider } from '../upstream/interfaces.upstream.js';
|
|
11
11
|
import { toBuffer } from '../core/helpers.buffer.js';
|
|
12
12
|
import type { IMavenCoordinate, IMavenMetadata, IChecksums } from './interfaces.maven.js';
|
|
13
13
|
import {
|
|
@@ -33,34 +33,64 @@ export class MavenRegistry extends BaseRegistry {
|
|
|
33
33
|
private authManager: AuthManager;
|
|
34
34
|
private basePath: string = '/maven';
|
|
35
35
|
private registryUrl: string;
|
|
36
|
-
private
|
|
36
|
+
private upstreamProvider: IUpstreamProvider | null = null;
|
|
37
37
|
|
|
38
38
|
constructor(
|
|
39
39
|
storage: RegistryStorage,
|
|
40
40
|
authManager: AuthManager,
|
|
41
41
|
basePath: string,
|
|
42
42
|
registryUrl: string,
|
|
43
|
-
|
|
43
|
+
upstreamProvider?: IUpstreamProvider
|
|
44
44
|
) {
|
|
45
45
|
super();
|
|
46
46
|
this.storage = storage;
|
|
47
47
|
this.authManager = authManager;
|
|
48
48
|
this.basePath = basePath;
|
|
49
49
|
this.registryUrl = registryUrl;
|
|
50
|
+
this.upstreamProvider = upstreamProvider || null;
|
|
51
|
+
}
|
|
50
52
|
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
53
|
+
/**
|
|
54
|
+
* Extract scope from Maven coordinates.
|
|
55
|
+
* For Maven, the groupId is the scope.
|
|
56
|
+
* @example "com.example" from "com.example:my-lib"
|
|
57
|
+
*/
|
|
58
|
+
private extractScope(groupId: string): string | null {
|
|
59
|
+
return groupId || null;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Get upstream for a specific request.
|
|
64
|
+
* Calls the provider to resolve upstream config dynamically.
|
|
65
|
+
*/
|
|
66
|
+
private async getUpstreamForRequest(
|
|
67
|
+
resource: string,
|
|
68
|
+
resourceType: string,
|
|
69
|
+
method: string,
|
|
70
|
+
actor?: IRequestActor
|
|
71
|
+
): Promise<MavenUpstream | null> {
|
|
72
|
+
if (!this.upstreamProvider) return null;
|
|
73
|
+
|
|
74
|
+
// For Maven, resource is "groupId:artifactId"
|
|
75
|
+
const [groupId] = resource.split(':');
|
|
76
|
+
const config = await this.upstreamProvider.resolveUpstreamConfig({
|
|
77
|
+
protocol: 'maven',
|
|
78
|
+
resource,
|
|
79
|
+
scope: this.extractScope(groupId),
|
|
80
|
+
actor,
|
|
81
|
+
method,
|
|
82
|
+
resourceType,
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
if (!config?.enabled) return null;
|
|
86
|
+
return new MavenUpstream(config);
|
|
55
87
|
}
|
|
56
88
|
|
|
57
89
|
/**
|
|
58
90
|
* Clean up resources (timers, connections, etc.)
|
|
59
91
|
*/
|
|
60
92
|
public destroy(): void {
|
|
61
|
-
|
|
62
|
-
this.upstream.stop();
|
|
63
|
-
}
|
|
93
|
+
// No persistent upstream to clean up with dynamic provider
|
|
64
94
|
}
|
|
65
95
|
|
|
66
96
|
public async init(): Promise<void> {
|
|
@@ -85,13 +115,21 @@ export class MavenRegistry extends BaseRegistry {
|
|
|
85
115
|
token = await this.authManager.validateToken(tokenString, 'maven');
|
|
86
116
|
}
|
|
87
117
|
|
|
118
|
+
// Build actor from context and validated token
|
|
119
|
+
const actor: IRequestActor = {
|
|
120
|
+
...context.actor,
|
|
121
|
+
userId: token?.userId,
|
|
122
|
+
ip: context.headers['x-forwarded-for'] || context.headers['X-Forwarded-For'],
|
|
123
|
+
userAgent: context.headers['user-agent'] || context.headers['User-Agent'],
|
|
124
|
+
};
|
|
125
|
+
|
|
88
126
|
// Parse path to determine request type
|
|
89
127
|
const coordinate = pathToGAV(path);
|
|
90
128
|
|
|
91
129
|
if (!coordinate) {
|
|
92
130
|
// Not a valid artifact path, could be metadata or root
|
|
93
131
|
if (path.endsWith('/maven-metadata.xml')) {
|
|
94
|
-
return this.handleMetadataRequest(context.method, path, token);
|
|
132
|
+
return this.handleMetadataRequest(context.method, path, token, actor);
|
|
95
133
|
}
|
|
96
134
|
|
|
97
135
|
return {
|
|
@@ -108,7 +146,7 @@ export class MavenRegistry extends BaseRegistry {
|
|
|
108
146
|
}
|
|
109
147
|
|
|
110
148
|
// Handle artifact requests (JAR, POM, WAR, etc.)
|
|
111
|
-
return this.handleArtifactRequest(context.method, coordinate, token, context.body);
|
|
149
|
+
return this.handleArtifactRequest(context.method, coordinate, token, context.body, actor);
|
|
112
150
|
}
|
|
113
151
|
|
|
114
152
|
protected async checkPermission(
|
|
@@ -128,7 +166,8 @@ export class MavenRegistry extends BaseRegistry {
|
|
|
128
166
|
method: string,
|
|
129
167
|
coordinate: IMavenCoordinate,
|
|
130
168
|
token: IAuthToken | null,
|
|
131
|
-
body?: Buffer | any
|
|
169
|
+
body?: Buffer | any,
|
|
170
|
+
actor?: IRequestActor
|
|
132
171
|
): Promise<IResponse> {
|
|
133
172
|
const { groupId, artifactId, version } = coordinate;
|
|
134
173
|
const filename = buildFilename(coordinate);
|
|
@@ -139,7 +178,7 @@ export class MavenRegistry extends BaseRegistry {
|
|
|
139
178
|
case 'HEAD':
|
|
140
179
|
// Maven repositories typically allow anonymous reads
|
|
141
180
|
return method === 'GET'
|
|
142
|
-
? this.getArtifact(groupId, artifactId, version, filename)
|
|
181
|
+
? this.getArtifact(groupId, artifactId, version, filename, actor)
|
|
143
182
|
: this.headArtifact(groupId, artifactId, version, filename);
|
|
144
183
|
|
|
145
184
|
case 'PUT':
|
|
@@ -211,7 +250,8 @@ export class MavenRegistry extends BaseRegistry {
|
|
|
211
250
|
private async handleMetadataRequest(
|
|
212
251
|
method: string,
|
|
213
252
|
path: string,
|
|
214
|
-
token: IAuthToken | null
|
|
253
|
+
token: IAuthToken | null,
|
|
254
|
+
actor?: IRequestActor
|
|
215
255
|
): Promise<IResponse> {
|
|
216
256
|
// Parse path to extract groupId and artifactId
|
|
217
257
|
// Path format: /com/example/my-lib/maven-metadata.xml
|
|
@@ -232,7 +272,7 @@ export class MavenRegistry extends BaseRegistry {
|
|
|
232
272
|
if (method === 'GET') {
|
|
233
273
|
// Metadata is usually public (read permission optional)
|
|
234
274
|
// Some registries allow anonymous metadata access
|
|
235
|
-
return this.getMetadata(groupId, artifactId);
|
|
275
|
+
return this.getMetadata(groupId, artifactId, actor);
|
|
236
276
|
}
|
|
237
277
|
|
|
238
278
|
return {
|
|
@@ -250,16 +290,33 @@ export class MavenRegistry extends BaseRegistry {
|
|
|
250
290
|
groupId: string,
|
|
251
291
|
artifactId: string,
|
|
252
292
|
version: string,
|
|
253
|
-
filename: string
|
|
293
|
+
filename: string,
|
|
294
|
+
actor?: IRequestActor
|
|
254
295
|
): Promise<IResponse> {
|
|
255
|
-
|
|
296
|
+
// Try local storage first (streaming)
|
|
297
|
+
const streamResult = await this.storage.getMavenArtifactStream(groupId, artifactId, version, filename);
|
|
298
|
+
if (streamResult) {
|
|
299
|
+
const ext = filename.split('.').pop() || '';
|
|
300
|
+
const contentType = this.getContentType(ext);
|
|
301
|
+
return {
|
|
302
|
+
status: 200,
|
|
303
|
+
headers: {
|
|
304
|
+
'Content-Type': contentType,
|
|
305
|
+
'Content-Length': streamResult.size.toString(),
|
|
306
|
+
},
|
|
307
|
+
body: streamResult.stream,
|
|
308
|
+
};
|
|
309
|
+
}
|
|
256
310
|
|
|
257
311
|
// Try upstream if not found locally
|
|
258
|
-
|
|
312
|
+
let data: Buffer | null = null;
|
|
313
|
+
const resource = `${groupId}:${artifactId}`;
|
|
314
|
+
const upstream = await this.getUpstreamForRequest(resource, 'artifact', 'GET', actor);
|
|
315
|
+
if (upstream) {
|
|
259
316
|
// Parse the filename to extract extension and classifier
|
|
260
317
|
const { extension, classifier } = this.parseFilename(filename, artifactId, version);
|
|
261
318
|
if (extension) {
|
|
262
|
-
data = await
|
|
319
|
+
data = await upstream.fetchArtifact(groupId, artifactId, version, extension, classifier);
|
|
263
320
|
if (data) {
|
|
264
321
|
// Cache the artifact locally
|
|
265
322
|
await this.storage.putMavenArtifact(groupId, artifactId, version, filename, data);
|
|
@@ -495,16 +552,20 @@ export class MavenRegistry extends BaseRegistry {
|
|
|
495
552
|
// METADATA OPERATIONS
|
|
496
553
|
// ========================================================================
|
|
497
554
|
|
|
498
|
-
private async getMetadata(groupId: string, artifactId: string): Promise<IResponse> {
|
|
555
|
+
private async getMetadata(groupId: string, artifactId: string, actor?: IRequestActor): Promise<IResponse> {
|
|
499
556
|
let metadataBuffer = await this.storage.getMavenMetadata(groupId, artifactId);
|
|
500
557
|
|
|
501
558
|
// Try upstream if not found locally
|
|
502
|
-
if (!metadataBuffer
|
|
503
|
-
const
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
559
|
+
if (!metadataBuffer) {
|
|
560
|
+
const resource = `${groupId}:${artifactId}`;
|
|
561
|
+
const upstream = await this.getUpstreamForRequest(resource, 'metadata', 'GET', actor);
|
|
562
|
+
if (upstream) {
|
|
563
|
+
const upstreamMetadata = await upstream.fetchMetadata(groupId, artifactId);
|
|
564
|
+
if (upstreamMetadata) {
|
|
565
|
+
metadataBuffer = Buffer.from(upstreamMetadata, 'utf-8');
|
|
566
|
+
// Cache the metadata locally
|
|
567
|
+
await this.storage.putMavenMetadata(groupId, artifactId, metadataBuffer);
|
|
568
|
+
}
|
|
508
569
|
}
|
|
509
570
|
}
|
|
510
571
|
|