@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.
@@ -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')?.replace('.zip', '') || '';
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
- // Look up repo_id from package name
145
- const [pkg] = await db
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
- return c.json({ error: 'Not Found', message: 'Package not found' }, 404);
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
- // Store in storage (synchronous)
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
- // Proactively extract and store README in background
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
- // Store in storage (synchronous)
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
- // Proactively extract and store README
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
- headers.set('Content-Type', 'application/zip');
469
- // Format filename as vendor--module-name--version.zip (replace / with --)
470
- const filename = `${packageName.replace('/', '--')}--${version}.zip`;
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', 'application/zip');
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
- // Store in storage (background)
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
- // Create or update artifact record
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
- // Build response
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
- headers.set('Content-Type', 'application/zip');
733
- const filename = `${packageName.replace('/', '--')}--${version}.zip`;
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