@push.rocks/smartregistry 2.2.3 → 2.3.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/dist_ts/00_commitinfo_data.js +1 -1
- package/dist_ts/cargo/classes.cargoregistry.d.ts +7 -1
- package/dist_ts/cargo/classes.cargoregistry.js +42 -4
- package/dist_ts/cargo/classes.cargoupstream.d.ts +44 -0
- package/dist_ts/cargo/classes.cargoupstream.js +129 -0
- package/dist_ts/cargo/index.d.ts +1 -0
- package/dist_ts/cargo/index.js +2 -1
- package/dist_ts/classes.smartregistry.js +8 -8
- package/dist_ts/composer/classes.composerregistry.d.ts +7 -1
- package/dist_ts/composer/classes.composerregistry.js +34 -3
- package/dist_ts/composer/classes.composerupstream.d.ts +40 -0
- package/dist_ts/composer/classes.composerupstream.js +159 -0
- package/dist_ts/composer/index.d.ts +1 -0
- package/dist_ts/composer/index.js +2 -1
- package/dist_ts/core/interfaces.core.d.ts +3 -0
- package/dist_ts/index.d.ts +1 -0
- package/dist_ts/index.js +3 -1
- package/dist_ts/maven/classes.mavenregistry.d.ts +12 -1
- package/dist_ts/maven/classes.mavenregistry.js +69 -4
- package/dist_ts/maven/classes.mavenupstream.d.ts +45 -0
- package/dist_ts/maven/classes.mavenupstream.js +153 -0
- package/dist_ts/maven/index.d.ts +1 -0
- package/dist_ts/maven/index.js +2 -1
- package/dist_ts/npm/classes.npmregistry.d.ts +3 -1
- package/dist_ts/npm/classes.npmregistry.js +55 -6
- package/dist_ts/npm/classes.npmupstream.d.ts +51 -0
- package/dist_ts/npm/classes.npmupstream.js +206 -0
- package/dist_ts/npm/index.d.ts +1 -0
- package/dist_ts/npm/index.js +2 -1
- package/dist_ts/oci/classes.ociregistry.d.ts +4 -1
- package/dist_ts/oci/classes.ociregistry.js +78 -17
- package/dist_ts/oci/classes.ociupstream.d.ts +62 -0
- package/dist_ts/oci/classes.ociupstream.js +206 -0
- package/dist_ts/oci/index.d.ts +1 -0
- package/dist_ts/oci/index.js +2 -1
- package/dist_ts/plugins.d.ts +4 -1
- package/dist_ts/plugins.js +6 -2
- package/dist_ts/pypi/classes.pypiregistry.d.ts +7 -1
- package/dist_ts/pypi/classes.pypiregistry.js +60 -4
- package/dist_ts/pypi/classes.pypiupstream.d.ts +48 -0
- package/dist_ts/pypi/classes.pypiupstream.js +165 -0
- package/dist_ts/pypi/index.d.ts +1 -0
- package/dist_ts/pypi/index.js +2 -1
- package/dist_ts/rubygems/classes.rubygemsregistry.d.ts +7 -1
- package/dist_ts/rubygems/classes.rubygemsregistry.js +35 -4
- package/dist_ts/rubygems/classes.rubygemsupstream.d.ts +47 -0
- package/dist_ts/rubygems/classes.rubygemsupstream.js +184 -0
- package/dist_ts/rubygems/index.d.ts +1 -0
- package/dist_ts/rubygems/index.js +2 -1
- package/dist_ts/upstream/classes.baseupstream.d.ts +112 -0
- package/dist_ts/upstream/classes.baseupstream.js +409 -0
- package/dist_ts/upstream/classes.circuitbreaker.d.ts +111 -0
- package/dist_ts/upstream/classes.circuitbreaker.js +192 -0
- package/dist_ts/upstream/classes.upstreamcache.d.ts +123 -0
- package/dist_ts/upstream/classes.upstreamcache.js +328 -0
- package/dist_ts/upstream/index.d.ts +6 -0
- package/dist_ts/upstream/index.js +7 -0
- package/dist_ts/upstream/interfaces.upstream.d.ts +169 -0
- package/dist_ts/upstream/interfaces.upstream.js +23 -0
- package/package.json +4 -2
- package/ts/00_commitinfo_data.ts +1 -1
- package/ts/cargo/classes.cargoregistry.ts +48 -3
- package/ts/cargo/classes.cargoupstream.ts +159 -0
- package/ts/cargo/index.ts +1 -0
- package/ts/classes.smartregistry.ts +49 -7
- package/ts/composer/classes.composerregistry.ts +39 -2
- package/ts/composer/classes.composerupstream.ts +200 -0
- package/ts/composer/index.ts +1 -0
- package/ts/core/interfaces.core.ts +3 -0
- package/ts/index.ts +3 -0
- package/ts/maven/classes.mavenregistry.ts +84 -3
- package/ts/maven/classes.mavenupstream.ts +220 -0
- package/ts/maven/index.ts +1 -0
- package/ts/npm/classes.npmregistry.ts +61 -5
- package/ts/npm/classes.npmupstream.ts +260 -0
- package/ts/npm/index.ts +1 -0
- package/ts/oci/classes.ociregistry.ts +89 -17
- package/ts/oci/classes.ociupstream.ts +263 -0
- package/ts/oci/index.ts +1 -0
- package/ts/plugins.ts +7 -1
- package/ts/pypi/classes.pypiregistry.ts +68 -3
- package/ts/pypi/classes.pypiupstream.ts +211 -0
- package/ts/pypi/index.ts +1 -0
- package/ts/rubygems/classes.rubygemsregistry.ts +40 -3
- package/ts/rubygems/classes.rubygemsupstream.ts +230 -0
- package/ts/rubygems/index.ts +1 -0
- package/ts/upstream/classes.baseupstream.ts +521 -0
- package/ts/upstream/classes.circuitbreaker.ts +238 -0
- package/ts/upstream/classes.upstreamcache.ts +423 -0
- package/ts/upstream/index.ts +11 -0
- package/ts/upstream/interfaces.upstream.ts +195 -0
|
@@ -0,0 +1,200 @@
|
|
|
1
|
+
import * as plugins from '../plugins.js';
|
|
2
|
+
import { BaseUpstream } from '../upstream/classes.baseupstream.js';
|
|
3
|
+
import type {
|
|
4
|
+
IProtocolUpstreamConfig,
|
|
5
|
+
IUpstreamFetchContext,
|
|
6
|
+
IUpstreamRegistryConfig,
|
|
7
|
+
} from '../upstream/interfaces.upstream.js';
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Composer-specific upstream implementation.
|
|
11
|
+
*
|
|
12
|
+
* Handles:
|
|
13
|
+
* - Package metadata fetching (packages.json, provider-includes)
|
|
14
|
+
* - Package version metadata (p2/{vendor}/{package}.json)
|
|
15
|
+
* - Dist file (zip) proxying
|
|
16
|
+
* - Packagist v2 API support
|
|
17
|
+
*/
|
|
18
|
+
export class ComposerUpstream extends BaseUpstream {
|
|
19
|
+
protected readonly protocolName = 'composer';
|
|
20
|
+
|
|
21
|
+
constructor(
|
|
22
|
+
config: IProtocolUpstreamConfig,
|
|
23
|
+
logger?: plugins.smartlog.Smartlog,
|
|
24
|
+
) {
|
|
25
|
+
super(config, logger);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Fetch the root packages.json from upstream.
|
|
30
|
+
*/
|
|
31
|
+
public async fetchPackagesJson(): Promise<any | null> {
|
|
32
|
+
const context: IUpstreamFetchContext = {
|
|
33
|
+
protocol: 'composer',
|
|
34
|
+
resource: '*',
|
|
35
|
+
resourceType: 'root',
|
|
36
|
+
path: '/packages.json',
|
|
37
|
+
method: 'GET',
|
|
38
|
+
headers: {
|
|
39
|
+
'accept': 'application/json',
|
|
40
|
+
},
|
|
41
|
+
query: {},
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
const result = await this.fetch(context);
|
|
45
|
+
|
|
46
|
+
if (!result || !result.success) {
|
|
47
|
+
return null;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
if (Buffer.isBuffer(result.body)) {
|
|
51
|
+
return JSON.parse(result.body.toString('utf8'));
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
return result.body;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Fetch package metadata using v2 API (p2/{vendor}/{package}.json).
|
|
59
|
+
*/
|
|
60
|
+
public async fetchPackageMetadata(vendor: string, packageName: string): Promise<any | null> {
|
|
61
|
+
const fullName = `${vendor}/${packageName}`;
|
|
62
|
+
const path = `/p2/${vendor}/${packageName}.json`;
|
|
63
|
+
|
|
64
|
+
const context: IUpstreamFetchContext = {
|
|
65
|
+
protocol: 'composer',
|
|
66
|
+
resource: fullName,
|
|
67
|
+
resourceType: 'metadata',
|
|
68
|
+
path,
|
|
69
|
+
method: 'GET',
|
|
70
|
+
headers: {
|
|
71
|
+
'accept': 'application/json',
|
|
72
|
+
},
|
|
73
|
+
query: {},
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
const result = await this.fetch(context);
|
|
77
|
+
|
|
78
|
+
if (!result || !result.success) {
|
|
79
|
+
return null;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
if (Buffer.isBuffer(result.body)) {
|
|
83
|
+
return JSON.parse(result.body.toString('utf8'));
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
return result.body;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Fetch package metadata with dev versions (p2/{vendor}/{package}~dev.json).
|
|
91
|
+
*/
|
|
92
|
+
public async fetchPackageDevMetadata(vendor: string, packageName: string): Promise<any | null> {
|
|
93
|
+
const fullName = `${vendor}/${packageName}`;
|
|
94
|
+
const path = `/p2/${vendor}/${packageName}~dev.json`;
|
|
95
|
+
|
|
96
|
+
const context: IUpstreamFetchContext = {
|
|
97
|
+
protocol: 'composer',
|
|
98
|
+
resource: fullName,
|
|
99
|
+
resourceType: 'metadata-dev',
|
|
100
|
+
path,
|
|
101
|
+
method: 'GET',
|
|
102
|
+
headers: {
|
|
103
|
+
'accept': 'application/json',
|
|
104
|
+
},
|
|
105
|
+
query: {},
|
|
106
|
+
};
|
|
107
|
+
|
|
108
|
+
const result = await this.fetch(context);
|
|
109
|
+
|
|
110
|
+
if (!result || !result.success) {
|
|
111
|
+
return null;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
if (Buffer.isBuffer(result.body)) {
|
|
115
|
+
return JSON.parse(result.body.toString('utf8'));
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
return result.body;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* Fetch a provider-includes file.
|
|
123
|
+
*/
|
|
124
|
+
public async fetchProviderIncludes(path: string): Promise<any | null> {
|
|
125
|
+
const context: IUpstreamFetchContext = {
|
|
126
|
+
protocol: 'composer',
|
|
127
|
+
resource: '*',
|
|
128
|
+
resourceType: 'provider',
|
|
129
|
+
path: path.startsWith('/') ? path : `/${path}`,
|
|
130
|
+
method: 'GET',
|
|
131
|
+
headers: {
|
|
132
|
+
'accept': 'application/json',
|
|
133
|
+
},
|
|
134
|
+
query: {},
|
|
135
|
+
};
|
|
136
|
+
|
|
137
|
+
const result = await this.fetch(context);
|
|
138
|
+
|
|
139
|
+
if (!result || !result.success) {
|
|
140
|
+
return null;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
if (Buffer.isBuffer(result.body)) {
|
|
144
|
+
return JSON.parse(result.body.toString('utf8'));
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
return result.body;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
/**
|
|
151
|
+
* Fetch a dist file (zip) from upstream.
|
|
152
|
+
*/
|
|
153
|
+
public async fetchDist(url: string): Promise<Buffer | null> {
|
|
154
|
+
// Parse the URL to get the path
|
|
155
|
+
let path: string;
|
|
156
|
+
try {
|
|
157
|
+
const parsed = new URL(url);
|
|
158
|
+
path = parsed.pathname;
|
|
159
|
+
} catch {
|
|
160
|
+
path = url;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
const context: IUpstreamFetchContext = {
|
|
164
|
+
protocol: 'composer',
|
|
165
|
+
resource: '*',
|
|
166
|
+
resourceType: 'dist',
|
|
167
|
+
path,
|
|
168
|
+
method: 'GET',
|
|
169
|
+
headers: {
|
|
170
|
+
'accept': 'application/zip, application/octet-stream',
|
|
171
|
+
},
|
|
172
|
+
query: {},
|
|
173
|
+
};
|
|
174
|
+
|
|
175
|
+
const result = await this.fetch(context);
|
|
176
|
+
|
|
177
|
+
if (!result || !result.success) {
|
|
178
|
+
return null;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
return Buffer.isBuffer(result.body) ? result.body : Buffer.from(result.body);
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
/**
|
|
185
|
+
* Override URL building for Composer-specific handling.
|
|
186
|
+
*/
|
|
187
|
+
protected buildUpstreamUrl(
|
|
188
|
+
upstream: IUpstreamRegistryConfig,
|
|
189
|
+
context: IUpstreamFetchContext,
|
|
190
|
+
): string {
|
|
191
|
+
let baseUrl = upstream.url;
|
|
192
|
+
|
|
193
|
+
// Remove trailing slash
|
|
194
|
+
if (baseUrl.endsWith('/')) {
|
|
195
|
+
baseUrl = baseUrl.slice(0, -1);
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
return `${baseUrl}${context.path}`;
|
|
199
|
+
}
|
|
200
|
+
}
|
package/ts/composer/index.ts
CHANGED
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
5
|
import type * as plugins from '../plugins.js';
|
|
6
|
+
import type { IProtocolUpstreamConfig } from '../upstream/interfaces.upstream.js';
|
|
6
7
|
|
|
7
8
|
/**
|
|
8
9
|
* Registry protocol types
|
|
@@ -86,6 +87,8 @@ export interface IProtocolConfig {
|
|
|
86
87
|
enabled: boolean;
|
|
87
88
|
basePath: string;
|
|
88
89
|
features?: Record<string, boolean>;
|
|
90
|
+
/** Upstream registry configuration for proxying/caching */
|
|
91
|
+
upstream?: IProtocolUpstreamConfig;
|
|
89
92
|
}
|
|
90
93
|
|
|
91
94
|
/**
|
package/ts/index.ts
CHANGED
|
@@ -7,6 +7,7 @@ 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
9
|
import type { IRequestContext, IResponse, IAuthToken } from '../core/interfaces.core.js';
|
|
10
|
+
import type { IProtocolUpstreamConfig } from '../upstream/interfaces.upstream.js';
|
|
10
11
|
import { toBuffer } from '../core/helpers.buffer.js';
|
|
11
12
|
import type { IMavenCoordinate, IMavenMetadata, IChecksums } from './interfaces.maven.js';
|
|
12
13
|
import {
|
|
@@ -21,6 +22,7 @@ import {
|
|
|
21
22
|
extractGAVFromPom,
|
|
22
23
|
gavToPath,
|
|
23
24
|
} from './helpers.maven.js';
|
|
25
|
+
import { MavenUpstream } from './classes.mavenupstream.js';
|
|
24
26
|
|
|
25
27
|
/**
|
|
26
28
|
* Maven Registry class
|
|
@@ -31,18 +33,34 @@ export class MavenRegistry extends BaseRegistry {
|
|
|
31
33
|
private authManager: AuthManager;
|
|
32
34
|
private basePath: string = '/maven';
|
|
33
35
|
private registryUrl: string;
|
|
36
|
+
private upstream: MavenUpstream | null = null;
|
|
34
37
|
|
|
35
38
|
constructor(
|
|
36
39
|
storage: RegistryStorage,
|
|
37
40
|
authManager: AuthManager,
|
|
38
41
|
basePath: string,
|
|
39
|
-
registryUrl: string
|
|
42
|
+
registryUrl: string,
|
|
43
|
+
upstreamConfig?: IProtocolUpstreamConfig
|
|
40
44
|
) {
|
|
41
45
|
super();
|
|
42
46
|
this.storage = storage;
|
|
43
47
|
this.authManager = authManager;
|
|
44
48
|
this.basePath = basePath;
|
|
45
49
|
this.registryUrl = registryUrl;
|
|
50
|
+
|
|
51
|
+
// Initialize upstream if configured
|
|
52
|
+
if (upstreamConfig?.enabled) {
|
|
53
|
+
this.upstream = new MavenUpstream(upstreamConfig);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Clean up resources (timers, connections, etc.)
|
|
59
|
+
*/
|
|
60
|
+
public destroy(): void {
|
|
61
|
+
if (this.upstream) {
|
|
62
|
+
this.upstream.stop();
|
|
63
|
+
}
|
|
46
64
|
}
|
|
47
65
|
|
|
48
66
|
public async init(): Promise<void> {
|
|
@@ -234,7 +252,23 @@ export class MavenRegistry extends BaseRegistry {
|
|
|
234
252
|
version: string,
|
|
235
253
|
filename: string
|
|
236
254
|
): Promise<IResponse> {
|
|
237
|
-
|
|
255
|
+
let data = await this.storage.getMavenArtifact(groupId, artifactId, version, filename);
|
|
256
|
+
|
|
257
|
+
// Try upstream if not found locally
|
|
258
|
+
if (!data && this.upstream) {
|
|
259
|
+
// Parse the filename to extract extension and classifier
|
|
260
|
+
const { extension, classifier } = this.parseFilename(filename, artifactId, version);
|
|
261
|
+
if (extension) {
|
|
262
|
+
data = await this.upstream.fetchArtifact(groupId, artifactId, version, extension, classifier);
|
|
263
|
+
if (data) {
|
|
264
|
+
// Cache the artifact locally
|
|
265
|
+
await this.storage.putMavenArtifact(groupId, artifactId, version, filename, data);
|
|
266
|
+
// Generate and store checksums
|
|
267
|
+
const checksums = await calculateChecksums(data);
|
|
268
|
+
await this.storeChecksums(groupId, artifactId, version, filename, checksums);
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
}
|
|
238
272
|
|
|
239
273
|
if (!data) {
|
|
240
274
|
return {
|
|
@@ -462,7 +496,17 @@ export class MavenRegistry extends BaseRegistry {
|
|
|
462
496
|
// ========================================================================
|
|
463
497
|
|
|
464
498
|
private async getMetadata(groupId: string, artifactId: string): Promise<IResponse> {
|
|
465
|
-
|
|
499
|
+
let metadataBuffer = await this.storage.getMavenMetadata(groupId, artifactId);
|
|
500
|
+
|
|
501
|
+
// Try upstream if not found locally
|
|
502
|
+
if (!metadataBuffer && this.upstream) {
|
|
503
|
+
const upstreamMetadata = await this.upstream.fetchMetadata(groupId, artifactId);
|
|
504
|
+
if (upstreamMetadata) {
|
|
505
|
+
metadataBuffer = Buffer.from(upstreamMetadata, 'utf-8');
|
|
506
|
+
// Cache the metadata locally
|
|
507
|
+
await this.storage.putMavenMetadata(groupId, artifactId, metadataBuffer);
|
|
508
|
+
}
|
|
509
|
+
}
|
|
466
510
|
|
|
467
511
|
if (!metadataBuffer) {
|
|
468
512
|
// Generate empty metadata if none exists
|
|
@@ -578,4 +622,41 @@ export class MavenRegistry extends BaseRegistry {
|
|
|
578
622
|
|
|
579
623
|
return contentTypes[extension] || 'application/octet-stream';
|
|
580
624
|
}
|
|
625
|
+
|
|
626
|
+
/**
|
|
627
|
+
* Parse a Maven filename to extract extension and classifier.
|
|
628
|
+
* Filename format: {artifactId}-{version}[-{classifier}].{extension}
|
|
629
|
+
*/
|
|
630
|
+
private parseFilename(
|
|
631
|
+
filename: string,
|
|
632
|
+
artifactId: string,
|
|
633
|
+
version: string
|
|
634
|
+
): { extension: string; classifier?: string } {
|
|
635
|
+
const prefix = `${artifactId}-${version}`;
|
|
636
|
+
|
|
637
|
+
if (!filename.startsWith(prefix)) {
|
|
638
|
+
// Fallback: just get the extension
|
|
639
|
+
const lastDot = filename.lastIndexOf('.');
|
|
640
|
+
return { extension: lastDot > 0 ? filename.slice(lastDot + 1) : '' };
|
|
641
|
+
}
|
|
642
|
+
|
|
643
|
+
const remainder = filename.slice(prefix.length);
|
|
644
|
+
// remainder is either ".extension" or "-classifier.extension"
|
|
645
|
+
|
|
646
|
+
if (remainder.startsWith('.')) {
|
|
647
|
+
return { extension: remainder.slice(1) };
|
|
648
|
+
}
|
|
649
|
+
|
|
650
|
+
if (remainder.startsWith('-')) {
|
|
651
|
+
const lastDot = remainder.lastIndexOf('.');
|
|
652
|
+
if (lastDot > 1) {
|
|
653
|
+
return {
|
|
654
|
+
classifier: remainder.slice(1, lastDot),
|
|
655
|
+
extension: remainder.slice(lastDot + 1),
|
|
656
|
+
};
|
|
657
|
+
}
|
|
658
|
+
}
|
|
659
|
+
|
|
660
|
+
return { extension: '' };
|
|
661
|
+
}
|
|
581
662
|
}
|
|
@@ -0,0 +1,220 @@
|
|
|
1
|
+
import * as plugins from '../plugins.js';
|
|
2
|
+
import { BaseUpstream } from '../upstream/classes.baseupstream.js';
|
|
3
|
+
import type {
|
|
4
|
+
IProtocolUpstreamConfig,
|
|
5
|
+
IUpstreamFetchContext,
|
|
6
|
+
IUpstreamRegistryConfig,
|
|
7
|
+
} from '../upstream/interfaces.upstream.js';
|
|
8
|
+
import type { IMavenCoordinate } from './interfaces.maven.js';
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Maven-specific upstream implementation.
|
|
12
|
+
*
|
|
13
|
+
* Handles:
|
|
14
|
+
* - Artifact fetching (JAR, POM, WAR, etc.)
|
|
15
|
+
* - Metadata fetching (maven-metadata.xml)
|
|
16
|
+
* - Checksum files (.md5, .sha1, .sha256, .sha512)
|
|
17
|
+
* - SNAPSHOT version handling
|
|
18
|
+
* - Content-addressable caching for release artifacts
|
|
19
|
+
*/
|
|
20
|
+
export class MavenUpstream extends BaseUpstream {
|
|
21
|
+
protected readonly protocolName = 'maven';
|
|
22
|
+
|
|
23
|
+
constructor(
|
|
24
|
+
config: IProtocolUpstreamConfig,
|
|
25
|
+
logger?: plugins.smartlog.Smartlog,
|
|
26
|
+
) {
|
|
27
|
+
super(config, logger);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Fetch an artifact from upstream registries.
|
|
32
|
+
*/
|
|
33
|
+
public async fetchArtifact(
|
|
34
|
+
groupId: string,
|
|
35
|
+
artifactId: string,
|
|
36
|
+
version: string,
|
|
37
|
+
extension: string,
|
|
38
|
+
classifier?: string,
|
|
39
|
+
): Promise<Buffer | null> {
|
|
40
|
+
const path = this.buildArtifactPath(groupId, artifactId, version, extension, classifier);
|
|
41
|
+
const resource = `${groupId}:${artifactId}`;
|
|
42
|
+
|
|
43
|
+
const context: IUpstreamFetchContext = {
|
|
44
|
+
protocol: 'maven',
|
|
45
|
+
resource,
|
|
46
|
+
resourceType: 'artifact',
|
|
47
|
+
path,
|
|
48
|
+
method: 'GET',
|
|
49
|
+
headers: {},
|
|
50
|
+
query: {},
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
const result = await this.fetch(context);
|
|
54
|
+
|
|
55
|
+
if (!result || !result.success) {
|
|
56
|
+
return null;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
return Buffer.isBuffer(result.body) ? result.body : Buffer.from(result.body);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Fetch maven-metadata.xml from upstream.
|
|
64
|
+
*/
|
|
65
|
+
public async fetchMetadata(groupId: string, artifactId: string, version?: string): Promise<string | null> {
|
|
66
|
+
const groupPath = groupId.replace(/\./g, '/');
|
|
67
|
+
let path: string;
|
|
68
|
+
|
|
69
|
+
if (version) {
|
|
70
|
+
// Version-level metadata (for SNAPSHOTs)
|
|
71
|
+
path = `/${groupPath}/${artifactId}/${version}/maven-metadata.xml`;
|
|
72
|
+
} else {
|
|
73
|
+
// Artifact-level metadata (lists all versions)
|
|
74
|
+
path = `/${groupPath}/${artifactId}/maven-metadata.xml`;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
const resource = `${groupId}:${artifactId}`;
|
|
78
|
+
|
|
79
|
+
const context: IUpstreamFetchContext = {
|
|
80
|
+
protocol: 'maven',
|
|
81
|
+
resource,
|
|
82
|
+
resourceType: 'metadata',
|
|
83
|
+
path,
|
|
84
|
+
method: 'GET',
|
|
85
|
+
headers: {
|
|
86
|
+
'accept': 'application/xml, text/xml',
|
|
87
|
+
},
|
|
88
|
+
query: {},
|
|
89
|
+
};
|
|
90
|
+
|
|
91
|
+
const result = await this.fetch(context);
|
|
92
|
+
|
|
93
|
+
if (!result || !result.success) {
|
|
94
|
+
return null;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
if (Buffer.isBuffer(result.body)) {
|
|
98
|
+
return result.body.toString('utf8');
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
return typeof result.body === 'string' ? result.body : null;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* Fetch a checksum file from upstream.
|
|
106
|
+
*/
|
|
107
|
+
public async fetchChecksum(
|
|
108
|
+
groupId: string,
|
|
109
|
+
artifactId: string,
|
|
110
|
+
version: string,
|
|
111
|
+
extension: string,
|
|
112
|
+
checksumType: 'md5' | 'sha1' | 'sha256' | 'sha512',
|
|
113
|
+
classifier?: string,
|
|
114
|
+
): Promise<string | null> {
|
|
115
|
+
const basePath = this.buildArtifactPath(groupId, artifactId, version, extension, classifier);
|
|
116
|
+
const path = `${basePath}.${checksumType}`;
|
|
117
|
+
const resource = `${groupId}:${artifactId}`;
|
|
118
|
+
|
|
119
|
+
const context: IUpstreamFetchContext = {
|
|
120
|
+
protocol: 'maven',
|
|
121
|
+
resource,
|
|
122
|
+
resourceType: 'checksum',
|
|
123
|
+
path,
|
|
124
|
+
method: 'GET',
|
|
125
|
+
headers: {
|
|
126
|
+
'accept': 'text/plain',
|
|
127
|
+
},
|
|
128
|
+
query: {},
|
|
129
|
+
};
|
|
130
|
+
|
|
131
|
+
const result = await this.fetch(context);
|
|
132
|
+
|
|
133
|
+
if (!result || !result.success) {
|
|
134
|
+
return null;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
if (Buffer.isBuffer(result.body)) {
|
|
138
|
+
return result.body.toString('utf8').trim();
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
return typeof result.body === 'string' ? result.body.trim() : null;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
/**
|
|
145
|
+
* Check if an artifact exists in upstream (HEAD request).
|
|
146
|
+
*/
|
|
147
|
+
public async headArtifact(
|
|
148
|
+
groupId: string,
|
|
149
|
+
artifactId: string,
|
|
150
|
+
version: string,
|
|
151
|
+
extension: string,
|
|
152
|
+
classifier?: string,
|
|
153
|
+
): Promise<{ exists: boolean; size?: number; lastModified?: string } | null> {
|
|
154
|
+
const path = this.buildArtifactPath(groupId, artifactId, version, extension, classifier);
|
|
155
|
+
const resource = `${groupId}:${artifactId}`;
|
|
156
|
+
|
|
157
|
+
const context: IUpstreamFetchContext = {
|
|
158
|
+
protocol: 'maven',
|
|
159
|
+
resource,
|
|
160
|
+
resourceType: 'artifact',
|
|
161
|
+
path,
|
|
162
|
+
method: 'HEAD',
|
|
163
|
+
headers: {},
|
|
164
|
+
query: {},
|
|
165
|
+
};
|
|
166
|
+
|
|
167
|
+
const result = await this.fetch(context);
|
|
168
|
+
|
|
169
|
+
if (!result) {
|
|
170
|
+
return null;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
if (!result.success) {
|
|
174
|
+
return { exists: false };
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
return {
|
|
178
|
+
exists: true,
|
|
179
|
+
size: result.headers['content-length'] ? parseInt(result.headers['content-length'], 10) : undefined,
|
|
180
|
+
lastModified: result.headers['last-modified'],
|
|
181
|
+
};
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
/**
|
|
185
|
+
* Build the path for a Maven artifact.
|
|
186
|
+
*/
|
|
187
|
+
private buildArtifactPath(
|
|
188
|
+
groupId: string,
|
|
189
|
+
artifactId: string,
|
|
190
|
+
version: string,
|
|
191
|
+
extension: string,
|
|
192
|
+
classifier?: string,
|
|
193
|
+
): string {
|
|
194
|
+
const groupPath = groupId.replace(/\./g, '/');
|
|
195
|
+
let filename = `${artifactId}-${version}`;
|
|
196
|
+
if (classifier) {
|
|
197
|
+
filename += `-${classifier}`;
|
|
198
|
+
}
|
|
199
|
+
filename += `.${extension}`;
|
|
200
|
+
|
|
201
|
+
return `/${groupPath}/${artifactId}/${version}/${filename}`;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
/**
|
|
205
|
+
* Override URL building for Maven-specific handling.
|
|
206
|
+
*/
|
|
207
|
+
protected buildUpstreamUrl(
|
|
208
|
+
upstream: IUpstreamRegistryConfig,
|
|
209
|
+
context: IUpstreamFetchContext,
|
|
210
|
+
): string {
|
|
211
|
+
let baseUrl = upstream.url;
|
|
212
|
+
|
|
213
|
+
// Remove trailing slash
|
|
214
|
+
if (baseUrl.endsWith('/')) {
|
|
215
|
+
baseUrl = baseUrl.slice(0, -1);
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
return `${baseUrl}${context.path}`;
|
|
219
|
+
}
|
|
220
|
+
}
|