@push.rocks/smartregistry 2.5.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
|
@@ -2,8 +2,8 @@ import { Smartlog } from '@push.rocks/smartlog';
|
|
|
2
2
|
import { BaseRegistry } from '../core/classes.baseregistry.js';
|
|
3
3
|
import { RegistryStorage } from '../core/classes.registrystorage.js';
|
|
4
4
|
import { AuthManager } from '../core/classes.authmanager.js';
|
|
5
|
-
import type { IRequestContext, IResponse, IAuthToken } from '../core/interfaces.core.js';
|
|
6
|
-
import type {
|
|
5
|
+
import type { IRequestContext, IResponse, IAuthToken, IRequestActor } from '../core/interfaces.core.js';
|
|
6
|
+
import type { IUpstreamProvider } from '../upstream/interfaces.upstream.js';
|
|
7
7
|
import { NpmUpstream } from './classes.npmupstream.js';
|
|
8
8
|
import type {
|
|
9
9
|
IPackument,
|
|
@@ -27,20 +27,21 @@ export class NpmRegistry extends BaseRegistry {
|
|
|
27
27
|
private basePath: string = '/npm';
|
|
28
28
|
private registryUrl: string;
|
|
29
29
|
private logger: Smartlog;
|
|
30
|
-
private
|
|
30
|
+
private upstreamProvider: IUpstreamProvider | null = null;
|
|
31
31
|
|
|
32
32
|
constructor(
|
|
33
33
|
storage: RegistryStorage,
|
|
34
34
|
authManager: AuthManager,
|
|
35
35
|
basePath: string = '/npm',
|
|
36
36
|
registryUrl: string = 'http://localhost:5000/npm',
|
|
37
|
-
|
|
37
|
+
upstreamProvider?: IUpstreamProvider
|
|
38
38
|
) {
|
|
39
39
|
super();
|
|
40
40
|
this.storage = storage;
|
|
41
41
|
this.authManager = authManager;
|
|
42
42
|
this.basePath = basePath;
|
|
43
43
|
this.registryUrl = registryUrl;
|
|
44
|
+
this.upstreamProvider = upstreamProvider || null;
|
|
44
45
|
|
|
45
46
|
// Initialize logger
|
|
46
47
|
this.logger = new Smartlog({
|
|
@@ -55,13 +56,49 @@ export class NpmRegistry extends BaseRegistry {
|
|
|
55
56
|
});
|
|
56
57
|
this.logger.enableConsole();
|
|
57
58
|
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
59
|
+
if (upstreamProvider) {
|
|
60
|
+
this.logger.log('info', 'NPM upstream provider configured');
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Extract scope from npm package name.
|
|
66
|
+
* @example "@company/utils" -> "company"
|
|
67
|
+
* @example "lodash" -> null
|
|
68
|
+
*/
|
|
69
|
+
private extractScope(packageName: string): string | null {
|
|
70
|
+
if (packageName.startsWith('@')) {
|
|
71
|
+
const slashIndex = packageName.indexOf('/');
|
|
72
|
+
if (slashIndex > 1) {
|
|
73
|
+
return packageName.substring(1, slashIndex);
|
|
74
|
+
}
|
|
64
75
|
}
|
|
76
|
+
return null;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Get upstream for a specific request.
|
|
81
|
+
* Calls the provider to resolve upstream config dynamically.
|
|
82
|
+
*/
|
|
83
|
+
private async getUpstreamForRequest(
|
|
84
|
+
resource: string,
|
|
85
|
+
resourceType: string,
|
|
86
|
+
method: string,
|
|
87
|
+
actor?: IRequestActor
|
|
88
|
+
): Promise<NpmUpstream | null> {
|
|
89
|
+
if (!this.upstreamProvider) return null;
|
|
90
|
+
|
|
91
|
+
const config = await this.upstreamProvider.resolveUpstreamConfig({
|
|
92
|
+
protocol: 'npm',
|
|
93
|
+
resource,
|
|
94
|
+
scope: this.extractScope(resource),
|
|
95
|
+
actor,
|
|
96
|
+
method,
|
|
97
|
+
resourceType,
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
if (!config?.enabled) return null;
|
|
101
|
+
return new NpmUpstream(config, this.registryUrl, this.logger);
|
|
65
102
|
}
|
|
66
103
|
|
|
67
104
|
public async init(): Promise<void> {
|
|
@@ -80,6 +117,14 @@ export class NpmRegistry extends BaseRegistry {
|
|
|
80
117
|
const tokenString = authHeader?.replace(/^Bearer\s+/i, '');
|
|
81
118
|
const token = tokenString ? await this.authManager.validateToken(tokenString, 'npm') : null;
|
|
82
119
|
|
|
120
|
+
// Build actor context for upstream resolution
|
|
121
|
+
const actor: IRequestActor = {
|
|
122
|
+
userId: token?.userId,
|
|
123
|
+
ip: context.headers['x-forwarded-for'] || context.headers['x-real-ip'],
|
|
124
|
+
userAgent: context.headers['user-agent'],
|
|
125
|
+
...context.actor, // Include any pre-populated actor info
|
|
126
|
+
};
|
|
127
|
+
|
|
83
128
|
this.logger.log('debug', `handleRequest: ${context.method} ${path}`, {
|
|
84
129
|
method: context.method,
|
|
85
130
|
path,
|
|
@@ -110,47 +155,47 @@ export class NpmRegistry extends BaseRegistry {
|
|
|
110
155
|
// Dist-tags: /-/package/{package}/dist-tags
|
|
111
156
|
const distTagsMatch = path.match(/^\/-\/package\/(@?[^\/]+(?:\/[^\/]+)?)\/dist-tags(?:\/(.+))?$/);
|
|
112
157
|
if (distTagsMatch) {
|
|
113
|
-
const [,
|
|
114
|
-
return this.handleDistTags(context.method,
|
|
158
|
+
const [, rawPkgName, tag] = distTagsMatch;
|
|
159
|
+
return this.handleDistTags(context.method, decodeURIComponent(rawPkgName), tag, context.body, token);
|
|
115
160
|
}
|
|
116
161
|
|
|
117
162
|
// Tarball download: /{package}/-/{filename}.tgz
|
|
118
163
|
const tarballMatch = path.match(/^\/(@?[^\/]+(?:\/[^\/]+)?)\/-\/(.+\.tgz)$/);
|
|
119
164
|
if (tarballMatch) {
|
|
120
|
-
const [,
|
|
121
|
-
return this.handleTarballDownload(
|
|
165
|
+
const [, rawPkgName, filename] = tarballMatch;
|
|
166
|
+
return this.handleTarballDownload(decodeURIComponent(rawPkgName), filename, token, actor);
|
|
122
167
|
}
|
|
123
168
|
|
|
124
169
|
// Unpublish specific version: DELETE /{package}/-/{version}
|
|
125
170
|
const unpublishVersionMatch = path.match(/^\/(@?[^\/]+(?:\/[^\/]+)?)\/-\/([^\/]+)$/);
|
|
126
171
|
if (unpublishVersionMatch && context.method === 'DELETE') {
|
|
127
|
-
const [,
|
|
128
|
-
this.logger.log('debug', 'unpublishVersionMatch', { packageName, version });
|
|
129
|
-
return this.unpublishVersion(
|
|
172
|
+
const [, rawPkgName, version] = unpublishVersionMatch;
|
|
173
|
+
this.logger.log('debug', 'unpublishVersionMatch', { packageName: decodeURIComponent(rawPkgName), version });
|
|
174
|
+
return this.unpublishVersion(decodeURIComponent(rawPkgName), version, token);
|
|
130
175
|
}
|
|
131
176
|
|
|
132
177
|
// Unpublish entire package: DELETE /{package}/-rev/{rev}
|
|
133
178
|
const unpublishPackageMatch = path.match(/^\/(@?[^\/]+(?:\/[^\/]+)?)\/-rev\/([^\/]+)$/);
|
|
134
179
|
if (unpublishPackageMatch && context.method === 'DELETE') {
|
|
135
|
-
const [,
|
|
136
|
-
this.logger.log('debug', 'unpublishPackageMatch', { packageName, rev });
|
|
137
|
-
return this.unpublishPackage(
|
|
180
|
+
const [, rawPkgName, rev] = unpublishPackageMatch;
|
|
181
|
+
this.logger.log('debug', 'unpublishPackageMatch', { packageName: decodeURIComponent(rawPkgName), rev });
|
|
182
|
+
return this.unpublishPackage(decodeURIComponent(rawPkgName), token);
|
|
138
183
|
}
|
|
139
184
|
|
|
140
185
|
// Package version: /{package}/{version}
|
|
141
186
|
const versionMatch = path.match(/^\/(@?[^\/]+(?:\/[^\/]+)?)\/([^\/]+)$/);
|
|
142
187
|
if (versionMatch) {
|
|
143
|
-
const [,
|
|
144
|
-
this.logger.log('debug', 'versionMatch', { packageName, version });
|
|
145
|
-
return this.handlePackageVersion(
|
|
188
|
+
const [, rawPkgName, version] = versionMatch;
|
|
189
|
+
this.logger.log('debug', 'versionMatch', { packageName: decodeURIComponent(rawPkgName), version });
|
|
190
|
+
return this.handlePackageVersion(decodeURIComponent(rawPkgName), version, token, actor);
|
|
146
191
|
}
|
|
147
192
|
|
|
148
193
|
// Package operations: /{package}
|
|
149
194
|
const packageMatch = path.match(/^\/(@?[^\/]+(?:\/[^\/]+)?)$/);
|
|
150
195
|
if (packageMatch) {
|
|
151
|
-
const packageName = packageMatch[1];
|
|
196
|
+
const packageName = decodeURIComponent(packageMatch[1]);
|
|
152
197
|
this.logger.log('debug', 'packageMatch', { packageName });
|
|
153
|
-
return this.handlePackage(context.method, packageName, context.body, context.query, token);
|
|
198
|
+
return this.handlePackage(context.method, packageName, context.body, context.query, token, actor);
|
|
154
199
|
}
|
|
155
200
|
|
|
156
201
|
return {
|
|
@@ -198,11 +243,12 @@ export class NpmRegistry extends BaseRegistry {
|
|
|
198
243
|
packageName: string,
|
|
199
244
|
body: any,
|
|
200
245
|
query: Record<string, string>,
|
|
201
|
-
token: IAuthToken | null
|
|
246
|
+
token: IAuthToken | null,
|
|
247
|
+
actor?: IRequestActor
|
|
202
248
|
): Promise<IResponse> {
|
|
203
249
|
switch (method) {
|
|
204
250
|
case 'GET':
|
|
205
|
-
return this.getPackument(packageName, token, query);
|
|
251
|
+
return this.getPackument(packageName, token, query, actor);
|
|
206
252
|
case 'PUT':
|
|
207
253
|
return this.publishPackage(packageName, body, token);
|
|
208
254
|
case 'DELETE':
|
|
@@ -219,7 +265,8 @@ export class NpmRegistry extends BaseRegistry {
|
|
|
219
265
|
private async getPackument(
|
|
220
266
|
packageName: string,
|
|
221
267
|
token: IAuthToken | null,
|
|
222
|
-
query: Record<string, string
|
|
268
|
+
query: Record<string, string>,
|
|
269
|
+
actor?: IRequestActor
|
|
223
270
|
): Promise<IResponse> {
|
|
224
271
|
let packument = await this.storage.getNpmPackument(packageName);
|
|
225
272
|
this.logger.log('debug', `getPackument: ${packageName}`, {
|
|
@@ -229,17 +276,20 @@ export class NpmRegistry extends BaseRegistry {
|
|
|
229
276
|
});
|
|
230
277
|
|
|
231
278
|
// If not found locally, try upstream
|
|
232
|
-
if (!packument
|
|
233
|
-
this.
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
279
|
+
if (!packument) {
|
|
280
|
+
const upstream = await this.getUpstreamForRequest(packageName, 'packument', 'GET', actor);
|
|
281
|
+
if (upstream) {
|
|
282
|
+
this.logger.log('debug', `getPackument: fetching from upstream`, { packageName });
|
|
283
|
+
const upstreamPackument = await upstream.fetchPackument(packageName);
|
|
284
|
+
if (upstreamPackument) {
|
|
285
|
+
this.logger.log('debug', `getPackument: found in upstream`, {
|
|
286
|
+
packageName,
|
|
287
|
+
versions: Object.keys(upstreamPackument.versions || {}).length
|
|
288
|
+
});
|
|
289
|
+
packument = upstreamPackument;
|
|
290
|
+
// Optionally cache the packument locally (without tarballs)
|
|
291
|
+
// We don't store tarballs here - they'll be fetched on demand
|
|
292
|
+
}
|
|
243
293
|
}
|
|
244
294
|
}
|
|
245
295
|
|
|
@@ -279,7 +329,8 @@ export class NpmRegistry extends BaseRegistry {
|
|
|
279
329
|
private async handlePackageVersion(
|
|
280
330
|
packageName: string,
|
|
281
331
|
version: string,
|
|
282
|
-
token: IAuthToken | null
|
|
332
|
+
token: IAuthToken | null,
|
|
333
|
+
actor?: IRequestActor
|
|
283
334
|
): Promise<IResponse> {
|
|
284
335
|
this.logger.log('debug', 'handlePackageVersion', { packageName, version });
|
|
285
336
|
let packument = await this.storage.getNpmPackument(packageName);
|
|
@@ -289,11 +340,14 @@ export class NpmRegistry extends BaseRegistry {
|
|
|
289
340
|
}
|
|
290
341
|
|
|
291
342
|
// If not found locally, try upstream
|
|
292
|
-
if (!packument
|
|
293
|
-
this.
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
343
|
+
if (!packument) {
|
|
344
|
+
const upstream = await this.getUpstreamForRequest(packageName, 'packument', 'GET', actor);
|
|
345
|
+
if (upstream) {
|
|
346
|
+
this.logger.log('debug', 'handlePackageVersion: fetching from upstream', { packageName });
|
|
347
|
+
const upstreamPackument = await upstream.fetchPackument(packageName);
|
|
348
|
+
if (upstreamPackument) {
|
|
349
|
+
packument = upstreamPackument;
|
|
350
|
+
}
|
|
297
351
|
}
|
|
298
352
|
}
|
|
299
353
|
|
|
@@ -563,7 +617,8 @@ export class NpmRegistry extends BaseRegistry {
|
|
|
563
617
|
private async handleTarballDownload(
|
|
564
618
|
packageName: string,
|
|
565
619
|
filename: string,
|
|
566
|
-
token: IAuthToken | null
|
|
620
|
+
token: IAuthToken | null,
|
|
621
|
+
actor?: IRequestActor
|
|
567
622
|
): Promise<IResponse> {
|
|
568
623
|
// Extract version from filename: package-name-1.0.0.tgz
|
|
569
624
|
const versionMatch = filename.match(/-([\d.]+(?:-[a-z0-9.]+)?)\.tgz$/i);
|
|
@@ -576,15 +631,29 @@ export class NpmRegistry extends BaseRegistry {
|
|
|
576
631
|
}
|
|
577
632
|
|
|
578
633
|
const version = versionMatch[1];
|
|
579
|
-
|
|
634
|
+
|
|
635
|
+
// Try local storage first (streaming)
|
|
636
|
+
const streamResult = await this.storage.getNpmTarballStream(packageName, version);
|
|
637
|
+
if (streamResult) {
|
|
638
|
+
return {
|
|
639
|
+
status: 200,
|
|
640
|
+
headers: {
|
|
641
|
+
'Content-Type': 'application/octet-stream',
|
|
642
|
+
'Content-Length': streamResult.size.toString(),
|
|
643
|
+
},
|
|
644
|
+
body: streamResult.stream,
|
|
645
|
+
};
|
|
646
|
+
}
|
|
580
647
|
|
|
581
648
|
// If not found locally, try upstream
|
|
582
|
-
|
|
649
|
+
let tarball: Buffer | null = null;
|
|
650
|
+
const upstream = await this.getUpstreamForRequest(packageName, 'tarball', 'GET', actor);
|
|
651
|
+
if (upstream) {
|
|
583
652
|
this.logger.log('debug', 'handleTarballDownload: fetching from upstream', {
|
|
584
653
|
packageName,
|
|
585
654
|
version,
|
|
586
655
|
});
|
|
587
|
-
const upstreamTarball = await
|
|
656
|
+
const upstreamTarball = await upstream.fetchTarball(packageName, version);
|
|
588
657
|
if (upstreamTarball) {
|
|
589
658
|
tarball = upstreamTarball;
|
|
590
659
|
// Cache the tarball locally for future requests
|