@package-broker/core 0.16.4 → 0.17.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/routes/composer.d.ts +16 -1
- package/dist/routes/composer.d.ts.map +1 -1
- package/dist/routes/composer.js +276 -139
- package/dist/routes/composer.js.map +1 -1
- package/dist/routes/dist.d.ts +8 -0
- package/dist/routes/dist.d.ts.map +1 -1
- package/dist/routes/dist.js +355 -66
- package/dist/routes/dist.js.map +1 -1
- package/dist/utils/checksum.d.ts +6 -0
- package/dist/utils/checksum.d.ts.map +1 -0
- package/dist/utils/checksum.js +16 -0
- package/dist/utils/checksum.js.map +1 -0
- package/package.json +1 -1
|
@@ -17,12 +17,22 @@ export interface ComposerRouteEnv {
|
|
|
17
17
|
requestId?: string;
|
|
18
18
|
};
|
|
19
19
|
}
|
|
20
|
+
/** Default maximum versions per package to avoid CPU timeout on Cloudflare Workers */
|
|
21
|
+
export declare const DEFAULT_MAX_VERSIONS = 50;
|
|
20
22
|
/**
|
|
21
23
|
* GET /packages.json
|
|
22
24
|
* Serve aggregated packages.json for all private repositories
|
|
23
25
|
* Uses KV caching with stale-while-revalidate strategy
|
|
24
26
|
*/
|
|
25
27
|
export declare function packagesJsonRoute(c: Context<ComposerRouteEnv>): Promise<Response>;
|
|
28
|
+
/**
|
|
29
|
+
* Lazy load package metadata from all available repositories
|
|
30
|
+
* Returns package data and repo_id if found, null otherwise
|
|
31
|
+
*/
|
|
32
|
+
export declare function lazyLoadPackageFromRepositories(db: DatabasePort, packageName: string, encryptionKey: string, kv?: KVNamespace | null): Promise<{
|
|
33
|
+
packageData: any;
|
|
34
|
+
repoId: string;
|
|
35
|
+
} | null>;
|
|
26
36
|
/**
|
|
27
37
|
* GET /p2/:vendor/:package.json
|
|
28
38
|
* Serve individual package metadata (Composer 2 provider format)
|
|
@@ -33,12 +43,17 @@ export declare function p2PackageRoute(c: Context<ComposerRouteEnv>): Promise<Re
|
|
|
33
43
|
* Build Composer 2 provider response for a single package from stored metadata.
|
|
34
44
|
* Composer 2 (p2) format expects versions as an ARRAY, not a dict keyed by version.
|
|
35
45
|
*/
|
|
36
|
-
export declare function buildP2Response(packageName: string, packageVersions: Array<typeof packages.$inferSelect>, maxVersions?: number): ComposerP2Response;
|
|
46
|
+
export declare function buildP2Response(packageName: string, packageVersions: Array<typeof packages.$inferSelect>, maxVersions?: number, baseUrl?: string): ComposerP2Response;
|
|
47
|
+
export declare function deriveVersionNormalized(version: string): string | undefined;
|
|
37
48
|
/**
|
|
38
49
|
* Ensure Packagist repository exists in database
|
|
39
50
|
* Creates it if it doesn't exist
|
|
40
51
|
*/
|
|
41
52
|
export declare function ensurePackagistRepository(db: DatabasePort, encryptionKey: string, kv?: KVNamespace): Promise<void>;
|
|
53
|
+
export declare function normalizePackageVersions(versions: any): Array<{
|
|
54
|
+
version: string;
|
|
55
|
+
metadata: any;
|
|
56
|
+
}>;
|
|
42
57
|
/**
|
|
43
58
|
* Transform package dist URLs to proxy URLs and store in database
|
|
44
59
|
* Waits for all database writes to complete before returning
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"composer.d.ts","sourceRoot":"","sources":["../../src/routes/composer.ts"],"names":[],"mappings":"AAQA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,MAAM,CAAC;AACpC,OAAO,KAAK,EAAE,YAAY,EAAa,MAAM,UAAU,CAAC;AACxD,OAAO,EAAgB,QAAQ,EAAE,MAAM,cAAc,CAAC;AAGtD,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,mBAAmB,CAAC;AAQvD,MAAM,WAAW,gBAAgB;IAC/B,QAAQ,EAAE;QACR,EAAE,EAAE,UAAU,CAAC;QACf,EAAE,CAAC,EAAE,WAAW,CAAC;QACjB,KAAK,CAAC,EAAE,KAAK,CAAC;QACd,wBAAwB,CAAC,EAAE,QAAQ,CAAC;QACpC,cAAc,EAAE,MAAM,CAAC;QACvB,oBAAoB,CAAC,EAAE,MAAM,CAAC;KAC/B,CAAC;IACF,SAAS,EAAE;QACT,OAAO,EAAE,aAAa,CAAC;QACvB,QAAQ,EAAE,YAAY,CAAC;QACvB,SAAS,CAAC,EAAE,MAAM,CAAC;KACpB,CAAC;CACH;
|
|
1
|
+
{"version":3,"file":"composer.d.ts","sourceRoot":"","sources":["../../src/routes/composer.ts"],"names":[],"mappings":"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"}
|
package/dist/routes/composer.js
CHANGED
|
@@ -13,7 +13,7 @@ import { encryptCredentials } from '../utils/encryption';
|
|
|
13
13
|
import { getLogger } from '../utils/logger';
|
|
14
14
|
import { getAnalytics } from '../utils/analytics';
|
|
15
15
|
/** Default maximum versions per package to avoid CPU timeout on Cloudflare Workers */
|
|
16
|
-
const DEFAULT_MAX_VERSIONS = 50;
|
|
16
|
+
export const DEFAULT_MAX_VERSIONS = 50;
|
|
17
17
|
/**
|
|
18
18
|
* Schedule background storage of package data.
|
|
19
19
|
* Uses Cloudflare Workflows when available, falls back to inline processing.
|
|
@@ -156,6 +156,116 @@ export async function packagesJsonRoute(c) {
|
|
|
156
156
|
headers.set('Cache-Control', 'public, max-age=300, stale-while-revalidate=60');
|
|
157
157
|
return new Response(JSON.stringify(packagesJson), { status: 200, headers });
|
|
158
158
|
}
|
|
159
|
+
function sortRepositoriesByPriority(repos) {
|
|
160
|
+
return [...repos].sort((a, b) => {
|
|
161
|
+
if (a.id === 'manual' && b.id !== 'manual')
|
|
162
|
+
return -1;
|
|
163
|
+
if (a.id !== 'manual' && b.id === 'manual')
|
|
164
|
+
return 1;
|
|
165
|
+
if (a.id === 'packagist' && b.id !== 'packagist')
|
|
166
|
+
return 1;
|
|
167
|
+
if (a.id !== 'packagist' && b.id === 'packagist')
|
|
168
|
+
return -1;
|
|
169
|
+
return (a.created_at || 0) - (b.created_at || 0);
|
|
170
|
+
});
|
|
171
|
+
}
|
|
172
|
+
function matchesPackageFilter(packageName, filter) {
|
|
173
|
+
if (!filter)
|
|
174
|
+
return true;
|
|
175
|
+
const patterns = filter.split(',').map((p) => p.trim().toLowerCase());
|
|
176
|
+
const pkgLower = packageName.toLowerCase();
|
|
177
|
+
return patterns.some((pattern) => {
|
|
178
|
+
if (pattern.endsWith('/*')) {
|
|
179
|
+
const prefix = pattern.slice(0, -1);
|
|
180
|
+
return pkgLower.startsWith(prefix);
|
|
181
|
+
}
|
|
182
|
+
if (pattern.endsWith('*')) {
|
|
183
|
+
const prefix = pattern.slice(0, -1);
|
|
184
|
+
return pkgLower.startsWith(prefix);
|
|
185
|
+
}
|
|
186
|
+
return pkgLower === pattern;
|
|
187
|
+
});
|
|
188
|
+
}
|
|
189
|
+
/**
|
|
190
|
+
* Lazy load package metadata from all available repositories
|
|
191
|
+
* Returns package data and repo_id if found, null otherwise
|
|
192
|
+
*/
|
|
193
|
+
export async function lazyLoadPackageFromRepositories(db, packageName, encryptionKey, kv) {
|
|
194
|
+
const allRepos = await db
|
|
195
|
+
.select()
|
|
196
|
+
.from(repositories)
|
|
197
|
+
.where(inArray(repositories.status, ['active', 'pending', 'syncing']));
|
|
198
|
+
const sortedRepos = sortRepositoriesByPriority(allRepos);
|
|
199
|
+
for (const repo of sortedRepos) {
|
|
200
|
+
try {
|
|
201
|
+
if (!matchesPackageFilter(packageName, repo.package_filter)) {
|
|
202
|
+
continue;
|
|
203
|
+
}
|
|
204
|
+
let packageData = null;
|
|
205
|
+
if (repo.vcs_type === 'composer') {
|
|
206
|
+
const { fetchPackageFromUpstream } = await import('../utils/upstream-fetch');
|
|
207
|
+
packageData = await fetchPackageFromUpstream({
|
|
208
|
+
id: repo.id,
|
|
209
|
+
url: repo.url,
|
|
210
|
+
vcs_type: repo.vcs_type,
|
|
211
|
+
credential_type: repo.credential_type,
|
|
212
|
+
auth_credentials: repo.auth_credentials,
|
|
213
|
+
package_filter: repo.package_filter,
|
|
214
|
+
}, packageName, encryptionKey);
|
|
215
|
+
}
|
|
216
|
+
else if (repo.vcs_type === 'git') {
|
|
217
|
+
const { fetchPackageFromGitHub, isGitHubUrl } = await import('../utils/upstream-fetch');
|
|
218
|
+
if (!isGitHubUrl(repo.url)) {
|
|
219
|
+
continue;
|
|
220
|
+
}
|
|
221
|
+
packageData = await fetchPackageFromGitHub({
|
|
222
|
+
id: repo.id,
|
|
223
|
+
url: repo.url,
|
|
224
|
+
vcs_type: repo.vcs_type,
|
|
225
|
+
credential_type: repo.credential_type,
|
|
226
|
+
auth_credentials: repo.auth_credentials,
|
|
227
|
+
package_filter: repo.package_filter,
|
|
228
|
+
}, packageName, encryptionKey);
|
|
229
|
+
}
|
|
230
|
+
if (packageData) {
|
|
231
|
+
return { packageData, repoId: repo.id };
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
catch (error) {
|
|
235
|
+
const logger = getLogger();
|
|
236
|
+
logger.warn('Error fetching package from repo', {
|
|
237
|
+
packageName,
|
|
238
|
+
repoId: repo.id,
|
|
239
|
+
vcsType: repo.vcs_type,
|
|
240
|
+
error: error instanceof Error ? error.message : String(error)
|
|
241
|
+
});
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
const { isPackagistMirroringEnabled } = await import('../modules/admin');
|
|
245
|
+
const mirroringEnabled = await isPackagistMirroringEnabled(kv);
|
|
246
|
+
if (mirroringEnabled) {
|
|
247
|
+
try {
|
|
248
|
+
const packagistUrl = `https://repo.packagist.org/p2/${packageName}.json`;
|
|
249
|
+
const response = await fetch(packagistUrl, {
|
|
250
|
+
headers: {
|
|
251
|
+
'User-Agent': COMPOSER_USER_AGENT,
|
|
252
|
+
},
|
|
253
|
+
});
|
|
254
|
+
if (response.ok) {
|
|
255
|
+
const packageData = await response.json();
|
|
256
|
+
return { packageData, repoId: 'packagist' };
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
catch (error) {
|
|
260
|
+
const logger = getLogger();
|
|
261
|
+
logger.warn('Error fetching from Packagist', {
|
|
262
|
+
packageName,
|
|
263
|
+
error: error instanceof Error ? error.message : String(error)
|
|
264
|
+
});
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
return null;
|
|
268
|
+
}
|
|
159
269
|
/**
|
|
160
270
|
* GET /p2/:vendor/:package.json
|
|
161
271
|
* Serve individual package metadata (Composer 2 provider format)
|
|
@@ -241,7 +351,9 @@ export async function p2PackageRoute(c) {
|
|
|
241
351
|
if (existingPackages.length > 0) {
|
|
242
352
|
// Build response from database packages
|
|
243
353
|
const maxVersions = getMaxVersions(c.env);
|
|
244
|
-
const
|
|
354
|
+
const url = new URL(c.req.url);
|
|
355
|
+
const baseUrl = `${url.protocol}//${url.host}`;
|
|
356
|
+
const packageData = buildP2Response(packageName, existingPackages, maxVersions, baseUrl);
|
|
245
357
|
// Cache the result (fire-and-forget to avoid blocking on KV rate limits)
|
|
246
358
|
const cachingEnabled = await isPackageCachingEnabled(c.env.KV);
|
|
247
359
|
if (cachingEnabled && c.env.KV) {
|
|
@@ -267,112 +379,36 @@ export async function p2PackageRoute(c) {
|
|
|
267
379
|
return new Response(JSON.stringify(packageData), { status: 200, headers });
|
|
268
380
|
}
|
|
269
381
|
// Not in database - try lazy loading from upstream repositories
|
|
270
|
-
const activeRepos = await db
|
|
271
|
-
.select()
|
|
272
|
-
.from(repositories)
|
|
273
|
-
.where(eq(repositories.status, 'active'));
|
|
274
382
|
const url = new URL(c.req.url);
|
|
275
383
|
const baseUrl = `${url.protocol}//${url.host}`;
|
|
276
384
|
const maxVersions = getMaxVersions(c.env);
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
return pkgLower === pattern;
|
|
299
|
-
});
|
|
300
|
-
if (!matches) {
|
|
301
|
-
continue; // Skip this repo - package not in filter list
|
|
302
|
-
}
|
|
303
|
-
}
|
|
304
|
-
let packageData = null;
|
|
305
|
-
if (repo.vcs_type === 'composer') {
|
|
306
|
-
// Fetch from Composer repository (Satis, Private Packagist, etc.)
|
|
307
|
-
const { fetchPackageFromUpstream } = await import('../utils/upstream-fetch');
|
|
308
|
-
packageData = await fetchPackageFromUpstream({
|
|
309
|
-
id: repo.id,
|
|
310
|
-
url: repo.url,
|
|
311
|
-
vcs_type: repo.vcs_type,
|
|
312
|
-
credential_type: repo.credential_type,
|
|
313
|
-
auth_credentials: repo.auth_credentials,
|
|
314
|
-
package_filter: repo.package_filter,
|
|
315
|
-
}, packageName, c.env.ENCRYPTION_KEY);
|
|
316
|
-
}
|
|
317
|
-
else if (repo.vcs_type === 'git') {
|
|
318
|
-
// Fetch from GitHub repository (public or private)
|
|
319
|
-
const { fetchPackageFromGitHub, isGitHubUrl } = await import('../utils/upstream-fetch');
|
|
320
|
-
// Validate GitHub URL using proper hostname check (security)
|
|
321
|
-
if (!isGitHubUrl(repo.url)) {
|
|
322
|
-
// Skip non-GitHub git repos (e.g., GitLab, Bitbucket - not yet supported)
|
|
323
|
-
continue;
|
|
324
|
-
}
|
|
325
|
-
packageData = await fetchPackageFromGitHub({
|
|
326
|
-
id: repo.id,
|
|
327
|
-
url: repo.url,
|
|
328
|
-
vcs_type: repo.vcs_type,
|
|
329
|
-
credential_type: repo.credential_type,
|
|
330
|
-
auth_credentials: repo.auth_credentials,
|
|
331
|
-
package_filter: repo.package_filter,
|
|
332
|
-
}, packageName, c.env.ENCRYPTION_KEY);
|
|
333
|
-
}
|
|
334
|
-
if (packageData) {
|
|
335
|
-
const transformedData = transformDistUrlsInMemory(packageData, repo.id, baseUrl, maxVersions);
|
|
336
|
-
// Store package versions in background
|
|
337
|
-
schedulePackageStorage(c, packageName, packageData, repo.id, baseUrl);
|
|
338
|
-
// Track metadata request (cache miss, from upstream)
|
|
339
|
-
const analytics = getAnalytics();
|
|
340
|
-
const requestId = c.get('requestId');
|
|
341
|
-
const packageCount = transformedData.packages?.[packageName] ? Object.keys(transformedData.packages[packageName]).length : 0;
|
|
342
|
-
analytics.trackPackageMetadataRequest({
|
|
343
|
-
requestId,
|
|
344
|
-
cacheHit: false,
|
|
345
|
-
packageCount,
|
|
346
|
-
});
|
|
347
|
-
const headers = new Headers();
|
|
348
|
-
headers.set('Content-Type', 'application/json');
|
|
349
|
-
headers.set('Last-Modified', new Date().toUTCString());
|
|
350
|
-
headers.set('Cache-Control', 'public, max-age=300, stale-while-revalidate=60');
|
|
351
|
-
headers.set('X-Cache', 'MISS-UPSTREAM');
|
|
352
|
-
return new Response(JSON.stringify(transformedData), { status: 200, headers });
|
|
353
|
-
}
|
|
354
|
-
}
|
|
355
|
-
catch (error) {
|
|
356
|
-
const logger = getLogger();
|
|
357
|
-
logger.warn('Error fetching package from repo', {
|
|
358
|
-
packageName,
|
|
359
|
-
repoId: repo.id,
|
|
360
|
-
vcsType: repo.vcs_type,
|
|
361
|
-
error: error instanceof Error ? error.message : String(error)
|
|
362
|
-
});
|
|
363
|
-
// Continue to next repository
|
|
364
|
-
}
|
|
365
|
-
}
|
|
366
|
-
// Not found in any upstream repo - check if Packagist mirroring is enabled
|
|
367
|
-
const mirroringEnabled = await isPackagistMirroringEnabled(c.env.KV);
|
|
368
|
-
if (!mirroringEnabled) {
|
|
369
|
-
return c.json({
|
|
370
|
-
error: 'Not Found',
|
|
371
|
-
message: 'Package not found. Public Packagist mirroring is disabled.',
|
|
372
|
-
}, 404);
|
|
385
|
+
const lazyLoadResult = await lazyLoadPackageFromRepositories(db, packageName, c.env.ENCRYPTION_KEY, c.env.KV);
|
|
386
|
+
if (lazyLoadResult) {
|
|
387
|
+
const { packageData, repoId } = lazyLoadResult;
|
|
388
|
+
const transformedData = transformDistUrlsInMemory(packageData, repoId, baseUrl, maxVersions);
|
|
389
|
+
// Store package versions in background
|
|
390
|
+
schedulePackageStorage(c, packageName, packageData, repoId, baseUrl);
|
|
391
|
+
// Track metadata request (cache miss, from upstream)
|
|
392
|
+
const analytics = getAnalytics();
|
|
393
|
+
const requestId = c.get('requestId');
|
|
394
|
+
const packageCount = transformedData.packages?.[packageName] ? Object.keys(transformedData.packages[packageName]).length : 0;
|
|
395
|
+
analytics.trackPackageMetadataRequest({
|
|
396
|
+
requestId,
|
|
397
|
+
cacheHit: false,
|
|
398
|
+
packageCount,
|
|
399
|
+
});
|
|
400
|
+
const headers = new Headers();
|
|
401
|
+
headers.set('Content-Type', 'application/json');
|
|
402
|
+
headers.set('Last-Modified', new Date().toUTCString());
|
|
403
|
+
headers.set('Cache-Control', 'public, max-age=300, stale-while-revalidate=60');
|
|
404
|
+
headers.set('X-Cache', 'MISS-UPSTREAM');
|
|
405
|
+
return new Response(JSON.stringify(transformedData), { status: 200, headers });
|
|
373
406
|
}
|
|
374
|
-
//
|
|
375
|
-
return
|
|
407
|
+
// Not found in any repository (including Packagist)
|
|
408
|
+
return c.json({
|
|
409
|
+
error: 'Not Found',
|
|
410
|
+
message: 'Package not found in any repository.',
|
|
411
|
+
}, 404);
|
|
376
412
|
}
|
|
377
413
|
/**
|
|
378
414
|
* Build aggregated packages.json from all repositories
|
|
@@ -413,15 +449,53 @@ async function buildPackagesJson(c) {
|
|
|
413
449
|
if (!packagesMap[pkg.name]) {
|
|
414
450
|
packagesMap[pkg.name] = {};
|
|
415
451
|
}
|
|
416
|
-
//
|
|
417
|
-
|
|
452
|
+
// Preserve original dist from metadata - do NOT modify dist.url or dist.type
|
|
453
|
+
let dist;
|
|
454
|
+
if (pkg.metadata) {
|
|
455
|
+
try {
|
|
456
|
+
const metadata = JSON.parse(pkg.metadata);
|
|
457
|
+
if (metadata.dist && typeof metadata.dist === 'object' && !Array.isArray(metadata.dist)) {
|
|
458
|
+
// Use original dist from upstream metadata exactly as-is
|
|
459
|
+
dist = { ...metadata.dist };
|
|
460
|
+
}
|
|
461
|
+
else {
|
|
462
|
+
// Fallback: create minimal dist if metadata doesn't have dist
|
|
463
|
+
dist = {
|
|
464
|
+
type: 'zip',
|
|
465
|
+
url: pkg.source_dist_url || transformDistUrlToMirrorFormat(pkg.dist_url) || pkg.dist_url,
|
|
466
|
+
};
|
|
467
|
+
}
|
|
468
|
+
}
|
|
469
|
+
catch {
|
|
470
|
+
// If metadata parsing fails, use fallback
|
|
471
|
+
dist = {
|
|
472
|
+
type: 'zip',
|
|
473
|
+
url: pkg.source_dist_url || transformDistUrlToMirrorFormat(pkg.dist_url) || pkg.dist_url,
|
|
474
|
+
};
|
|
475
|
+
}
|
|
476
|
+
}
|
|
477
|
+
else {
|
|
478
|
+
// No metadata available - use fallback
|
|
479
|
+
dist = {
|
|
480
|
+
type: 'zip',
|
|
481
|
+
url: pkg.source_dist_url || transformDistUrlToMirrorFormat(pkg.dist_url) || pkg.dist_url,
|
|
482
|
+
};
|
|
483
|
+
}
|
|
484
|
+
// Add mirrors section to dist object if not already present
|
|
485
|
+
// This ensures Composer can use the mirror while preserving original dist.url
|
|
486
|
+
// This prevents Composer from treating packages as upgrades when dist.url differs
|
|
487
|
+
if (!dist.mirrors || !Array.isArray(dist.mirrors) || dist.mirrors.length === 0) {
|
|
488
|
+
dist.mirrors = [
|
|
489
|
+
{
|
|
490
|
+
url: `${baseUrl}/dist/m/%package%/%version%.%type%`,
|
|
491
|
+
preferred: true,
|
|
492
|
+
},
|
|
493
|
+
];
|
|
494
|
+
}
|
|
418
495
|
packagesMap[pkg.name][pkg.version] = {
|
|
419
496
|
name: pkg.name,
|
|
420
497
|
version: pkg.version,
|
|
421
|
-
dist
|
|
422
|
-
type: 'zip',
|
|
423
|
-
url: transformDistUrlToMirrorFormat(pkg.dist_url) || pkg.dist_url,
|
|
424
|
-
},
|
|
498
|
+
dist,
|
|
425
499
|
};
|
|
426
500
|
}
|
|
427
501
|
return {
|
|
@@ -432,7 +506,7 @@ async function buildPackagesJson(c) {
|
|
|
432
506
|
* Build Composer 2 provider response for a single package from stored metadata.
|
|
433
507
|
* Composer 2 (p2) format expects versions as an ARRAY, not a dict keyed by version.
|
|
434
508
|
*/
|
|
435
|
-
export function buildP2Response(packageName, packageVersions, maxVersions = DEFAULT_MAX_VERSIONS) {
|
|
509
|
+
export function buildP2Response(packageName, packageVersions, maxVersions = DEFAULT_MAX_VERSIONS, baseUrl) {
|
|
436
510
|
// Apply version limiting to DB records as well
|
|
437
511
|
// Convert to normalized format for limiting
|
|
438
512
|
const normalizedForLimiting = packageVersions.map(pkg => ({
|
|
@@ -448,21 +522,42 @@ export function buildP2Response(packageName, packageVersions, maxVersions = DEFA
|
|
|
448
522
|
const filteredPackageVersions = packageVersions.filter(pkg => limitedVersionSet.has(pkg.version));
|
|
449
523
|
const versions = [];
|
|
450
524
|
for (const pkg of filteredPackageVersions) {
|
|
451
|
-
const dist = {
|
|
452
|
-
type: 'zip',
|
|
453
|
-
url: transformDistUrlToMirrorFormat(pkg.dist_url) || pkg.dist_url,
|
|
454
|
-
};
|
|
455
|
-
if (pkg.dist_reference) {
|
|
456
|
-
dist.reference = pkg.dist_reference;
|
|
457
|
-
}
|
|
458
525
|
let fullMetadata = null;
|
|
459
526
|
if (pkg.metadata) {
|
|
460
527
|
try {
|
|
461
528
|
fullMetadata = JSON.parse(pkg.metadata);
|
|
462
529
|
}
|
|
463
530
|
catch {
|
|
531
|
+
// Ignore invalid JSON; use fallback metadata
|
|
464
532
|
}
|
|
465
533
|
}
|
|
534
|
+
// Preserve original dist from metadata - do NOT modify dist.url or dist.type
|
|
535
|
+
let dist;
|
|
536
|
+
if (fullMetadata?.dist && typeof fullMetadata.dist === 'object' && !Array.isArray(fullMetadata.dist)) {
|
|
537
|
+
// Use original dist from upstream metadata exactly as-is
|
|
538
|
+
dist = { ...fullMetadata.dist };
|
|
539
|
+
}
|
|
540
|
+
else {
|
|
541
|
+
// Fallback: only create minimal dist if metadata doesn't exist (shouldn't happen in practice)
|
|
542
|
+
dist = {
|
|
543
|
+
type: 'zip',
|
|
544
|
+
url: pkg.source_dist_url || transformDistUrlToMirrorFormat(pkg.dist_url) || pkg.dist_url,
|
|
545
|
+
};
|
|
546
|
+
if (pkg.dist_reference) {
|
|
547
|
+
dist.reference = pkg.dist_reference;
|
|
548
|
+
}
|
|
549
|
+
}
|
|
550
|
+
// Add mirrors section to dist object if not already present
|
|
551
|
+
// This ensures Composer can use the mirror while preserving original dist.url
|
|
552
|
+
// This prevents Composer from treating packages as upgrades when dist.url differs
|
|
553
|
+
if (baseUrl && (!dist.mirrors || !Array.isArray(dist.mirrors) || dist.mirrors.length === 0)) {
|
|
554
|
+
dist.mirrors = [
|
|
555
|
+
{
|
|
556
|
+
url: `${baseUrl}/dist/m/%package%/%version%.%type%`,
|
|
557
|
+
preferred: true,
|
|
558
|
+
},
|
|
559
|
+
];
|
|
560
|
+
}
|
|
466
561
|
const displayVersion = typeof fullMetadata?.version === 'string' ? fullMetadata.version : pkg.version;
|
|
467
562
|
const normalizedVersion = typeof fullMetadata?.version_normalized === 'string'
|
|
468
563
|
? fullMetadata.version_normalized
|
|
@@ -484,6 +579,7 @@ export function buildP2Response(packageName, packageVersions, maxVersions = DEFA
|
|
|
484
579
|
}
|
|
485
580
|
}
|
|
486
581
|
catch {
|
|
582
|
+
// Ignore license processing errors; use original value
|
|
487
583
|
versionDataBase.license = pkg.license;
|
|
488
584
|
}
|
|
489
585
|
}
|
|
@@ -510,12 +606,6 @@ export function buildP2Response(packageName, packageVersions, maxVersions = DEFA
|
|
|
510
606
|
...(fullMetadata.source.reference && { reference: fullMetadata.source.reference }),
|
|
511
607
|
};
|
|
512
608
|
}
|
|
513
|
-
if (fullMetadata.dist?.type && fullMetadata.dist.type !== 'zip') {
|
|
514
|
-
dist.type = fullMetadata.dist.type;
|
|
515
|
-
}
|
|
516
|
-
if (fullMetadata.dist?.shasum) {
|
|
517
|
-
dist.shasum = fullMetadata.dist.shasum;
|
|
518
|
-
}
|
|
519
609
|
if (fullMetadata.require && typeof fullMetadata.require === 'object' && !Array.isArray(fullMetadata.require)) {
|
|
520
610
|
versionDataBase.require = fullMetadata.require;
|
|
521
611
|
}
|
|
@@ -582,21 +672,52 @@ export function buildP2Response(packageName, packageVersions, maxVersions = DEFA
|
|
|
582
672
|
},
|
|
583
673
|
};
|
|
584
674
|
}
|
|
585
|
-
function deriveVersionNormalized(version) {
|
|
675
|
+
export function deriveVersionNormalized(version) {
|
|
586
676
|
const v = version.startsWith('v') ? version.slice(1) : version;
|
|
677
|
+
// Handle patch versions (e.g., "103.0.7-p8" → "103.0.7.0-patch8")
|
|
587
678
|
const patchMatch = v.match(/^(\d+\.\d+\.\d+)-p(\d+)$/);
|
|
588
679
|
if (patchMatch) {
|
|
589
680
|
const [, base, patch] = patchMatch;
|
|
590
681
|
return `${base}.0-patch${patch}`;
|
|
591
682
|
}
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
683
|
+
// Explicit parser avoids regex backtracking and ReDoS concerns:
|
|
684
|
+
// 1. Parse up to 4 numeric segments separated by dots from the start of the string.
|
|
685
|
+
// 2. Treat everything after the parsed numeric part as the suffix (e.g., "-beta", "-alpha", "-dev").
|
|
686
|
+
let index = 0;
|
|
687
|
+
const length = v.length;
|
|
688
|
+
const segments = [];
|
|
689
|
+
while (index < length && segments.length < 4) {
|
|
690
|
+
const start = index;
|
|
691
|
+
// Read a numeric segment
|
|
692
|
+
while (index < length) {
|
|
693
|
+
const ch = v.charCodeAt(index);
|
|
694
|
+
if (ch < 48 || ch > 57) { // not '0'–'9'
|
|
695
|
+
break;
|
|
696
|
+
}
|
|
697
|
+
index++;
|
|
698
|
+
}
|
|
699
|
+
if (index === start) {
|
|
700
|
+
// No digits found where a segment was expected
|
|
701
|
+
break;
|
|
702
|
+
}
|
|
703
|
+
segments.push(v.slice(start, index));
|
|
704
|
+
// If we have 4 segments or we've reached the end, stop
|
|
705
|
+
if (segments.length === 4 || index >= length) {
|
|
706
|
+
break;
|
|
707
|
+
}
|
|
708
|
+
// Expect a dot between segments; if not present, stop parsing numeric part
|
|
709
|
+
if (v[index] === '.') {
|
|
710
|
+
index++;
|
|
711
|
+
continue;
|
|
712
|
+
}
|
|
713
|
+
break;
|
|
714
|
+
}
|
|
715
|
+
if (segments.length > 0) {
|
|
716
|
+
const [major, minor = '0', patch = '0', build = '0'] = segments;
|
|
717
|
+
const suffix = v.slice(index); // everything after the numeric part
|
|
718
|
+
return `${major}.${minor}.${patch}.${build}${suffix}`;
|
|
599
719
|
}
|
|
720
|
+
// Fallback: return undefined for non-standard versions
|
|
600
721
|
return undefined;
|
|
601
722
|
}
|
|
602
723
|
/**
|
|
@@ -914,17 +1035,33 @@ function transformDistUrlsInMemory(packageData, repoId, proxyBaseUrl, maxVersion
|
|
|
914
1035
|
? metadata.version_normalized
|
|
915
1036
|
: deriveVersionNormalized(version);
|
|
916
1037
|
// Build transformed version with original display version
|
|
1038
|
+
// Preserve original dist from metadata - do NOT modify dist.url or dist.type
|
|
1039
|
+
let dist = metadata.dist || {
|
|
1040
|
+
type: 'zip',
|
|
1041
|
+
url: `${proxyBaseUrl}/dist/m/${pkgName}/${version}.zip`,
|
|
1042
|
+
reference: distReference,
|
|
1043
|
+
};
|
|
1044
|
+
// Add mirrors section to dist object if not already present
|
|
1045
|
+
// This ensures Composer can use the mirror while preserving original dist.url
|
|
1046
|
+
// This prevents Composer from treating packages as upgrades when dist.url differs
|
|
1047
|
+
if (!dist.mirrors || !Array.isArray(dist.mirrors) || dist.mirrors.length === 0) {
|
|
1048
|
+
dist = {
|
|
1049
|
+
...dist,
|
|
1050
|
+
mirrors: [
|
|
1051
|
+
{
|
|
1052
|
+
url: `${proxyBaseUrl}/dist/m/%package%/%version%.%type%`,
|
|
1053
|
+
preferred: true,
|
|
1054
|
+
},
|
|
1055
|
+
],
|
|
1056
|
+
};
|
|
1057
|
+
}
|
|
917
1058
|
const versionData = {
|
|
918
1059
|
...metadata,
|
|
919
1060
|
name: pkgName,
|
|
920
1061
|
version,
|
|
921
1062
|
...(normalizedVersion ? { version_normalized: normalizedVersion } : {}),
|
|
922
|
-
dist
|
|
923
|
-
|
|
924
|
-
type: metadata.dist?.type || 'zip',
|
|
925
|
-
url: `${proxyBaseUrl}/dist/m/${pkgName}/${version}.zip`,
|
|
926
|
-
reference: distReference,
|
|
927
|
-
},
|
|
1063
|
+
// Use dist with mirrors section
|
|
1064
|
+
dist,
|
|
928
1065
|
};
|
|
929
1066
|
// Clean invalid source field if present
|
|
930
1067
|
if (versionData.source === '__unset' ||
|
|
@@ -947,7 +1084,7 @@ function transformDistUrlsInMemory(packageData, repoId, proxyBaseUrl, maxVersion
|
|
|
947
1084
|
}
|
|
948
1085
|
return result;
|
|
949
1086
|
}
|
|
950
|
-
function normalizePackageVersions(versions) {
|
|
1087
|
+
export function normalizePackageVersions(versions) {
|
|
951
1088
|
if (Array.isArray(versions)) {
|
|
952
1089
|
return versions.map((metadata) => ({
|
|
953
1090
|
// Use original `version` field, NOT `version_normalized`
|