inup 1.4.10 → 1.5.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.
Files changed (50) hide show
  1. package/README.md +1 -7
  2. package/dist/cli.js +2 -1
  3. package/dist/config/constants.js +1 -2
  4. package/dist/config/project-config.js +6 -0
  5. package/dist/core/package-detector.js +163 -89
  6. package/dist/core/upgrade-runner.js +68 -16
  7. package/dist/features/changelog/clients/github-client.js +134 -0
  8. package/dist/features/changelog/clients/npm-registry-client.js +53 -0
  9. package/dist/features/changelog/index.js +19 -0
  10. package/dist/features/changelog/parsers/changelog-parser.js +68 -0
  11. package/dist/features/changelog/parsers/github-release-html-parser.js +61 -0
  12. package/dist/features/changelog/parsers/package-metadata.js +34 -0
  13. package/dist/features/changelog/parsers/repository-ref.js +26 -0
  14. package/dist/features/changelog/services/changelog-service.js +30 -0
  15. package/dist/features/changelog/services/package-metadata-service.js +108 -0
  16. package/dist/features/changelog/services/release-notes-service.js +180 -0
  17. package/dist/features/changelog/types/changelog.types.js +3 -0
  18. package/dist/interactive-ui.js +343 -161
  19. package/dist/services/background-audit.js +60 -0
  20. package/dist/services/index.js +3 -3
  21. package/dist/services/jsdelivr-registry.js +92 -176
  22. package/dist/services/npm-registry.js +97 -27
  23. package/dist/services/vulnerability-checker.js +133 -0
  24. package/dist/ui/controllers/index.js +8 -0
  25. package/dist/ui/controllers/package-info-modal-controller.js +237 -0
  26. package/dist/ui/controllers/vulnerability-audit-controller.js +82 -0
  27. package/dist/ui/index.js +3 -0
  28. package/dist/ui/input-handler.js +41 -10
  29. package/dist/ui/modal/index.js +22 -0
  30. package/dist/ui/modal/layout.js +84 -0
  31. package/dist/ui/modal/package-info-sections.js +327 -0
  32. package/dist/ui/modal/package-info.js +147 -0
  33. package/dist/ui/modal/theme-selector.js +46 -0
  34. package/dist/ui/modal/types.js +3 -0
  35. package/dist/ui/presenters/index.js +11 -0
  36. package/dist/ui/presenters/vulnerability.js +76 -0
  37. package/dist/ui/renderer/index.js +9 -11
  38. package/dist/ui/renderer/package-list.js +166 -66
  39. package/dist/ui/state/filter-manager.js +17 -2
  40. package/dist/ui/state/modal-manager.js +48 -6
  41. package/dist/ui/state/state-manager.js +49 -12
  42. package/dist/ui/utils/cursor.js +18 -0
  43. package/dist/ui/utils/index.js +8 -1
  44. package/dist/ui/utils/terminal-input.js +82 -0
  45. package/dist/ui/utils/text.js +75 -0
  46. package/dist/ui/utils/version.js +3 -2
  47. package/package.json +7 -11
  48. package/dist/services/changelog-fetcher.js +0 -190
  49. package/dist/ui/renderer/modal.js +0 -190
  50. package/dist/ui/renderer/theme-selector.js +0 -83
@@ -0,0 +1,60 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.BackgroundAuditTracker = void 0;
4
+ const utils_1 = require("../utils");
5
+ class BackgroundAuditTracker {
6
+ constructor() {
7
+ this.pending = new Map();
8
+ this.inFlight = new Set();
9
+ this.completed = new Set();
10
+ }
11
+ enqueue(packages) {
12
+ let added = 0;
13
+ for (const pkg of packages) {
14
+ if (!pkg.name || !pkg.version)
15
+ continue;
16
+ if (this.pending.has(pkg.name) ||
17
+ this.inFlight.has(pkg.name) ||
18
+ this.completed.has(pkg.name)) {
19
+ continue;
20
+ }
21
+ this.pending.set(pkg.name, pkg.version);
22
+ added++;
23
+ }
24
+ if (added > 0) {
25
+ utils_1.debugLog.info('background-audit', `queued ${added} package(s)`);
26
+ }
27
+ return added;
28
+ }
29
+ reserveNextBatch(limit = 20) {
30
+ const packages = new Map();
31
+ const packageNames = [];
32
+ for (const [name, version] of this.pending) {
33
+ packages.set(name, version);
34
+ packageNames.push(name);
35
+ this.pending.delete(name);
36
+ this.inFlight.add(name);
37
+ if (packageNames.length >= limit) {
38
+ break;
39
+ }
40
+ }
41
+ return { packages, packageNames };
42
+ }
43
+ markCompleted(packageNames) {
44
+ for (const packageName of packageNames) {
45
+ this.inFlight.delete(packageName);
46
+ this.completed.add(packageName);
47
+ }
48
+ }
49
+ getProgress() {
50
+ const total = this.pending.size + this.inFlight.size + this.completed.size;
51
+ return {
52
+ completed: this.completed.size,
53
+ total,
54
+ isRunning: this.pending.size > 0 || this.inFlight.size > 0,
55
+ hasData: this.completed.size > 0,
56
+ };
57
+ }
58
+ }
59
+ exports.BackgroundAuditTracker = BackgroundAuditTracker;
60
+ //# sourceMappingURL=background-audit.js.map
@@ -19,8 +19,8 @@ var __exportStar = (this && this.__exportStar) || function(m, exports) {
19
19
  Object.defineProperty(exports, "__esModule", { value: true });
20
20
  __exportStar(require("./npm-registry"), exports);
21
21
  __exportStar(require("./jsdelivr-registry"), exports);
22
- __exportStar(require("./changelog-fetcher"), exports);
22
+ __exportStar(require("../features/changelog"), exports);
23
23
  __exportStar(require("./version-checker"), exports);
24
- __exportStar(require("./persistent-cache"), exports);
25
- __exportStar(require("./cache-manager"), exports);
24
+ __exportStar(require("./vulnerability-checker"), exports);
25
+ __exportStar(require("./background-audit"), exports);
26
26
  //# sourceMappingURL=index.js.map
@@ -33,22 +33,18 @@ var __importStar = (this && this.__importStar) || (function () {
33
33
  };
34
34
  })();
35
35
  Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.fetchExactPackageManifest = fetchExactPackageManifest;
36
37
  exports.getAllPackageDataFromJsdelivr = getAllPackageDataFromJsdelivr;
37
- exports.clearJsdelivrPackageCache = clearJsdelivrPackageCache;
38
38
  exports.closeJsdelivrPool = closeJsdelivrPool;
39
+ exports.clearExactManifestCache = clearExactManifestCache;
39
40
  const undici_1 = require("undici");
40
41
  const semver = __importStar(require("semver"));
41
42
  const config_1 = require("../config");
42
- const npm_registry_1 = require("./npm-registry");
43
- const cache_manager_1 = require("./cache-manager");
44
- const utils_1 = require("../ui/utils");
45
- const utils_2 = require("../utils");
46
- // Batch configuration for progressive loading
47
- const BATCH_SIZE = 5;
48
- const BATCH_TIMEOUT_MS = 500;
43
+ const utils_1 = require("../utils");
49
44
  const DEFAULT_JSDELIVR_RETRY_TIMEOUT_MS = 2000;
50
45
  const DEFAULT_JSDELIVR_POOL_TIMEOUT_MS = 60000;
51
46
  const MIN_JSDELIVR_CONNECT_TIMEOUT_MS = 500;
47
+ const EXACT_VERSION_PATTERN = /^[0-9]+\.[0-9]+\.[0-9]+(?:-[0-9A-Za-z.-]+)?(?:\+[0-9A-Za-z.-]+)?$/;
52
48
  const toPositiveInteger = (value) => {
53
49
  if (!Number.isFinite(value) || value <= 0) {
54
50
  return null;
@@ -222,14 +218,9 @@ const sortVersionsDescending = (versions) => {
222
218
  };
223
219
  const isExpectedTransientError = (error) => isTimeoutError(error) || isTransientNetworkError(error);
224
220
  /**
225
- * Fetches package.json from jsdelivr CDN for a specific version tag using undici pool.
226
- * Uses connection pooling and keep-alive for maximum performance.
227
- * Retries on transient failures while keeping a short fallback budget.
228
- * @param packageName - The npm package name
229
- * @param versionTag - The version tag (e.g., '14', 'latest')
230
- * @returns The package.json content or null if not found
221
+ * Fetches a package.json manifest from jsDelivr for a version tag.
231
222
  */
232
- async function fetchPackageJsonFromJsdelivr(packageName, versionTag) {
223
+ async function fetchPackageManifestFromJsdelivr(packageName, versionTag) {
233
224
  const url = `${config_1.JSDELIVR_CDN_URL}/${encodeURIComponent(packageName)}@${versionTag}/package.json`;
234
225
  for (let attempt = 0; attempt < RETRY_TIMEOUTS.length; attempt++) {
235
226
  const timeout = RETRY_TIMEOUTS[attempt];
@@ -249,26 +240,28 @@ async function fetchPackageJsonFromJsdelivr(packageName, versionTag) {
249
240
  await consumeBodySafely(body);
250
241
  if (isRetryableStatus(statusCode) && attempt < RETRY_TIMEOUTS.length - 1) {
251
242
  const delay = getRetryDelay(attempt, headers);
252
- utils_2.debugLog.warn('jsdelivr', `${packageName}@${versionTag} HTTP ${statusCode}, retry ${attempt + 1} in ${delay}ms`);
243
+ utils_1.debugLog.warn('jsdelivr', `${packageName}@${versionTag} HTTP ${statusCode}, retry ${attempt + 1} in ${delay}ms`);
253
244
  if (delay > 0) {
254
245
  await sleep(delay);
255
246
  }
256
247
  continue;
257
248
  }
258
- utils_2.debugLog.warn('jsdelivr', `${packageName}@${versionTag} HTTP ${statusCode}, no more retries`);
249
+ utils_1.debugLog.warn('jsdelivr', `${packageName}@${versionTag} HTTP ${statusCode}, no more retries`);
259
250
  return null;
260
251
  }
261
252
  const text = await body.text();
262
253
  const data = JSON.parse(text);
263
- const version = typeof data.version === 'string' ? data.version.trim() : '';
264
- utils_2.debugLog.perf('jsdelivr', `fetch ${packageName}@${versionTag} → ${version || 'no version'}`, tReq);
265
- return version ? { version } : null;
254
+ if (!data || typeof data !== 'object' || Array.isArray(data)) {
255
+ return null;
256
+ }
257
+ utils_1.debugLog.perf('jsdelivr', `fetch manifest ${packageName}@${versionTag}`, tReq);
258
+ return data;
266
259
  }
267
260
  catch (error) {
268
261
  if ((isTimeoutError(error) || isTransientNetworkError(error)) &&
269
262
  attempt < RETRY_TIMEOUTS.length - 1) {
270
263
  const delay = getRetryDelay(attempt);
271
- utils_2.debugLog.warn('jsdelivr', `${packageName}@${versionTag} transient error on attempt ${attempt + 1}, retry in ${delay}ms`, error);
264
+ utils_1.debugLog.warn('jsdelivr', `${packageName}@${versionTag} transient error on attempt ${attempt + 1}, retry in ${delay}ms`, error);
272
265
  if (delay > 0) {
273
266
  await sleep(delay);
274
267
  }
@@ -277,203 +270,126 @@ async function fetchPackageJsonFromJsdelivr(packageName, versionTag) {
277
270
  if (!isExpectedTransientError(error)) {
278
271
  // Unexpected errors are logged for observability.
279
272
  console.error(`jsDelivr fetch failed for ${packageName}@${versionTag} on attempt ${attempt + 1}/${RETRY_TIMEOUTS.length}`, error);
280
- utils_2.debugLog.error('jsdelivr', `unexpected error for ${packageName}@${versionTag} attempt ${attempt + 1}`, error);
273
+ utils_1.debugLog.error('jsdelivr', `unexpected error for ${packageName}@${versionTag} attempt ${attempt + 1}`, error);
281
274
  }
282
275
  else {
283
- utils_2.debugLog.warn('jsdelivr', `${packageName}@${versionTag} exhausted retries`, error);
276
+ utils_1.debugLog.warn('jsdelivr', `${packageName}@${versionTag} exhausted retries`, error);
284
277
  }
285
278
  return null;
286
279
  }
287
280
  }
288
281
  return null;
289
282
  }
290
- /**
291
- * Fetches package version data from jsdelivr CDN for multiple packages.
292
- * Uses undici connection pool for blazing fast performance with connection reuse.
293
- * Falls back to npm registry immediately when jsdelivr fails (interleaved, not sequential).
294
- * Supports batched callbacks for progressive UI updates.
295
- * @param packageNames - Array of package names to fetch
296
- * @param currentVersions - Optional map of package names to their current versions
297
- * @param onProgress - Optional progress callback
298
- * @param onBatchReady - Optional callback for batch updates (fires every BATCH_SIZE packages or BATCH_TIMEOUT_MS)
299
- * @returns Map of package names to their version data
300
- */
301
- async function getAllPackageDataFromJsdelivr(packageNames, currentVersions, onProgress, onBatchReady) {
283
+ async function fetchPackageManifestFromNpmRegistry(packageName, version) {
284
+ const controller = new AbortController();
285
+ const timeoutId = setTimeout(() => controller.abort(), config_1.REQUEST_TIMEOUT);
286
+ try {
287
+ const response = await fetch(`${config_1.NPM_REGISTRY_URL}/${encodeURIComponent(packageName)}/${encodeURIComponent(version)}`, {
288
+ method: 'GET',
289
+ headers: {
290
+ accept: 'application/json',
291
+ },
292
+ signal: controller.signal,
293
+ });
294
+ if (!response.ok) {
295
+ return null;
296
+ }
297
+ return (await response.json());
298
+ }
299
+ catch (error) {
300
+ utils_1.debugLog.warn('npm-registry', `exact manifest fallback failed for ${packageName}@${version}`, error);
301
+ return null;
302
+ }
303
+ finally {
304
+ clearTimeout(timeoutId);
305
+ }
306
+ }
307
+ const inFlightManifests = new Map();
308
+ async function fetchExactPackageManifest(packageName, version) {
309
+ const normalizedVersion = version.trim();
310
+ if (!EXACT_VERSION_PATTERN.test(normalizedVersion) || !semver.valid(normalizedVersion)) {
311
+ utils_1.debugLog.warn('jsdelivr', `skipping non-exact version lookup for ${packageName}@${version}`);
312
+ return null;
313
+ }
314
+ const cacheKey = `${packageName}@${normalizedVersion}`;
315
+ const inFlight = inFlightManifests.get(cacheKey);
316
+ if (inFlight) {
317
+ return await inFlight;
318
+ }
319
+ const lookupPromise = (async () => {
320
+ const jsdelivrManifest = await fetchPackageManifestFromJsdelivr(packageName, normalizedVersion);
321
+ if (jsdelivrManifest) {
322
+ return jsdelivrManifest;
323
+ }
324
+ return await fetchPackageManifestFromNpmRegistry(packageName, normalizedVersion);
325
+ })().finally(() => {
326
+ inFlightManifests.delete(cacheKey);
327
+ });
328
+ inFlightManifests.set(cacheKey, lookupPromise);
329
+ return await lookupPromise;
330
+ }
331
+ async function getAllPackageDataFromJsdelivr(packageNames, currentVersions, onProgress) {
302
332
  const packageData = new Map();
303
333
  if (packageNames.length === 0) {
304
334
  return packageData;
305
335
  }
306
336
  const total = packageNames.length;
307
337
  let completedCount = 0;
308
- let progressCallback = onProgress;
309
- let batchReadyCallback = onBatchReady;
310
- // Batch buffer for progressive updates
311
- let batchBuffer = [];
312
- let batchTimer = null;
313
- const emitProgress = (packageName, completed, packageTotal) => {
314
- if (!progressCallback) {
315
- return;
316
- }
317
- try {
318
- progressCallback(packageName, completed, packageTotal);
319
- }
320
- catch (error) {
321
- console.error('Progress callback failed, disabling progress updates for this run.', error);
322
- progressCallback = undefined;
323
- }
324
- };
325
- const emitBatch = (batch) => {
326
- if (!batchReadyCallback) {
327
- return;
328
- }
329
- try {
330
- batchReadyCallback(batch);
331
- }
332
- catch (error) {
333
- console.error('Batch callback failed, disabling batch updates for this run.', error);
334
- batchReadyCallback = undefined;
335
- }
336
- };
337
- // Helper to flush the current batch
338
- const flushBatch = () => {
339
- if (batchBuffer.length > 0) {
340
- const batch = [...batchBuffer];
341
- batchBuffer = [];
342
- emitBatch(batch);
343
- }
344
- if (batchTimer) {
345
- clearTimeout(batchTimer);
346
- batchTimer = null;
347
- }
348
- };
349
- // Helper to add package to batch and flush if needed
350
- const addToBatch = (packageName, data) => {
351
- if (!batchReadyCallback) {
352
- return;
353
- }
354
- batchBuffer.push({ name: packageName, data });
355
- // Flush if batch is full
356
- if (batchBuffer.length >= BATCH_SIZE) {
357
- flushBatch();
358
- }
359
- else if (!batchTimer) {
360
- // Set timer to flush batch after timeout
361
- batchTimer = setTimeout(flushBatch, BATCH_TIMEOUT_MS);
362
- }
363
- };
364
- // Process individual package fetch with immediate npm fallback on failure
365
338
  const inFlightLookups = new Map();
366
- const fetchFromNpmFallback = async (packageName) => {
367
- const tFallback = Date.now();
368
- utils_2.debugLog.info('jsdelivr', `falling back to npm registry for ${packageName}`);
369
- try {
370
- const npmData = await (0, npm_registry_1.getAllPackageData)([packageName]);
371
- const result = npmData.get(packageName) ?? null;
372
- if (result) {
373
- cache_manager_1.packageCache.set(packageName, result);
374
- utils_2.debugLog.perf('jsdelivr', `npm fallback resolved ${packageName} → ${result.latestVersion}`, tFallback);
375
- }
376
- else {
377
- utils_2.debugLog.warn('jsdelivr', `npm fallback returned no data for ${packageName}`);
378
- }
379
- return result;
380
- }
381
- catch (error) {
382
- utils_2.debugLog.error('jsdelivr', `npm fallback failed for ${packageName}`, error);
339
+ const fetchPackageData = async (packageName, currentVersion) => {
340
+ const latestManifest = await fetchPackageManifestFromJsdelivr(packageName, 'latest');
341
+ const latestVersion = typeof latestManifest?.version === 'string' ? latestManifest.version.trim() : '';
342
+ if (!latestVersion) {
383
343
  return null;
384
344
  }
385
- };
386
- const fetchFreshPackageData = async (packageName, currentVersion) => {
387
- try {
388
- const majorVersion = extractMajorVersion(currentVersion);
389
- const latestResult = await fetchPackageJsonFromJsdelivr(packageName, 'latest');
390
- if (!latestResult) {
391
- return await fetchFromNpmFallback(packageName);
392
- }
393
- const latestVersion = latestResult.version;
394
- const latestMajorVersion = extractMajorVersion(latestVersion);
395
- const shouldFetchMajorVersion = Boolean(majorVersion && (latestMajorVersion === null || majorVersion !== latestMajorVersion));
396
- const majorResult = shouldFetchMajorVersion
397
- ? await fetchPackageJsonFromJsdelivr(packageName, majorVersion)
398
- : null;
399
- const allVersions = [latestVersion];
400
- if (majorResult && majorResult.version !== latestVersion) {
401
- allVersions.push(majorResult.version);
402
- }
403
- const sortedVersions = sortVersionsDescending(allVersions);
404
- const orderedVersions = sortedVersions[0] === latestVersion
405
- ? sortedVersions
406
- : [latestVersion, ...sortedVersions.filter((version) => version !== latestVersion)];
407
- const result = {
408
- latestVersion,
409
- allVersions: orderedVersions,
410
- };
411
- cache_manager_1.packageCache.set(packageName, result);
412
- return result;
413
- }
414
- catch {
415
- return await fetchFromNpmFallback(packageName);
416
- }
345
+ const majorVersion = extractMajorVersion(currentVersion);
346
+ const latestMajorVersion = extractMajorVersion(latestVersion);
347
+ const shouldFetchMajorVersion = Boolean(majorVersion && (latestMajorVersion === null || latestMajorVersion !== majorVersion));
348
+ const majorManifest = shouldFetchMajorVersion
349
+ ? await fetchPackageManifestFromJsdelivr(packageName, majorVersion)
350
+ : null;
351
+ const majorResolvedVersion = typeof majorManifest?.version === 'string' ? majorManifest.version.trim() : '';
352
+ const sortedVersions = sortVersionsDescending([latestVersion, majorResolvedVersion].filter(Boolean));
353
+ const allVersions = sortedVersions[0] === latestVersion
354
+ ? sortedVersions
355
+ : [latestVersion, ...sortedVersions.filter((version) => version !== latestVersion)];
356
+ return {
357
+ latestVersion,
358
+ allVersions,
359
+ };
417
360
  };
418
361
  const getPackageData = async (packageName, currentVersion) => {
419
- const cached = cache_manager_1.packageCache.get(packageName);
420
- if (cached) {
421
- utils_2.debugLog.info('jsdelivr', `cache hit: ${packageName} → ${cached.latestVersion}`);
422
- return cached;
423
- }
424
362
  const inFlight = inFlightLookups.get(packageName);
425
363
  if (inFlight) {
426
364
  return await inFlight;
427
365
  }
428
- const lookupPromise = fetchFreshPackageData(packageName, currentVersion).finally(() => {
366
+ const lookupPromise = fetchPackageData(packageName, currentVersion).finally(() => {
429
367
  inFlightLookups.delete(packageName);
430
368
  });
431
369
  inFlightLookups.set(packageName, lookupPromise);
432
370
  return await lookupPromise;
433
371
  };
434
- const fetchPackageWithFallback = async (packageName) => {
372
+ await Promise.all(packageNames.map(async (packageName) => {
435
373
  try {
436
- const currentVersion = currentVersions?.get(packageName);
437
- const result = await getPackageData(packageName, currentVersion);
374
+ const result = await getPackageData(packageName, currentVersions?.get(packageName));
438
375
  if (result) {
439
376
  packageData.set(packageName, result);
440
- addToBatch(packageName, result);
441
377
  }
442
378
  }
443
- catch (error) {
444
- console.error(`Failed to resolve package data for ${packageName}; continuing with others.`, error);
445
- }
446
379
  finally {
447
380
  completedCount++;
448
- emitProgress(packageName, completedCount, total);
449
- }
450
- };
451
- try {
452
- // Fire all requests simultaneously - each request internally handles retries/fallback.
453
- await Promise.all(packageNames.map(fetchPackageWithFallback));
454
- }
455
- finally {
456
- // Flush any remaining batch items
457
- flushBatch();
458
- // Flush persistent cache to disk
459
- cache_manager_1.packageCache.flush();
460
- // Clear the progress line if no custom progress handler
461
- if (!onProgress) {
462
- utils_1.ConsoleUtils.clearProgress();
381
+ onProgress?.(packageName, completedCount, total);
463
382
  }
464
- }
383
+ }));
465
384
  return packageData;
466
385
  }
467
- /**
468
- * Clear the package cache (useful for testing)
469
- */
470
- function clearJsdelivrPackageCache() {
471
- cache_manager_1.packageCache.clear();
472
- }
473
386
  /**
474
387
  * Close the jsDelivr connection pool (useful for graceful shutdown)
475
388
  */
476
389
  async function closeJsdelivrPool() {
477
390
  await jsdelivrPool.close();
478
391
  }
392
+ function clearExactManifestCache() {
393
+ inFlightManifests.clear();
394
+ }
479
395
  //# sourceMappingURL=jsdelivr-registry.js.map
@@ -34,21 +34,48 @@ var __importStar = (this && this.__importStar) || (function () {
34
34
  })();
35
35
  Object.defineProperty(exports, "__esModule", { value: true });
36
36
  exports.getAllPackageData = getAllPackageData;
37
+ exports.getAllPackageDataBatched = getAllPackageDataBatched;
37
38
  exports.clearPackageCache = clearPackageCache;
38
39
  const semver = __importStar(require("semver"));
39
40
  const config_1 = require("../config");
40
- const cache_manager_1 = require("./cache-manager");
41
+ const jsdelivr_registry_1 = require("./jsdelivr-registry");
41
42
  const utils_1 = require("../ui/utils");
43
+ const inFlightLookups = new Map();
44
+ const isRetryableStatus = (statusCode) => statusCode === 408 || statusCode === 429 || statusCode >= 500;
45
+ const isTransientNetworkError = (error) => {
46
+ if (!(error instanceof Error)) {
47
+ return false;
48
+ }
49
+ const maybeCode = error.code;
50
+ return (error.name === 'AbortError' ||
51
+ maybeCode === 'ENOTFOUND' ||
52
+ maybeCode === 'EAI_AGAIN' ||
53
+ maybeCode === 'ECONNRESET' ||
54
+ maybeCode === 'ECONNREFUSED' ||
55
+ maybeCode === 'ETIMEDOUT' ||
56
+ maybeCode === 'EPIPE');
57
+ };
58
+ const fetchFromJsdelivrFallback = async (packageName, currentVersion) => {
59
+ const jsdelivrData = await (0, jsdelivr_registry_1.getAllPackageDataFromJsdelivr)([packageName], currentVersion ? new Map([[packageName, currentVersion]]) : undefined);
60
+ return jsdelivrData.get(packageName) ?? { latestVersion: 'unknown', allVersions: [] };
61
+ };
62
+ async function getFreshPackageData(packageName, currentVersion) {
63
+ const cacheKey = `${packageName}@${currentVersion ?? ''}`;
64
+ const inFlight = inFlightLookups.get(cacheKey);
65
+ if (inFlight) {
66
+ return await inFlight;
67
+ }
68
+ const lookupPromise = fetchPackageFromRegistryWithFallback(packageName, currentVersion).finally(() => {
69
+ inFlightLookups.delete(cacheKey);
70
+ });
71
+ inFlightLookups.set(cacheKey, lookupPromise);
72
+ return await lookupPromise;
73
+ }
42
74
  /**
43
75
  * Fetches package data from npm registry.
44
- * Uses the shared CacheManager for caching.
76
+ * Falls back to jsDelivr when npm is temporarily unavailable.
45
77
  */
46
- async function fetchPackageFromRegistry(packageName) {
47
- // Use CacheManager for unified caching (memory + disk)
48
- const cached = cache_manager_1.packageCache.get(packageName);
49
- if (cached) {
50
- return cached;
51
- }
78
+ async function fetchPackageFromRegistryWithFallback(packageName, currentVersion) {
52
79
  try {
53
80
  const url = `${config_1.NPM_REGISTRY_URL}/${encodeURIComponent(packageName)}`;
54
81
  const controller = new AbortController();
@@ -63,32 +90,29 @@ async function fetchPackageFromRegistry(packageName) {
63
90
  });
64
91
  clearTimeout(timeoutId);
65
92
  if (!response.ok) {
93
+ if (isRetryableStatus(response.status)) {
94
+ return await fetchFromJsdelivrFallback(packageName, currentVersion);
95
+ }
66
96
  throw new Error(`HTTP ${response.status}`);
67
97
  }
68
98
  const text = await response.text();
69
99
  const data = JSON.parse(text);
70
- // Extract versions and filter to valid semver (X.Y.Z format, no pre-releases)
71
- const allVersions = Object.keys(data.versions || {}).filter((version) => {
72
- // Match only X.Y.Z format (no pre-release, no build metadata)
73
- return /^[0-9]+\.[0-9]+\.[0-9]+$/.test(version);
74
- });
75
- // Sort versions to find the latest
100
+ const allVersions = Object.keys(data.versions || {}).filter((version) => /^[0-9]+\.[0-9]+\.[0-9]+$/.test(version));
76
101
  const sortedVersions = allVersions.sort(semver.rcompare);
77
102
  const latestVersion = sortedVersions.length > 0 ? sortedVersions[0] : 'unknown';
78
- const result = {
103
+ return {
79
104
  latestVersion,
80
105
  allVersions,
81
106
  };
82
- // Cache the result using CacheManager (handles both memory and disk)
83
- cache_manager_1.packageCache.set(packageName, result);
84
- return result;
85
107
  }
86
108
  finally {
87
109
  clearTimeout(timeoutId);
88
110
  }
89
111
  }
90
112
  catch (error) {
91
- // Return fallback data for failed packages
113
+ if (isTransientNetworkError(error)) {
114
+ return await fetchFromJsdelivrFallback(packageName, currentVersion);
115
+ }
92
116
  return { latestVersion: 'unknown', allVersions: [] };
93
117
  }
94
118
  }
@@ -97,17 +121,15 @@ async function fetchPackageFromRegistry(packageName) {
97
121
  * Uses native fetch with timeout support for reliable performance.
98
122
  * Only returns valid semantic versions (X.Y.Z format, excluding pre-releases).
99
123
  */
100
- async function getAllPackageData(packageNames, onProgress) {
124
+ async function getAllPackageData(packageNames, onProgress, currentVersions) {
101
125
  const packageData = new Map();
102
126
  if (packageNames.length === 0) {
103
127
  return packageData;
104
128
  }
105
129
  const total = packageNames.length;
106
130
  let completedCount = 0;
107
- // Fire all requests simultaneously
108
- // Concurrency is handled naturally by the event loop with fetch
109
131
  const allPromises = packageNames.map(async (packageName) => {
110
- const data = await fetchPackageFromRegistry(packageName);
132
+ const data = await getFreshPackageData(packageName, currentVersions?.get(packageName));
111
133
  packageData.set(packageName, data);
112
134
  completedCount++;
113
135
  if (onProgress) {
@@ -116,18 +138,66 @@ async function getAllPackageData(packageNames, onProgress) {
116
138
  });
117
139
  // Wait for all requests to complete
118
140
  await Promise.all(allPromises);
119
- // Flush persistent cache to disk
120
- cache_manager_1.packageCache.flush();
121
141
  // Clear the progress line if no custom progress handler
122
142
  if (!onProgress) {
123
143
  utils_1.ConsoleUtils.clearProgress();
124
144
  }
125
145
  return packageData;
126
146
  }
147
+ async function runWithConcurrencyLimit(items, concurrency, worker) {
148
+ if (items.length === 0) {
149
+ return;
150
+ }
151
+ const limit = Math.max(1, Math.min(concurrency, items.length));
152
+ let nextIndex = 0;
153
+ const runWorker = async () => {
154
+ while (nextIndex < items.length) {
155
+ const currentIndex = nextIndex++;
156
+ await worker(items[currentIndex], currentIndex);
157
+ }
158
+ };
159
+ await Promise.all(Array.from({ length: limit }, () => runWorker()));
160
+ }
161
+ async function getAllPackageDataBatched(packageNames, onBatchReady, currentVersions, options = {}) {
162
+ const packageData = new Map();
163
+ if (packageNames.length === 0) {
164
+ return packageData;
165
+ }
166
+ const batchSizes = options.batchSizes && options.batchSizes.length > 0
167
+ ? options.batchSizes.map((size) => Math.max(1, size))
168
+ : [Math.max(1, options.batchSize ?? 20)];
169
+ const concurrency = Math.max(1, options.concurrency ?? 5);
170
+ const total = packageNames.length;
171
+ let completedCount = 0;
172
+ let batchStart = 0;
173
+ let batchIndex = 0;
174
+ while (batchStart < packageNames.length) {
175
+ const batchSize = batchSizes[Math.min(batchIndex, batchSizes.length - 1)];
176
+ const batchNames = packageNames.slice(batchStart, batchStart + batchSize);
177
+ const batchResults = new Array(batchNames.length);
178
+ await runWithConcurrencyLimit(batchNames, concurrency, async (packageName, itemIndex) => {
179
+ const data = await getFreshPackageData(packageName, currentVersions?.get(packageName));
180
+ packageData.set(packageName, data);
181
+ completedCount++;
182
+ batchResults[itemIndex] = {
183
+ packageName,
184
+ data,
185
+ completed: completedCount,
186
+ total,
187
+ batchIndex,
188
+ itemIndex,
189
+ };
190
+ });
191
+ onBatchReady?.(batchResults.filter(Boolean));
192
+ batchStart += batchSize;
193
+ batchIndex++;
194
+ }
195
+ return packageData;
196
+ }
127
197
  /**
128
- * Clear the package cache (useful for testing)
198
+ * Retained for backward compatibility. Registry responses are fresh-by-default.
129
199
  */
130
200
  function clearPackageCache() {
131
- cache_manager_1.packageCache.clear();
201
+ inFlightLookups.clear();
132
202
  }
133
203
  //# sourceMappingURL=npm-registry.js.map