@push.rocks/smartregistry 1.1.1 → 1.4.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 +79 -0
- package/dist_ts/cargo/classes.cargoregistry.js +490 -0
- package/dist_ts/cargo/index.d.ts +5 -0
- package/dist_ts/cargo/index.js +6 -0
- package/dist_ts/cargo/interfaces.cargo.d.ts +160 -0
- package/dist_ts/cargo/interfaces.cargo.js +6 -0
- package/dist_ts/classes.smartregistry.d.ts +2 -2
- package/dist_ts/classes.smartregistry.js +50 -2
- package/dist_ts/composer/classes.composerregistry.d.ts +26 -0
- package/dist_ts/composer/classes.composerregistry.js +366 -0
- package/dist_ts/composer/helpers.composer.d.ts +35 -0
- package/dist_ts/composer/helpers.composer.js +120 -0
- package/dist_ts/composer/index.d.ts +7 -0
- package/dist_ts/composer/index.js +8 -0
- package/dist_ts/composer/interfaces.composer.d.ts +102 -0
- package/dist_ts/composer/interfaces.composer.js +6 -0
- package/dist_ts/core/classes.authmanager.d.ts +46 -1
- package/dist_ts/core/classes.authmanager.js +121 -12
- package/dist_ts/core/classes.registrystorage.d.ts +103 -0
- package/dist_ts/core/classes.registrystorage.js +253 -1
- package/dist_ts/core/interfaces.core.d.ts +4 -1
- package/dist_ts/index.d.ts +4 -1
- package/dist_ts/index.js +8 -2
- package/dist_ts/maven/classes.mavenregistry.d.ts +35 -0
- package/dist_ts/maven/classes.mavenregistry.js +407 -0
- package/dist_ts/maven/helpers.maven.d.ts +68 -0
- package/dist_ts/maven/helpers.maven.js +286 -0
- package/dist_ts/maven/index.d.ts +6 -0
- package/dist_ts/maven/index.js +7 -0
- package/dist_ts/maven/interfaces.maven.d.ts +116 -0
- package/dist_ts/maven/interfaces.maven.js +6 -0
- package/package.json +3 -2
- package/readme.md +288 -14
- package/ts/00_commitinfo_data.ts +1 -1
- package/ts/cargo/classes.cargoregistry.ts +604 -0
- package/ts/cargo/index.ts +6 -0
- package/ts/cargo/interfaces.cargo.ts +169 -0
- package/ts/classes.smartregistry.ts +56 -2
- package/ts/composer/classes.composerregistry.ts +475 -0
- package/ts/composer/helpers.composer.ts +139 -0
- package/ts/composer/index.ts +8 -0
- package/ts/composer/interfaces.composer.ts +111 -0
- package/ts/core/classes.authmanager.ts +145 -12
- package/ts/core/classes.registrystorage.ts +334 -0
- package/ts/core/interfaces.core.ts +4 -1
- package/ts/index.ts +10 -1
- package/ts/maven/classes.mavenregistry.ts +580 -0
- package/ts/maven/helpers.maven.ts +346 -0
- package/ts/maven/index.ts +7 -0
- package/ts/maven/interfaces.maven.ts +127 -0
|
@@ -0,0 +1,580 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Maven Registry Implementation
|
|
3
|
+
* Implements Maven repository protocol for Java artifacts
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { BaseRegistry } from '../core/classes.baseregistry.js';
|
|
7
|
+
import type { RegistryStorage } from '../core/classes.registrystorage.js';
|
|
8
|
+
import type { AuthManager } from '../core/classes.authmanager.js';
|
|
9
|
+
import type { IRequestContext, IResponse, IAuthToken } from '../core/interfaces.core.js';
|
|
10
|
+
import type { IMavenCoordinate, IMavenMetadata, IChecksums } from './interfaces.maven.js';
|
|
11
|
+
import {
|
|
12
|
+
pathToGAV,
|
|
13
|
+
buildFilename,
|
|
14
|
+
calculateChecksums,
|
|
15
|
+
generateMetadataXml,
|
|
16
|
+
parseMetadataXml,
|
|
17
|
+
formatMavenTimestamp,
|
|
18
|
+
isSnapshot,
|
|
19
|
+
validatePom,
|
|
20
|
+
extractGAVFromPom,
|
|
21
|
+
gavToPath,
|
|
22
|
+
} from './helpers.maven.js';
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Maven Registry class
|
|
26
|
+
* Handles Maven repository HTTP protocol
|
|
27
|
+
*/
|
|
28
|
+
export class MavenRegistry extends BaseRegistry {
|
|
29
|
+
private storage: RegistryStorage;
|
|
30
|
+
private authManager: AuthManager;
|
|
31
|
+
private basePath: string = '/maven';
|
|
32
|
+
private registryUrl: string;
|
|
33
|
+
|
|
34
|
+
constructor(
|
|
35
|
+
storage: RegistryStorage,
|
|
36
|
+
authManager: AuthManager,
|
|
37
|
+
basePath: string,
|
|
38
|
+
registryUrl: string
|
|
39
|
+
) {
|
|
40
|
+
super();
|
|
41
|
+
this.storage = storage;
|
|
42
|
+
this.authManager = authManager;
|
|
43
|
+
this.basePath = basePath;
|
|
44
|
+
this.registryUrl = registryUrl;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
public async init(): Promise<void> {
|
|
48
|
+
// No special initialization needed for Maven
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
public getBasePath(): string {
|
|
52
|
+
return this.basePath;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
public async handleRequest(context: IRequestContext): Promise<IResponse> {
|
|
56
|
+
// Remove base path from URL
|
|
57
|
+
const path = context.path.replace(this.basePath, '');
|
|
58
|
+
|
|
59
|
+
// Extract token from Authorization header
|
|
60
|
+
const authHeader = context.headers['authorization'] || context.headers['Authorization'];
|
|
61
|
+
let token: IAuthToken | null = null;
|
|
62
|
+
|
|
63
|
+
if (authHeader) {
|
|
64
|
+
const tokenString = authHeader.replace(/^(Bearer|Basic)\s+/i, '');
|
|
65
|
+
// For now, try to validate as Maven token (reuse npm token type)
|
|
66
|
+
token = await this.authManager.validateToken(tokenString, 'maven');
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// Parse path to determine request type
|
|
70
|
+
const coordinate = pathToGAV(path);
|
|
71
|
+
|
|
72
|
+
if (!coordinate) {
|
|
73
|
+
// Not a valid artifact path, could be metadata or root
|
|
74
|
+
if (path.endsWith('/maven-metadata.xml')) {
|
|
75
|
+
return this.handleMetadataRequest(context.method, path, token);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
return {
|
|
79
|
+
status: 404,
|
|
80
|
+
headers: { 'Content-Type': 'application/json' },
|
|
81
|
+
body: { error: 'NOT_FOUND', message: 'Invalid Maven path' },
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// Check if it's a checksum file
|
|
86
|
+
if (coordinate.extension === 'md5' || coordinate.extension === 'sha1' ||
|
|
87
|
+
coordinate.extension === 'sha256' || coordinate.extension === 'sha512') {
|
|
88
|
+
return this.handleChecksumRequest(context.method, coordinate, token, path);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
// Handle artifact requests (JAR, POM, WAR, etc.)
|
|
92
|
+
return this.handleArtifactRequest(context.method, coordinate, token, context.body);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
protected async checkPermission(
|
|
96
|
+
token: IAuthToken | null,
|
|
97
|
+
resource: string,
|
|
98
|
+
action: string
|
|
99
|
+
): Promise<boolean> {
|
|
100
|
+
if (!token) return false;
|
|
101
|
+
return this.authManager.authorize(token, `maven:artifact:${resource}`, action);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
// ========================================================================
|
|
105
|
+
// REQUEST HANDLERS
|
|
106
|
+
// ========================================================================
|
|
107
|
+
|
|
108
|
+
private async handleArtifactRequest(
|
|
109
|
+
method: string,
|
|
110
|
+
coordinate: IMavenCoordinate,
|
|
111
|
+
token: IAuthToken | null,
|
|
112
|
+
body?: Buffer | any
|
|
113
|
+
): Promise<IResponse> {
|
|
114
|
+
const { groupId, artifactId, version } = coordinate;
|
|
115
|
+
const filename = buildFilename(coordinate);
|
|
116
|
+
const resource = `${groupId}:${artifactId}`;
|
|
117
|
+
|
|
118
|
+
switch (method) {
|
|
119
|
+
case 'GET':
|
|
120
|
+
case 'HEAD':
|
|
121
|
+
// Maven repositories typically allow anonymous reads
|
|
122
|
+
return method === 'GET'
|
|
123
|
+
? this.getArtifact(groupId, artifactId, version, filename)
|
|
124
|
+
: this.headArtifact(groupId, artifactId, version, filename);
|
|
125
|
+
|
|
126
|
+
case 'PUT':
|
|
127
|
+
// Write permission required
|
|
128
|
+
if (!await this.checkPermission(token, resource, 'write')) {
|
|
129
|
+
return {
|
|
130
|
+
status: 401,
|
|
131
|
+
headers: {
|
|
132
|
+
'WWW-Authenticate': `Bearer realm="${this.basePath}",service="maven-registry"`,
|
|
133
|
+
},
|
|
134
|
+
body: { error: 'UNAUTHORIZED', message: 'Write permission required' },
|
|
135
|
+
};
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
if (!body) {
|
|
139
|
+
return {
|
|
140
|
+
status: 400,
|
|
141
|
+
headers: {},
|
|
142
|
+
body: { error: 'BAD_REQUEST', message: 'Request body required' },
|
|
143
|
+
};
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
return this.putArtifact(groupId, artifactId, version, filename, coordinate, body);
|
|
147
|
+
|
|
148
|
+
case 'DELETE':
|
|
149
|
+
// Delete permission required
|
|
150
|
+
if (!await this.checkPermission(token, resource, 'delete')) {
|
|
151
|
+
return {
|
|
152
|
+
status: 401,
|
|
153
|
+
headers: {
|
|
154
|
+
'WWW-Authenticate': `Bearer realm="${this.basePath}",service="maven-registry"`,
|
|
155
|
+
},
|
|
156
|
+
body: { error: 'UNAUTHORIZED', message: 'Delete permission required' },
|
|
157
|
+
};
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
return this.deleteArtifact(groupId, artifactId, version, filename);
|
|
161
|
+
|
|
162
|
+
default:
|
|
163
|
+
return {
|
|
164
|
+
status: 405,
|
|
165
|
+
headers: { 'Allow': 'GET, HEAD, PUT, DELETE' },
|
|
166
|
+
body: { error: 'METHOD_NOT_ALLOWED', message: 'Method not allowed' },
|
|
167
|
+
};
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
private async handleChecksumRequest(
|
|
172
|
+
method: string,
|
|
173
|
+
coordinate: IMavenCoordinate,
|
|
174
|
+
token: IAuthToken | null,
|
|
175
|
+
path: string
|
|
176
|
+
): Promise<IResponse> {
|
|
177
|
+
const { groupId, artifactId, version, extension } = coordinate;
|
|
178
|
+
const resource = `${groupId}:${artifactId}`;
|
|
179
|
+
|
|
180
|
+
// Checksums follow the same permissions as their artifacts (public read)
|
|
181
|
+
if (method === 'GET' || method === 'HEAD') {
|
|
182
|
+
return this.getChecksum(groupId, artifactId, version, coordinate, path);
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
return {
|
|
186
|
+
status: 405,
|
|
187
|
+
headers: { 'Allow': 'GET, HEAD' },
|
|
188
|
+
body: { error: 'METHOD_NOT_ALLOWED', message: 'Checksums are auto-generated' },
|
|
189
|
+
};
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
private async handleMetadataRequest(
|
|
193
|
+
method: string,
|
|
194
|
+
path: string,
|
|
195
|
+
token: IAuthToken | null
|
|
196
|
+
): Promise<IResponse> {
|
|
197
|
+
// Parse path to extract groupId and artifactId
|
|
198
|
+
// Path format: /com/example/my-lib/maven-metadata.xml
|
|
199
|
+
const parts = path.split('/').filter(p => p && p !== 'maven-metadata.xml');
|
|
200
|
+
|
|
201
|
+
if (parts.length < 2) {
|
|
202
|
+
return {
|
|
203
|
+
status: 400,
|
|
204
|
+
headers: {},
|
|
205
|
+
body: { error: 'BAD_REQUEST', message: 'Invalid metadata path' },
|
|
206
|
+
};
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
const artifactId = parts[parts.length - 1];
|
|
210
|
+
const groupId = parts.slice(0, -1).join('.');
|
|
211
|
+
const resource = `${groupId}:${artifactId}`;
|
|
212
|
+
|
|
213
|
+
if (method === 'GET') {
|
|
214
|
+
// Metadata is usually public (read permission optional)
|
|
215
|
+
// Some registries allow anonymous metadata access
|
|
216
|
+
return this.getMetadata(groupId, artifactId);
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
return {
|
|
220
|
+
status: 405,
|
|
221
|
+
headers: { 'Allow': 'GET' },
|
|
222
|
+
body: { error: 'METHOD_NOT_ALLOWED', message: 'Metadata is auto-generated' },
|
|
223
|
+
};
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
// ========================================================================
|
|
227
|
+
// ARTIFACT OPERATIONS
|
|
228
|
+
// ========================================================================
|
|
229
|
+
|
|
230
|
+
private async getArtifact(
|
|
231
|
+
groupId: string,
|
|
232
|
+
artifactId: string,
|
|
233
|
+
version: string,
|
|
234
|
+
filename: string
|
|
235
|
+
): Promise<IResponse> {
|
|
236
|
+
const data = await this.storage.getMavenArtifact(groupId, artifactId, version, filename);
|
|
237
|
+
|
|
238
|
+
if (!data) {
|
|
239
|
+
return {
|
|
240
|
+
status: 404,
|
|
241
|
+
headers: {},
|
|
242
|
+
body: { error: 'NOT_FOUND', message: 'Artifact not found' },
|
|
243
|
+
};
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
// Determine content type based on extension
|
|
247
|
+
const extension = filename.split('.').pop() || '';
|
|
248
|
+
const contentType = this.getContentType(extension);
|
|
249
|
+
|
|
250
|
+
return {
|
|
251
|
+
status: 200,
|
|
252
|
+
headers: {
|
|
253
|
+
'Content-Type': contentType,
|
|
254
|
+
'Content-Length': data.length.toString(),
|
|
255
|
+
},
|
|
256
|
+
body: data,
|
|
257
|
+
};
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
private async headArtifact(
|
|
261
|
+
groupId: string,
|
|
262
|
+
artifactId: string,
|
|
263
|
+
version: string,
|
|
264
|
+
filename: string
|
|
265
|
+
): Promise<IResponse> {
|
|
266
|
+
const exists = await this.storage.mavenArtifactExists(groupId, artifactId, version, filename);
|
|
267
|
+
|
|
268
|
+
if (!exists) {
|
|
269
|
+
return {
|
|
270
|
+
status: 404,
|
|
271
|
+
headers: {},
|
|
272
|
+
body: null,
|
|
273
|
+
};
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
// Get file size for Content-Length header
|
|
277
|
+
const data = await this.storage.getMavenArtifact(groupId, artifactId, version, filename);
|
|
278
|
+
const extension = filename.split('.').pop() || '';
|
|
279
|
+
const contentType = this.getContentType(extension);
|
|
280
|
+
|
|
281
|
+
return {
|
|
282
|
+
status: 200,
|
|
283
|
+
headers: {
|
|
284
|
+
'Content-Type': contentType,
|
|
285
|
+
'Content-Length': data ? data.length.toString() : '0',
|
|
286
|
+
},
|
|
287
|
+
body: null,
|
|
288
|
+
};
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
private async putArtifact(
|
|
292
|
+
groupId: string,
|
|
293
|
+
artifactId: string,
|
|
294
|
+
version: string,
|
|
295
|
+
filename: string,
|
|
296
|
+
coordinate: IMavenCoordinate,
|
|
297
|
+
body: Buffer | any
|
|
298
|
+
): Promise<IResponse> {
|
|
299
|
+
const data = Buffer.isBuffer(body) ? body : Buffer.from(JSON.stringify(body));
|
|
300
|
+
|
|
301
|
+
// Validate POM if uploading .pom file
|
|
302
|
+
if (coordinate.extension === 'pom') {
|
|
303
|
+
const pomValid = validatePom(data.toString('utf-8'));
|
|
304
|
+
if (!pomValid) {
|
|
305
|
+
return {
|
|
306
|
+
status: 400,
|
|
307
|
+
headers: {},
|
|
308
|
+
body: { error: 'INVALID_POM', message: 'Invalid POM file' },
|
|
309
|
+
};
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
// Verify GAV matches path
|
|
313
|
+
const pomGAV = extractGAVFromPom(data.toString('utf-8'));
|
|
314
|
+
if (pomGAV && (pomGAV.groupId !== groupId || pomGAV.artifactId !== artifactId || pomGAV.version !== version)) {
|
|
315
|
+
return {
|
|
316
|
+
status: 400,
|
|
317
|
+
headers: {},
|
|
318
|
+
body: { error: 'GAV_MISMATCH', message: 'POM coordinates do not match upload path' },
|
|
319
|
+
};
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
// Store the artifact
|
|
324
|
+
await this.storage.putMavenArtifact(groupId, artifactId, version, filename, data);
|
|
325
|
+
|
|
326
|
+
// Generate and store checksums
|
|
327
|
+
const checksums = await calculateChecksums(data);
|
|
328
|
+
await this.storeChecksums(groupId, artifactId, version, filename, checksums);
|
|
329
|
+
|
|
330
|
+
// Update maven-metadata.xml if this is a primary artifact (jar, pom, war)
|
|
331
|
+
if (['jar', 'pom', 'war', 'ear', 'aar'].includes(coordinate.extension)) {
|
|
332
|
+
await this.updateMetadata(groupId, artifactId, version);
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
return {
|
|
336
|
+
status: 201,
|
|
337
|
+
headers: {
|
|
338
|
+
'Location': `${this.registryUrl}/${gavToPath(groupId, artifactId, version)}/${filename}`,
|
|
339
|
+
},
|
|
340
|
+
body: { success: true, message: 'Artifact uploaded successfully' },
|
|
341
|
+
};
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
private async deleteArtifact(
|
|
345
|
+
groupId: string,
|
|
346
|
+
artifactId: string,
|
|
347
|
+
version: string,
|
|
348
|
+
filename: string
|
|
349
|
+
): Promise<IResponse> {
|
|
350
|
+
const exists = await this.storage.mavenArtifactExists(groupId, artifactId, version, filename);
|
|
351
|
+
|
|
352
|
+
if (!exists) {
|
|
353
|
+
return {
|
|
354
|
+
status: 404,
|
|
355
|
+
headers: {},
|
|
356
|
+
body: { error: 'NOT_FOUND', message: 'Artifact not found' },
|
|
357
|
+
};
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
await this.storage.deleteMavenArtifact(groupId, artifactId, version, filename);
|
|
361
|
+
|
|
362
|
+
// Also delete checksums
|
|
363
|
+
for (const ext of ['md5', 'sha1', 'sha256', 'sha512']) {
|
|
364
|
+
const checksumFile = `${filename}.${ext}`;
|
|
365
|
+
const checksumExists = await this.storage.mavenArtifactExists(groupId, artifactId, version, checksumFile);
|
|
366
|
+
if (checksumExists) {
|
|
367
|
+
await this.storage.deleteMavenArtifact(groupId, artifactId, version, checksumFile);
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
return {
|
|
372
|
+
status: 204,
|
|
373
|
+
headers: {},
|
|
374
|
+
body: null,
|
|
375
|
+
};
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
// ========================================================================
|
|
379
|
+
// CHECKSUM OPERATIONS
|
|
380
|
+
// ========================================================================
|
|
381
|
+
|
|
382
|
+
private async getChecksum(
|
|
383
|
+
groupId: string,
|
|
384
|
+
artifactId: string,
|
|
385
|
+
version: string,
|
|
386
|
+
coordinate: IMavenCoordinate,
|
|
387
|
+
fullPath: string
|
|
388
|
+
): Promise<IResponse> {
|
|
389
|
+
// Extract the filename from the full path (last component)
|
|
390
|
+
// The fullPath might be something like /com/example/test/test-artifact/1.0.0/test-artifact-1.0.0.jar.md5
|
|
391
|
+
const pathParts = fullPath.split('/');
|
|
392
|
+
const checksumFilename = pathParts[pathParts.length - 1];
|
|
393
|
+
|
|
394
|
+
const data = await this.storage.getMavenArtifact(groupId, artifactId, version, checksumFilename);
|
|
395
|
+
|
|
396
|
+
if (!data) {
|
|
397
|
+
return {
|
|
398
|
+
status: 404,
|
|
399
|
+
headers: {},
|
|
400
|
+
body: { error: 'NOT_FOUND', message: 'Checksum not found' },
|
|
401
|
+
};
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
return {
|
|
405
|
+
status: 200,
|
|
406
|
+
headers: {
|
|
407
|
+
'Content-Type': 'text/plain',
|
|
408
|
+
'Content-Length': data.length.toString(),
|
|
409
|
+
},
|
|
410
|
+
body: data,
|
|
411
|
+
};
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
private async storeChecksums(
|
|
415
|
+
groupId: string,
|
|
416
|
+
artifactId: string,
|
|
417
|
+
version: string,
|
|
418
|
+
filename: string,
|
|
419
|
+
checksums: IChecksums
|
|
420
|
+
): Promise<void> {
|
|
421
|
+
// Store each checksum as a separate file
|
|
422
|
+
await this.storage.putMavenArtifact(
|
|
423
|
+
groupId,
|
|
424
|
+
artifactId,
|
|
425
|
+
version,
|
|
426
|
+
`${filename}.md5`,
|
|
427
|
+
Buffer.from(checksums.md5, 'utf-8')
|
|
428
|
+
);
|
|
429
|
+
|
|
430
|
+
await this.storage.putMavenArtifact(
|
|
431
|
+
groupId,
|
|
432
|
+
artifactId,
|
|
433
|
+
version,
|
|
434
|
+
`${filename}.sha1`,
|
|
435
|
+
Buffer.from(checksums.sha1, 'utf-8')
|
|
436
|
+
);
|
|
437
|
+
|
|
438
|
+
if (checksums.sha256) {
|
|
439
|
+
await this.storage.putMavenArtifact(
|
|
440
|
+
groupId,
|
|
441
|
+
artifactId,
|
|
442
|
+
version,
|
|
443
|
+
`${filename}.sha256`,
|
|
444
|
+
Buffer.from(checksums.sha256, 'utf-8')
|
|
445
|
+
);
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
if (checksums.sha512) {
|
|
449
|
+
await this.storage.putMavenArtifact(
|
|
450
|
+
groupId,
|
|
451
|
+
artifactId,
|
|
452
|
+
version,
|
|
453
|
+
`${filename}.sha512`,
|
|
454
|
+
Buffer.from(checksums.sha512, 'utf-8')
|
|
455
|
+
);
|
|
456
|
+
}
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
// ========================================================================
|
|
460
|
+
// METADATA OPERATIONS
|
|
461
|
+
// ========================================================================
|
|
462
|
+
|
|
463
|
+
private async getMetadata(groupId: string, artifactId: string): Promise<IResponse> {
|
|
464
|
+
const metadataBuffer = await this.storage.getMavenMetadata(groupId, artifactId);
|
|
465
|
+
|
|
466
|
+
if (!metadataBuffer) {
|
|
467
|
+
// Generate empty metadata if none exists
|
|
468
|
+
const emptyMetadata: IMavenMetadata = {
|
|
469
|
+
groupId,
|
|
470
|
+
artifactId,
|
|
471
|
+
versioning: {
|
|
472
|
+
versions: [],
|
|
473
|
+
lastUpdated: formatMavenTimestamp(new Date()),
|
|
474
|
+
},
|
|
475
|
+
};
|
|
476
|
+
|
|
477
|
+
const xml = generateMetadataXml(emptyMetadata);
|
|
478
|
+
return {
|
|
479
|
+
status: 200,
|
|
480
|
+
headers: {
|
|
481
|
+
'Content-Type': 'application/xml',
|
|
482
|
+
'Content-Length': xml.length.toString(),
|
|
483
|
+
},
|
|
484
|
+
body: Buffer.from(xml, 'utf-8'),
|
|
485
|
+
};
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
return {
|
|
489
|
+
status: 200,
|
|
490
|
+
headers: {
|
|
491
|
+
'Content-Type': 'application/xml',
|
|
492
|
+
'Content-Length': metadataBuffer.length.toString(),
|
|
493
|
+
},
|
|
494
|
+
body: metadataBuffer,
|
|
495
|
+
};
|
|
496
|
+
}
|
|
497
|
+
|
|
498
|
+
private async updateMetadata(
|
|
499
|
+
groupId: string,
|
|
500
|
+
artifactId: string,
|
|
501
|
+
newVersion: string
|
|
502
|
+
): Promise<void> {
|
|
503
|
+
// Get existing metadata or create new
|
|
504
|
+
const existingBuffer = await this.storage.getMavenMetadata(groupId, artifactId);
|
|
505
|
+
let metadata: IMavenMetadata;
|
|
506
|
+
|
|
507
|
+
if (existingBuffer) {
|
|
508
|
+
const parsed = parseMetadataXml(existingBuffer.toString('utf-8'));
|
|
509
|
+
if (parsed) {
|
|
510
|
+
metadata = parsed;
|
|
511
|
+
} else {
|
|
512
|
+
// Create new if parsing failed
|
|
513
|
+
metadata = {
|
|
514
|
+
groupId,
|
|
515
|
+
artifactId,
|
|
516
|
+
versioning: {
|
|
517
|
+
versions: [],
|
|
518
|
+
lastUpdated: formatMavenTimestamp(new Date()),
|
|
519
|
+
},
|
|
520
|
+
};
|
|
521
|
+
}
|
|
522
|
+
} else {
|
|
523
|
+
metadata = {
|
|
524
|
+
groupId,
|
|
525
|
+
artifactId,
|
|
526
|
+
versioning: {
|
|
527
|
+
versions: [],
|
|
528
|
+
lastUpdated: formatMavenTimestamp(new Date()),
|
|
529
|
+
},
|
|
530
|
+
};
|
|
531
|
+
}
|
|
532
|
+
|
|
533
|
+
// Add new version if not already present
|
|
534
|
+
if (!metadata.versioning.versions.includes(newVersion)) {
|
|
535
|
+
metadata.versioning.versions.push(newVersion);
|
|
536
|
+
metadata.versioning.versions.sort(); // Sort versions
|
|
537
|
+
}
|
|
538
|
+
|
|
539
|
+
// Update latest and release
|
|
540
|
+
const versions = metadata.versioning.versions;
|
|
541
|
+
metadata.versioning.latest = versions[versions.length - 1];
|
|
542
|
+
|
|
543
|
+
// Release is the latest non-SNAPSHOT version
|
|
544
|
+
const releaseVersions = versions.filter(v => !isSnapshot(v));
|
|
545
|
+
if (releaseVersions.length > 0) {
|
|
546
|
+
metadata.versioning.release = releaseVersions[releaseVersions.length - 1];
|
|
547
|
+
}
|
|
548
|
+
|
|
549
|
+
// Update timestamp
|
|
550
|
+
metadata.versioning.lastUpdated = formatMavenTimestamp(new Date());
|
|
551
|
+
|
|
552
|
+
// Generate and store XML
|
|
553
|
+
const xml = generateMetadataXml(metadata);
|
|
554
|
+
await this.storage.putMavenMetadata(groupId, artifactId, Buffer.from(xml, 'utf-8'));
|
|
555
|
+
|
|
556
|
+
// Note: Checksums for maven-metadata.xml are optional and not critical
|
|
557
|
+
// They would need special handling since metadata uses a different storage path
|
|
558
|
+
}
|
|
559
|
+
|
|
560
|
+
// ========================================================================
|
|
561
|
+
// UTILITY METHODS
|
|
562
|
+
// ========================================================================
|
|
563
|
+
|
|
564
|
+
private getContentType(extension: string): string {
|
|
565
|
+
const contentTypes: Record<string, string> = {
|
|
566
|
+
'jar': 'application/java-archive',
|
|
567
|
+
'war': 'application/java-archive',
|
|
568
|
+
'ear': 'application/java-archive',
|
|
569
|
+
'aar': 'application/java-archive',
|
|
570
|
+
'pom': 'application/xml',
|
|
571
|
+
'xml': 'application/xml',
|
|
572
|
+
'md5': 'text/plain',
|
|
573
|
+
'sha1': 'text/plain',
|
|
574
|
+
'sha256': 'text/plain',
|
|
575
|
+
'sha512': 'text/plain',
|
|
576
|
+
};
|
|
577
|
+
|
|
578
|
+
return contentTypes[extension] || 'application/octet-stream';
|
|
579
|
+
}
|
|
580
|
+
}
|