@package-broker/core 0.16.4 → 0.17.1
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
package/dist/routes/dist.js
CHANGED
|
@@ -13,6 +13,56 @@ import { nanoid } from 'nanoid';
|
|
|
13
13
|
import { unzipSync, strFromU8 } from 'fflate';
|
|
14
14
|
import { getLogger } from '../utils/logger';
|
|
15
15
|
import { getAnalytics } from '../utils/analytics';
|
|
16
|
+
import { lazyLoadPackageFromRepositories, normalizePackageVersions, storePackageInDB, deriveVersionNormalized } from './composer';
|
|
17
|
+
import { computeShasum } from '../utils/checksum';
|
|
18
|
+
/**
|
|
19
|
+
* Get Content-Type header based on dist.type from package metadata
|
|
20
|
+
*/
|
|
21
|
+
function getContentTypeForDistType(distType) {
|
|
22
|
+
if (!distType) {
|
|
23
|
+
return 'application/zip'; // Default fallback
|
|
24
|
+
}
|
|
25
|
+
const type = distType.toLowerCase();
|
|
26
|
+
// Map common dist types to Content-Type headers
|
|
27
|
+
if (type === 'zip') {
|
|
28
|
+
return 'application/zip';
|
|
29
|
+
}
|
|
30
|
+
if (type === 'tar') {
|
|
31
|
+
return 'application/x-tar';
|
|
32
|
+
}
|
|
33
|
+
if (type === 'tar.gz' || type === 'tgz') {
|
|
34
|
+
return 'application/gzip';
|
|
35
|
+
}
|
|
36
|
+
if (type === 'tar.bz2') {
|
|
37
|
+
return 'application/x-bzip2';
|
|
38
|
+
}
|
|
39
|
+
// Default fallback
|
|
40
|
+
return 'application/octet-stream';
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Get file extension for Content-Disposition header based on dist.type
|
|
44
|
+
*/
|
|
45
|
+
function getFileExtensionForDistType(distType) {
|
|
46
|
+
if (!distType) {
|
|
47
|
+
return 'zip'; // Default fallback
|
|
48
|
+
}
|
|
49
|
+
const type = distType.toLowerCase();
|
|
50
|
+
// Map dist types to file extensions
|
|
51
|
+
if (type === 'zip') {
|
|
52
|
+
return 'zip';
|
|
53
|
+
}
|
|
54
|
+
if (type === 'tar') {
|
|
55
|
+
return 'tar';
|
|
56
|
+
}
|
|
57
|
+
if (type === 'tar.gz' || type === 'tgz') {
|
|
58
|
+
return 'tar.gz';
|
|
59
|
+
}
|
|
60
|
+
if (type === 'tar.bz2') {
|
|
61
|
+
return 'tar.bz2';
|
|
62
|
+
}
|
|
63
|
+
// Default fallback
|
|
64
|
+
return 'zip';
|
|
65
|
+
}
|
|
16
66
|
/**
|
|
17
67
|
* Extract README.md or README.mdown from ZIP archive
|
|
18
68
|
*/
|
|
@@ -123,6 +173,70 @@ async function extractAndStoreReadme(storage, zipData, storageType, repoId, pack
|
|
|
123
173
|
logger.error('Error extracting/storing README', { packageName, version, storageType, repoId }, error instanceof Error ? error : new Error(String(error)));
|
|
124
174
|
}
|
|
125
175
|
}
|
|
176
|
+
export function findVersionInMetadata(versionFromUrl, displayVersion, normalizedVersions) {
|
|
177
|
+
for (const { version: metadataVersion, metadata } of normalizedVersions) {
|
|
178
|
+
if (versionFromUrl === metadataVersion) {
|
|
179
|
+
return { version: metadataVersion, metadata };
|
|
180
|
+
}
|
|
181
|
+
if (displayVersion === metadataVersion) {
|
|
182
|
+
return { version: metadataVersion, metadata };
|
|
183
|
+
}
|
|
184
|
+
const metadataNormalized = metadata.version_normalized;
|
|
185
|
+
if (metadataNormalized && versionFromUrl === metadataNormalized) {
|
|
186
|
+
return { version: metadataVersion, metadata };
|
|
187
|
+
}
|
|
188
|
+
const derivedNormalized = deriveVersionNormalized(metadataVersion);
|
|
189
|
+
if (derivedNormalized && versionFromUrl === derivedNormalized) {
|
|
190
|
+
return { version: metadataVersion, metadata };
|
|
191
|
+
}
|
|
192
|
+
const metadataVersionNoV = metadataVersion.startsWith('v') ? metadataVersion.slice(1) : metadataVersion;
|
|
193
|
+
if (displayVersion === metadataVersionNoV) {
|
|
194
|
+
return { version: metadataVersion, metadata };
|
|
195
|
+
}
|
|
196
|
+
const versionFromUrlNoV = versionFromUrl.startsWith('v') ? versionFromUrl.slice(1) : versionFromUrl;
|
|
197
|
+
const metadataVersionNoVNormalized = deriveVersionNormalized(metadataVersionNoV);
|
|
198
|
+
if (metadataVersionNoVNormalized && versionFromUrlNoV === metadataVersionNoVNormalized) {
|
|
199
|
+
return { version: metadataVersion, metadata };
|
|
200
|
+
}
|
|
201
|
+
if (derivedNormalized && displayVersion === derivedNormalized) {
|
|
202
|
+
return { version: metadataVersion, metadata };
|
|
203
|
+
}
|
|
204
|
+
const displayVersionNormalized = deriveVersionNormalized(displayVersion);
|
|
205
|
+
if (displayVersionNormalized && metadataNormalized && displayVersionNormalized === metadataNormalized) {
|
|
206
|
+
return { version: metadataVersion, metadata };
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
return null;
|
|
210
|
+
}
|
|
211
|
+
/**
|
|
212
|
+
* Extract version from URL parameter by removing archive extensions
|
|
213
|
+
* Handles: .zip, .tar, .tar.gz, .tar.bz2
|
|
214
|
+
*/
|
|
215
|
+
function extractVersionFromParam(versionParam) {
|
|
216
|
+
if (!versionParam)
|
|
217
|
+
return '';
|
|
218
|
+
return versionParam
|
|
219
|
+
.replace(/\.tar\.bz2$/, '')
|
|
220
|
+
.replace(/\.tar\.gz$/, '')
|
|
221
|
+
.replace(/\.tar$/, '')
|
|
222
|
+
.replace(/\.zip$/, '');
|
|
223
|
+
}
|
|
224
|
+
export function normalizeVersionToDisplay(version) {
|
|
225
|
+
const patchMatch = version.match(/^(\d+\.\d+\.\d+)\.0-patch(\d+)$/);
|
|
226
|
+
if (patchMatch) {
|
|
227
|
+
const [, base, patch] = patchMatch;
|
|
228
|
+
return `${base}-p${patch}`;
|
|
229
|
+
}
|
|
230
|
+
if (version.match(/^\d+\.\d+\.\d+\.0$/)) {
|
|
231
|
+
return version.slice(0, -2);
|
|
232
|
+
}
|
|
233
|
+
const suffixMatch = version.match(/^(\d+\.\d+\.\d+)\.0(-.+)$/);
|
|
234
|
+
if (suffixMatch) {
|
|
235
|
+
const [, base, suffix] = suffixMatch;
|
|
236
|
+
return `${base}${suffix}`;
|
|
237
|
+
}
|
|
238
|
+
return version;
|
|
239
|
+
}
|
|
126
240
|
/**
|
|
127
241
|
* GET /dist/:repo_id/:vendor/:package/:version.zip
|
|
128
242
|
* OR
|
|
@@ -134,23 +248,30 @@ export async function distRoute(c) {
|
|
|
134
248
|
let vendor = c.req.param('vendor');
|
|
135
249
|
let pkgParam = c.req.param('package');
|
|
136
250
|
let packageName;
|
|
137
|
-
let version = c.req.param('version')
|
|
251
|
+
let version = extractVersionFromParam(c.req.param('version'));
|
|
138
252
|
const reference = c.req.param('reference');
|
|
139
253
|
const db = c.get('database');
|
|
140
|
-
// Handle mirror URL format: /dist/:package/:version/r:reference.zip
|
|
141
|
-
// In this case, repo_id and vendor/package split are not in the URL
|
|
142
254
|
if (!repoId && !vendor && pkgParam) {
|
|
143
255
|
const fullPackageName = pkgParam;
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
.select({ repo_id: packages.repo_id })
|
|
256
|
+
const displayVersion = normalizeVersionToDisplay(version);
|
|
257
|
+
let [pkg] = await db
|
|
258
|
+
.select({ repo_id: packages.repo_id, version: packages.version })
|
|
147
259
|
.from(packages)
|
|
148
260
|
.where(and(eq(packages.name, fullPackageName), eq(packages.version, version)))
|
|
149
261
|
.limit(1);
|
|
262
|
+
if (!pkg && displayVersion !== version) {
|
|
263
|
+
[pkg] = await db
|
|
264
|
+
.select({ repo_id: packages.repo_id, version: packages.version })
|
|
265
|
+
.from(packages)
|
|
266
|
+
.where(and(eq(packages.name, fullPackageName), eq(packages.version, displayVersion)))
|
|
267
|
+
.limit(1);
|
|
268
|
+
if (pkg) {
|
|
269
|
+
version = pkg.version;
|
|
270
|
+
}
|
|
271
|
+
}
|
|
150
272
|
if (pkg) {
|
|
151
273
|
repoId = pkg.repo_id;
|
|
152
274
|
packageName = fullPackageName;
|
|
153
|
-
// Split package name into vendor/package for compatibility
|
|
154
275
|
const parts = fullPackageName.split('/');
|
|
155
276
|
if (parts.length === 2) {
|
|
156
277
|
vendor = parts[0];
|
|
@@ -161,7 +282,87 @@ export async function distRoute(c) {
|
|
|
161
282
|
}
|
|
162
283
|
}
|
|
163
284
|
else {
|
|
164
|
-
|
|
285
|
+
const url = new URL(c.req.url);
|
|
286
|
+
const baseUrl = `${url.protocol}//${url.host}`;
|
|
287
|
+
const lazyLoadResult = await lazyLoadPackageFromRepositories(db, fullPackageName, c.env.ENCRYPTION_KEY, c.env.KV);
|
|
288
|
+
if (lazyLoadResult) {
|
|
289
|
+
const { packageData, repoId: foundRepoId } = lazyLoadResult;
|
|
290
|
+
const versions = packageData.packages?.[fullPackageName];
|
|
291
|
+
const normalizedVersions = normalizePackageVersions(versions);
|
|
292
|
+
const versionData = findVersionInMetadata(version, displayVersion, normalizedVersions);
|
|
293
|
+
if (versionData) {
|
|
294
|
+
const distType = versionData.metadata.dist?.type || 'zip';
|
|
295
|
+
const fileExt = getFileExtensionForDistType(distType);
|
|
296
|
+
const proxyDistUrl = `${baseUrl}/dist/${foundRepoId}/${fullPackageName}/${versionData.version}.${fileExt}`;
|
|
297
|
+
const sourceDistUrl = versionData.metadata.dist?.url || null;
|
|
298
|
+
await storePackageInDB(db, fullPackageName, versionData.version, versionData.metadata, foundRepoId, proxyDistUrl, sourceDistUrl);
|
|
299
|
+
repoId = foundRepoId;
|
|
300
|
+
packageName = fullPackageName;
|
|
301
|
+
version = versionData.version;
|
|
302
|
+
const parts = fullPackageName.split('/');
|
|
303
|
+
if (parts.length === 2) {
|
|
304
|
+
vendor = parts[0];
|
|
305
|
+
pkgParam = parts[1];
|
|
306
|
+
}
|
|
307
|
+
else {
|
|
308
|
+
return c.json({ error: 'Bad Request', message: 'Invalid package name format' }, 400);
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
else {
|
|
312
|
+
return c.json({ error: 'Not Found', message: 'Version not found in repository' }, 404);
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
else {
|
|
316
|
+
return c.json({ error: 'Not Found', message: 'Package not found in any repository' }, 404);
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
else if (!repoId && vendor && pkgParam) {
|
|
321
|
+
packageName = `${vendor}/${pkgParam}`;
|
|
322
|
+
const displayVersion = normalizeVersionToDisplay(version);
|
|
323
|
+
let [pkg] = await db
|
|
324
|
+
.select({ repo_id: packages.repo_id, version: packages.version })
|
|
325
|
+
.from(packages)
|
|
326
|
+
.where(and(eq(packages.name, packageName), eq(packages.version, version)))
|
|
327
|
+
.limit(1);
|
|
328
|
+
if (!pkg && displayVersion !== version) {
|
|
329
|
+
[pkg] = await db
|
|
330
|
+
.select({ repo_id: packages.repo_id, version: packages.version })
|
|
331
|
+
.from(packages)
|
|
332
|
+
.where(and(eq(packages.name, packageName), eq(packages.version, displayVersion)))
|
|
333
|
+
.limit(1);
|
|
334
|
+
if (pkg) {
|
|
335
|
+
version = pkg.version;
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
if (pkg) {
|
|
339
|
+
repoId = pkg.repo_id;
|
|
340
|
+
}
|
|
341
|
+
else {
|
|
342
|
+
const url = new URL(c.req.url);
|
|
343
|
+
const baseUrl = `${url.protocol}//${url.host}`;
|
|
344
|
+
const lazyLoadResult = await lazyLoadPackageFromRepositories(db, packageName, c.env.ENCRYPTION_KEY, c.env.KV);
|
|
345
|
+
if (lazyLoadResult) {
|
|
346
|
+
const { packageData, repoId: foundRepoId } = lazyLoadResult;
|
|
347
|
+
const versions = packageData.packages?.[packageName];
|
|
348
|
+
const normalizedVersions = normalizePackageVersions(versions);
|
|
349
|
+
const versionData = findVersionInMetadata(version, displayVersion, normalizedVersions);
|
|
350
|
+
if (versionData) {
|
|
351
|
+
const distType = versionData.metadata.dist?.type || 'zip';
|
|
352
|
+
const fileExt = getFileExtensionForDistType(distType);
|
|
353
|
+
const proxyDistUrl = `${baseUrl}/dist/${foundRepoId}/${packageName}/${versionData.version}.${fileExt}`;
|
|
354
|
+
const sourceDistUrl = versionData.metadata.dist?.url || null;
|
|
355
|
+
await storePackageInDB(db, packageName, versionData.version, versionData.metadata, foundRepoId, proxyDistUrl, sourceDistUrl);
|
|
356
|
+
repoId = foundRepoId;
|
|
357
|
+
version = versionData.version;
|
|
358
|
+
}
|
|
359
|
+
else {
|
|
360
|
+
return c.json({ error: 'Not Found', message: 'Version not found in repository' }, 404);
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
else {
|
|
364
|
+
return c.json({ error: 'Not Found', message: 'Package not found in any repository' }, 404);
|
|
365
|
+
}
|
|
165
366
|
}
|
|
166
367
|
}
|
|
167
368
|
else {
|
|
@@ -172,19 +373,14 @@ export async function distRoute(c) {
|
|
|
172
373
|
return c.json({ error: 'Bad Request', message: 'Missing required parameters' }, 400);
|
|
173
374
|
}
|
|
174
375
|
const storage = c.var.storage;
|
|
175
|
-
// Handle Packagist packages (repo_id = "packagist") - cache in storage
|
|
176
376
|
if (repoId === 'packagist') {
|
|
177
|
-
// Use public storage key for Packagist packages
|
|
178
377
|
const storageKey = buildStorageKey('public', 'packagist', packageName, version);
|
|
179
|
-
// Look up artifact in database
|
|
180
378
|
let artifact = (await db
|
|
181
379
|
.select()
|
|
182
380
|
.from(artifacts)
|
|
183
381
|
.where(and(eq(artifacts.repo_id, 'packagist'), eq(artifacts.package_name, packageName), eq(artifacts.version, version)))
|
|
184
382
|
.limit(1))[0];
|
|
185
|
-
// Check if artifact exists in storage
|
|
186
383
|
let stream = await storage.get(storageKey);
|
|
187
|
-
// If not in storage, fetch from Packagist and cache
|
|
188
384
|
if (!stream) {
|
|
189
385
|
const packagistUrl = `https://repo.packagist.org/p2/${packageName}.json`;
|
|
190
386
|
try {
|
|
@@ -197,10 +393,6 @@ export async function distRoute(c) {
|
|
|
197
393
|
const packagistData = await response.json();
|
|
198
394
|
const versions = packagistData.packages?.[packageName];
|
|
199
395
|
// Find version in array (Composer 2 p2 format) or dictionary (legacy format)
|
|
200
|
-
// Handle version normalization:
|
|
201
|
-
// - 1.5.9.0 → 1.5.9 (trailing .0)
|
|
202
|
-
// - 3.9999999.9999999.9999999-dev → 3.x-dev (dev version)
|
|
203
|
-
// - 2.4.8.0-patch3 → 2.4.8-p3 (patch alias)
|
|
204
396
|
const shortVersion = version.replace(/\.0$/, '');
|
|
205
397
|
const devMatch = version.match(/^(\d+)\.9999999\.9999999\.9999999-dev$/);
|
|
206
398
|
const xDevVersion = devMatch ? `${devMatch[1]}.x-dev` : null;
|
|
@@ -246,26 +438,58 @@ export async function distRoute(c) {
|
|
|
246
438
|
totalSize += value.length;
|
|
247
439
|
}
|
|
248
440
|
}
|
|
249
|
-
// Combine chunks into a single Uint8Array
|
|
250
441
|
const combined = new Uint8Array(totalSize);
|
|
251
442
|
let offset = 0;
|
|
252
443
|
for (const chunk of chunks) {
|
|
253
444
|
combined.set(chunk, offset);
|
|
254
445
|
offset += chunk.length;
|
|
255
446
|
}
|
|
256
|
-
|
|
447
|
+
let computedShasum;
|
|
448
|
+
if (!versionData.dist?.shasum) {
|
|
449
|
+
computedShasum = computeShasum(combined);
|
|
450
|
+
}
|
|
257
451
|
const arrayBuffer = combined.buffer.slice(combined.byteOffset, combined.byteOffset + combined.byteLength);
|
|
258
452
|
try {
|
|
259
453
|
await storage.put(storageKey, arrayBuffer);
|
|
260
454
|
const logger = getLogger();
|
|
261
455
|
logger.info('Successfully stored Packagist artifact in storage', { storageKey, size: totalSize, packageName, version });
|
|
262
|
-
|
|
456
|
+
if (computedShasum) {
|
|
457
|
+
c.executionCtx.waitUntil((async () => {
|
|
458
|
+
try {
|
|
459
|
+
const [pkg] = await db
|
|
460
|
+
.select({ metadata: packages.metadata })
|
|
461
|
+
.from(packages)
|
|
462
|
+
.where(and(eq(packages.repo_id, 'packagist'), eq(packages.name, packageName), eq(packages.version, version)))
|
|
463
|
+
.limit(1);
|
|
464
|
+
if (pkg?.metadata) {
|
|
465
|
+
try {
|
|
466
|
+
const metadata = JSON.parse(pkg.metadata);
|
|
467
|
+
if (!metadata.dist?.shasum) {
|
|
468
|
+
if (!metadata.dist) {
|
|
469
|
+
metadata.dist = {};
|
|
470
|
+
}
|
|
471
|
+
metadata.dist.shasum = computedShasum;
|
|
472
|
+
await db
|
|
473
|
+
.update(packages)
|
|
474
|
+
.set({ metadata: JSON.stringify(metadata) })
|
|
475
|
+
.where(and(eq(packages.repo_id, 'packagist'), eq(packages.name, packageName), eq(packages.version, version)));
|
|
476
|
+
}
|
|
477
|
+
}
|
|
478
|
+
catch {
|
|
479
|
+
// Ignore metadata update errors in background task
|
|
480
|
+
}
|
|
481
|
+
}
|
|
482
|
+
}
|
|
483
|
+
catch {
|
|
484
|
+
// Ignore background task errors
|
|
485
|
+
}
|
|
486
|
+
})());
|
|
487
|
+
}
|
|
263
488
|
c.executionCtx.waitUntil(extractAndStoreReadme(storage, combined, 'public', 'packagist', packageName, version));
|
|
264
489
|
}
|
|
265
490
|
catch (err) {
|
|
266
491
|
const logger = getLogger();
|
|
267
492
|
logger.error('Error storing Packagist artifact in storage', { storageKey, size: totalSize, packageName, version }, err instanceof Error ? err : new Error(String(err)));
|
|
268
|
-
// Don't fail the download if storage fails - still return the file to user
|
|
269
493
|
}
|
|
270
494
|
// Create or update artifact record
|
|
271
495
|
const now = Math.floor(Date.now() / 1000);
|
|
@@ -325,20 +549,14 @@ export async function distRoute(c) {
|
|
|
325
549
|
const logger = getLogger();
|
|
326
550
|
logger.error('Error proxying Packagist artifact', { packageName, version }, error instanceof Error ? error : new Error(String(error)));
|
|
327
551
|
}
|
|
328
|
-
// If Packagist fetch failed or version not found, try local DB fallback
|
|
329
552
|
if (!stream) {
|
|
330
|
-
// Fallback: Check if we have the package in our local DB
|
|
331
|
-
// This handles cases where metadata is cached in KV/DB (so composer found it)
|
|
332
|
-
// but upstream Packagist no longer lists it (so dist fetch failed)
|
|
333
553
|
try {
|
|
334
|
-
// Normalize version for DB lookup (remove trailing .0 if present)
|
|
335
554
|
const shortVersion = version.replace(/\.0$/, '');
|
|
336
555
|
let [pkg] = await db
|
|
337
556
|
.select()
|
|
338
557
|
.from(packages)
|
|
339
558
|
.where(and(eq(packages.repo_id, 'packagist'), eq(packages.name, packageName), or(eq(packages.version, version), eq(packages.version, shortVersion))))
|
|
340
559
|
.limit(1);
|
|
341
|
-
// Handle 3.999... -> 3.x-dev normalization for DB lookup
|
|
342
560
|
if (!pkg && version.includes('9999999') && version.endsWith('-dev')) {
|
|
343
561
|
const devMatch = version.match(/^(\d+)\.9999999\.9999999\.9999999-dev$/);
|
|
344
562
|
if (devMatch) {
|
|
@@ -353,7 +571,6 @@ export async function distRoute(c) {
|
|
|
353
571
|
if (pkg?.source_dist_url) {
|
|
354
572
|
const logger = getLogger();
|
|
355
573
|
logger.info('Found package in local DB fallback', { packageName, version, sourceDistUrl: pkg.source_dist_url });
|
|
356
|
-
// Download from source
|
|
357
574
|
const sourceResponse = await fetch(pkg.source_dist_url, {
|
|
358
575
|
headers: {
|
|
359
576
|
'User-Agent': COMPOSER_USER_AGENT,
|
|
@@ -375,18 +592,51 @@ export async function distRoute(c) {
|
|
|
375
592
|
totalSize += value.length;
|
|
376
593
|
}
|
|
377
594
|
}
|
|
378
|
-
// Combine chunks into a single Uint8Array
|
|
379
595
|
const combined = new Uint8Array(totalSize);
|
|
380
596
|
let offset = 0;
|
|
381
597
|
for (const chunk of chunks) {
|
|
382
598
|
combined.set(chunk, offset);
|
|
383
599
|
offset += chunk.length;
|
|
384
600
|
}
|
|
385
|
-
|
|
601
|
+
let computedShasum;
|
|
602
|
+
if (pkg?.metadata) {
|
|
603
|
+
try {
|
|
604
|
+
const metadata = JSON.parse(pkg.metadata);
|
|
605
|
+
if (!metadata.dist?.shasum) {
|
|
606
|
+
computedShasum = computeShasum(combined);
|
|
607
|
+
}
|
|
608
|
+
}
|
|
609
|
+
catch {
|
|
610
|
+
// If metadata parsing fails, compute shasum anyway
|
|
611
|
+
computedShasum = computeShasum(combined);
|
|
612
|
+
}
|
|
613
|
+
}
|
|
614
|
+
else {
|
|
615
|
+
computedShasum = computeShasum(combined);
|
|
616
|
+
}
|
|
386
617
|
const arrayBuffer = combined.buffer.slice(combined.byteOffset, combined.byteOffset + combined.byteLength);
|
|
387
618
|
try {
|
|
388
619
|
await storage.put(storageKey, arrayBuffer);
|
|
389
|
-
|
|
620
|
+
if (computedShasum && pkg) {
|
|
621
|
+
c.executionCtx.waitUntil((async () => {
|
|
622
|
+
try {
|
|
623
|
+
const metadata = pkg.metadata ? JSON.parse(pkg.metadata) : {};
|
|
624
|
+
if (!metadata.dist?.shasum) {
|
|
625
|
+
if (!metadata.dist) {
|
|
626
|
+
metadata.dist = {};
|
|
627
|
+
}
|
|
628
|
+
metadata.dist.shasum = computedShasum;
|
|
629
|
+
await db
|
|
630
|
+
.update(packages)
|
|
631
|
+
.set({ metadata: JSON.stringify(metadata) })
|
|
632
|
+
.where(and(eq(packages.repo_id, 'packagist'), eq(packages.name, packageName), eq(packages.version, version)));
|
|
633
|
+
}
|
|
634
|
+
}
|
|
635
|
+
catch {
|
|
636
|
+
// Ignore metadata update errors in background task
|
|
637
|
+
}
|
|
638
|
+
})());
|
|
639
|
+
}
|
|
390
640
|
c.executionCtx.waitUntil(extractAndStoreReadme(storage, combined, 'public', 'packagist', packageName, version));
|
|
391
641
|
}
|
|
392
642
|
catch (err) {
|
|
@@ -463,11 +713,32 @@ export async function distRoute(c) {
|
|
|
463
713
|
// Run in background to not block the response
|
|
464
714
|
c.executionCtx.waitUntil(updateDownloadCount());
|
|
465
715
|
}
|
|
716
|
+
// Get dist.type from package metadata to determine Content-Type
|
|
717
|
+
let distType = null;
|
|
718
|
+
if (artifact) {
|
|
719
|
+
// Try to get dist.type from stored package metadata
|
|
720
|
+
const [pkgForType] = await db
|
|
721
|
+
.select({ metadata: packages.metadata })
|
|
722
|
+
.from(packages)
|
|
723
|
+
.where(and(eq(packages.repo_id, 'packagist'), eq(packages.name, packageName), eq(packages.version, version)))
|
|
724
|
+
.limit(1);
|
|
725
|
+
if (pkgForType?.metadata) {
|
|
726
|
+
try {
|
|
727
|
+
const metadata = JSON.parse(pkgForType.metadata);
|
|
728
|
+
distType = metadata.dist?.type || null;
|
|
729
|
+
}
|
|
730
|
+
catch {
|
|
731
|
+
// Ignore parse errors
|
|
732
|
+
}
|
|
733
|
+
}
|
|
734
|
+
}
|
|
466
735
|
// Build response headers
|
|
467
736
|
const headers = new Headers();
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
737
|
+
const contentType = getContentTypeForDistType(distType);
|
|
738
|
+
const fileExt = getFileExtensionForDistType(distType);
|
|
739
|
+
headers.set('Content-Type', contentType);
|
|
740
|
+
// Format filename as vendor--module-name--version.{ext} (replace / with --)
|
|
741
|
+
const filename = `${packageName.replace('/', '--')}--${version}.${fileExt}`;
|
|
471
742
|
headers.set('Content-Disposition', `attachment; filename="${filename}"`);
|
|
472
743
|
if (artifact?.size) {
|
|
473
744
|
headers.set('Content-Length', String(artifact.size));
|
|
@@ -495,20 +766,16 @@ export async function distRoute(c) {
|
|
|
495
766
|
}
|
|
496
767
|
// For private repositories, use private storage key
|
|
497
768
|
const storageKey = buildStorageKey('private', repoId, packageName, version);
|
|
498
|
-
// Look up artifact in database
|
|
499
769
|
let artifact = (await db
|
|
500
770
|
.select()
|
|
501
771
|
.from(artifacts)
|
|
502
772
|
.where(and(eq(artifacts.repo_id, repoId), eq(artifacts.package_name, packageName), eq(artifacts.version, version)))
|
|
503
773
|
.limit(1))[0];
|
|
504
|
-
// Look up package to get source_dist_url (needed for on-demand mirroring)
|
|
505
774
|
let [pkg] = await db
|
|
506
775
|
.select()
|
|
507
776
|
.from(packages)
|
|
508
777
|
.where(and(eq(packages.repo_id, repoId), eq(packages.name, packageName), eq(packages.version, version)))
|
|
509
778
|
.limit(1);
|
|
510
|
-
// If not found with specific repo_id, try to find it with any repo_id
|
|
511
|
-
// This handles cases where package was stored but repo_id doesn't match
|
|
512
779
|
if (!pkg) {
|
|
513
780
|
const [pkgAnyRepo] = await db
|
|
514
781
|
.select()
|
|
@@ -522,7 +789,6 @@ export async function distRoute(c) {
|
|
|
522
789
|
}
|
|
523
790
|
}
|
|
524
791
|
if (!pkg) {
|
|
525
|
-
// Package not found in database - try to fetch from repository directly
|
|
526
792
|
// This handles race conditions where metadata wasn't stored yet
|
|
527
793
|
const [repo] = await db
|
|
528
794
|
.select()
|
|
@@ -549,8 +815,9 @@ export async function distRoute(c) {
|
|
|
549
815
|
const credentials = JSON.parse(credentialsJson);
|
|
550
816
|
const sourceResponse = await downloadFromSource(sourceDistUrl, repo.credential_type, credentials);
|
|
551
817
|
if (sourceResponse.ok && sourceResponse.body) {
|
|
818
|
+
const distType = packageVersion.dist?.type || null;
|
|
552
819
|
const headers = new Headers();
|
|
553
|
-
headers.set('Content-Type',
|
|
820
|
+
headers.set('Content-Type', getContentTypeForDistType(distType));
|
|
554
821
|
headers.set('Cache-Control', 'public, max-age=3600');
|
|
555
822
|
return new Response(sourceResponse.body, {
|
|
556
823
|
status: 200,
|
|
@@ -564,7 +831,6 @@ export async function distRoute(c) {
|
|
|
564
831
|
logger.error('Error fetching package from upstream', { packageName, version, repoId }, error instanceof Error ? error : new Error(String(error)));
|
|
565
832
|
}
|
|
566
833
|
}
|
|
567
|
-
// Package not found in database and couldn't fetch from upstream
|
|
568
834
|
const logger = getLogger();
|
|
569
835
|
logger.warn('Package not found in DB for repo', { packageName, version, repoId, note: 'This may indicate a race condition or missing package metadata' });
|
|
570
836
|
return c.json({
|
|
@@ -572,7 +838,6 @@ export async function distRoute(c) {
|
|
|
572
838
|
message: 'Package not found. The package metadata may not be available yet. Try refreshing package metadata.'
|
|
573
839
|
}, 404);
|
|
574
840
|
}
|
|
575
|
-
// Check conditional request (If-Modified-Since) if artifact exists
|
|
576
841
|
if (artifact) {
|
|
577
842
|
const ifModifiedSince = c.req.header('If-Modified-Since');
|
|
578
843
|
if (ifModifiedSince && artifact.created_at) {
|
|
@@ -583,23 +848,18 @@ export async function distRoute(c) {
|
|
|
583
848
|
}
|
|
584
849
|
}
|
|
585
850
|
}
|
|
586
|
-
// Get artifact from storage
|
|
587
851
|
let stream = await storage.get(storageKey);
|
|
588
|
-
// If artifact not in storage, try on-demand mirroring
|
|
589
852
|
if (!stream) {
|
|
590
|
-
// Validate source_dist_url exists and is a valid URL
|
|
591
853
|
if (!pkg.source_dist_url) {
|
|
592
854
|
const logger = getLogger();
|
|
593
855
|
logger.error('Package found but source_dist_url is missing', { packageName, version, repoId });
|
|
594
856
|
return c.json({ error: 'Not Found', message: 'Artifact file not found and source URL unavailable. Please re-sync the repository.' }, 404);
|
|
595
857
|
}
|
|
596
|
-
// Validate it's actually a URL (not a placeholder or column name)
|
|
597
858
|
if (!pkg.source_dist_url.startsWith('http://') && !pkg.source_dist_url.startsWith('https://')) {
|
|
598
859
|
const logger = getLogger();
|
|
599
860
|
logger.error('Package has invalid source_dist_url', { packageName, version, repoId, sourceDistUrl: pkg.source_dist_url });
|
|
600
861
|
return c.json({ error: 'Not Found', message: 'Invalid source URL. Please re-sync the repository to update package metadata.' }, 404);
|
|
601
862
|
}
|
|
602
|
-
// Get repository for credentials
|
|
603
863
|
const [repo] = await db
|
|
604
864
|
.select()
|
|
605
865
|
.from(repositories)
|
|
@@ -609,18 +869,13 @@ export async function distRoute(c) {
|
|
|
609
869
|
return c.json({ error: 'Not Found', message: 'Repository not found' }, 404);
|
|
610
870
|
}
|
|
611
871
|
try {
|
|
612
|
-
// Decrypt credentials
|
|
613
872
|
const credentialsJson = await decryptCredentials(repo.auth_credentials, c.env.ENCRYPTION_KEY);
|
|
614
873
|
const credentials = JSON.parse(credentialsJson);
|
|
615
|
-
// Download from source with authentication
|
|
616
874
|
const sourceResponse = await downloadFromSource(pkg.source_dist_url, repo.credential_type, credentials);
|
|
617
|
-
// Read the response body as a stream
|
|
618
875
|
const sourceStream = sourceResponse.body;
|
|
619
876
|
if (!sourceStream) {
|
|
620
877
|
throw new Error('Source response has no body');
|
|
621
878
|
}
|
|
622
|
-
// Store in R2 storage (non-blocking for response, but we need to wait for it)
|
|
623
|
-
// We'll stream to user while storing in background
|
|
624
879
|
const chunks = [];
|
|
625
880
|
const reader = sourceStream.getReader();
|
|
626
881
|
let totalSize = 0;
|
|
@@ -634,25 +889,53 @@ export async function distRoute(c) {
|
|
|
634
889
|
totalSize += value.length;
|
|
635
890
|
}
|
|
636
891
|
}
|
|
637
|
-
// Combine chunks
|
|
638
892
|
const combined = new Uint8Array(totalSize);
|
|
639
893
|
let offset = 0;
|
|
640
894
|
for (const chunk of chunks) {
|
|
641
895
|
combined.set(chunk, offset);
|
|
642
896
|
offset += chunk.length;
|
|
643
897
|
}
|
|
644
|
-
// Create stream for response
|
|
645
898
|
stream = new Response(combined).body;
|
|
646
|
-
|
|
899
|
+
let computedShasum;
|
|
900
|
+
if (pkg?.metadata) {
|
|
901
|
+
try {
|
|
902
|
+
const metadata = JSON.parse(pkg.metadata);
|
|
903
|
+
if (!metadata.dist?.shasum) {
|
|
904
|
+
computedShasum = computeShasum(combined);
|
|
905
|
+
}
|
|
906
|
+
}
|
|
907
|
+
catch {
|
|
908
|
+
// If metadata parsing fails, compute shasum anyway
|
|
909
|
+
computedShasum = computeShasum(combined);
|
|
910
|
+
}
|
|
911
|
+
}
|
|
912
|
+
else {
|
|
913
|
+
computedShasum = computeShasum(combined);
|
|
914
|
+
}
|
|
647
915
|
c.executionCtx.waitUntil((async () => {
|
|
648
916
|
try {
|
|
649
|
-
// Store artifact
|
|
650
|
-
// Convert to ArrayBuffer
|
|
651
917
|
const arrayBuffer = combined.buffer.slice(combined.byteOffset, combined.byteOffset + combined.byteLength);
|
|
652
918
|
await storage.put(storageKey, arrayBuffer);
|
|
653
919
|
const logger = getLogger();
|
|
654
920
|
logger.info('Successfully stored artifact on-demand', { storageKey, size: totalSize });
|
|
655
|
-
|
|
921
|
+
if (computedShasum && pkg) {
|
|
922
|
+
try {
|
|
923
|
+
const metadata = pkg.metadata ? JSON.parse(pkg.metadata) : {};
|
|
924
|
+
if (!metadata.dist?.shasum) {
|
|
925
|
+
if (!metadata.dist) {
|
|
926
|
+
metadata.dist = {};
|
|
927
|
+
}
|
|
928
|
+
metadata.dist.shasum = computedShasum;
|
|
929
|
+
await db
|
|
930
|
+
.update(packages)
|
|
931
|
+
.set({ metadata: JSON.stringify(metadata) })
|
|
932
|
+
.where(and(eq(packages.repo_id, repoId), eq(packages.name, packageName), eq(packages.version, version)));
|
|
933
|
+
}
|
|
934
|
+
}
|
|
935
|
+
catch {
|
|
936
|
+
// Ignore metadata update errors in background task
|
|
937
|
+
}
|
|
938
|
+
}
|
|
656
939
|
const now = Math.floor(Date.now() / 1000);
|
|
657
940
|
if (artifact) {
|
|
658
941
|
await db
|
|
@@ -675,7 +958,6 @@ export async function distRoute(c) {
|
|
|
675
958
|
download_count: 0,
|
|
676
959
|
created_at: now,
|
|
677
960
|
}).onConflictDoNothing();
|
|
678
|
-
// Update local artifact for download count
|
|
679
961
|
artifact = {
|
|
680
962
|
id: artifactId,
|
|
681
963
|
repo_id: repoId,
|
|
@@ -688,7 +970,6 @@ export async function distRoute(c) {
|
|
|
688
970
|
last_downloaded_at: null,
|
|
689
971
|
};
|
|
690
972
|
}
|
|
691
|
-
// Proactively extract and store README
|
|
692
973
|
await extractAndStoreReadme(storage, combined, 'private', repoId, packageName, version);
|
|
693
974
|
}
|
|
694
975
|
catch (error) {
|
|
@@ -727,10 +1008,21 @@ export async function distRoute(c) {
|
|
|
727
1008
|
}
|
|
728
1009
|
})());
|
|
729
1010
|
}
|
|
730
|
-
|
|
1011
|
+
let distType = null;
|
|
1012
|
+
if (pkg?.metadata) {
|
|
1013
|
+
try {
|
|
1014
|
+
const metadata = JSON.parse(pkg.metadata);
|
|
1015
|
+
distType = metadata.dist?.type || null;
|
|
1016
|
+
}
|
|
1017
|
+
catch {
|
|
1018
|
+
// Ignore invalid JSON; fallback to null distType
|
|
1019
|
+
}
|
|
1020
|
+
}
|
|
731
1021
|
const headers = new Headers();
|
|
732
|
-
|
|
733
|
-
const
|
|
1022
|
+
const contentType = getContentTypeForDistType(distType);
|
|
1023
|
+
const fileExt = getFileExtensionForDistType(distType);
|
|
1024
|
+
headers.set('Content-Type', contentType);
|
|
1025
|
+
const filename = `${packageName.replace('/', '--')}--${version}.${fileExt}`;
|
|
734
1026
|
headers.set('Content-Disposition', `attachment; filename="${filename}"`);
|
|
735
1027
|
if (artifact?.size) {
|
|
736
1028
|
headers.set('Content-Length', String(artifact.size));
|
|
@@ -738,9 +1030,7 @@ export async function distRoute(c) {
|
|
|
738
1030
|
if (artifact?.created_at) {
|
|
739
1031
|
headers.set('Last-Modified', new Date(artifact.created_at * 1000).toUTCString());
|
|
740
1032
|
}
|
|
741
|
-
// Cache settings
|
|
742
1033
|
headers.set('Cache-Control', 'public, max-age=31536000, immutable');
|
|
743
|
-
// Track package download analytics
|
|
744
1034
|
const analytics = getAnalytics();
|
|
745
1035
|
const requestId = c.get('requestId');
|
|
746
1036
|
analytics.trackPackageDownload({
|
|
@@ -756,7 +1046,6 @@ export async function distRoute(c) {
|
|
|
756
1046
|
headers,
|
|
757
1047
|
});
|
|
758
1048
|
}
|
|
759
|
-
// Aliases for specific route patterns handled by the same function
|
|
760
1049
|
export const distMirrorRoute = distRoute;
|
|
761
1050
|
export const distLockfileRoute = distRoute;
|
|
762
1051
|
//# sourceMappingURL=dist.js.map
|