1- import type { NuxtApp } from '#app'
1+ import type { PackageVersionsInfo } from 'fast-npm-meta'
2+ import { getVersionsBatch } from 'fast-npm-meta'
23import { maxSatisfying , prerelease , major , minor , diff , gt } from 'semver'
3- import type { Packument } from '#shared/types'
4- import { mapWithConcurrency } from '#shared/utils/async'
5- import type { CachedFetchFunction } from '#shared/utils/fetch-cache-config'
64import {
75 type OutdatedDependencyInfo ,
86 isNonSemverConstraint ,
97 constraintIncludesPrerelease ,
108} from '~/utils/npm/outdated-dependencies'
119
12- // Cache for packument fetches to avoid duplicate requests across components
13- const packumentCache = new Map < string , Promise < Packument | null > > ( )
10+ const BATCH_SIZE = 50
1411
15- /**
16- * Check if a dependency is outdated.
17- * Returns null if up-to-date or if we can't determine.
18- */
19- async function checkDependencyOutdated (
20- cachedFetch : CachedFetchFunction ,
21- $npmRegistry : NuxtApp [ '$npmRegistry' ] ,
22- packageName : string ,
12+ function resolveOutdated (
13+ versions : string [ ] ,
14+ latestTag : string ,
2315 constraint : string ,
24- ) : Promise < OutdatedDependencyInfo | null > {
25- if ( isNonSemverConstraint ( constraint ) ) {
26- return null
27- }
28-
29- // Check in-memory cache first
30- let packument : Packument | null
31- const cached = packumentCache . get ( packageName )
32- if ( cached ) {
33- packument = await cached
34- } else {
35- const promise = $npmRegistry < Packument > ( `/${ encodePackageName ( packageName ) } ` )
36- . then ( ( { data } ) => data )
37- . catch ( ( ) => null )
38- packumentCache . set ( packageName , promise )
39- packument = await promise
40- }
41-
42- if ( ! packument ) return null
43-
44- const latestTag = packument [ 'dist-tags' ] ?. latest
45- if ( ! latestTag ) return null
46-
47- // Handle "latest" constraint specially - return info with current version
16+ ) : OutdatedDependencyInfo | null {
4817 if ( constraint === 'latest' ) {
4918 return {
5019 resolved : latestTag ,
@@ -55,20 +24,17 @@ async function checkDependencyOutdated(
5524 }
5625 }
5726
58- let versions = Object . keys ( packument . versions )
59- const includesPrerelease = constraintIncludesPrerelease ( constraint )
60-
61- if ( ! includesPrerelease ) {
62- versions = versions . filter ( v => ! prerelease ( v ) )
27+ let filteredVersions = versions
28+ if ( ! constraintIncludesPrerelease ( constraint ) ) {
29+ filteredVersions = versions . filter ( v => ! prerelease ( v ) )
6330 }
6431
65- const resolved = maxSatisfying ( versions , constraint )
32+ const resolved = maxSatisfying ( filteredVersions , constraint )
6633 if ( ! resolved ) return null
6734
6835 if ( resolved === latestTag ) return null
6936
70- // If resolved version is newer than latest, not outdated
71- // (e.g., using ^2.0.0-rc when latest is 1.x)
37+ // Resolved is newer than latest (e.g. ^2.0.0-rc when latest is 1.x)
7238 if ( gt ( resolved , latestTag ) ) {
7339 return null
7440 }
@@ -87,14 +53,12 @@ async function checkDependencyOutdated(
8753}
8854
8955/**
90- * Composable to check for outdated dependencies.
56+ * Check for outdated dependencies via fast-npm-meta batch version lookups .
9157 * Returns a reactive map of dependency name to outdated info.
9258 */
9359export function useOutdatedDependencies (
9460 dependencies : MaybeRefOrGetter < Record < string , string > | undefined > ,
9561) {
96- const { $npmRegistry } = useNuxtApp ( )
97- const cachedFetch = useCachedFetch ( )
9862 const outdated = shallowRef < Record < string , OutdatedDependencyInfo > > ( { } )
9963
10064 async function fetchOutdatedInfo ( deps : Record < string , string > | undefined ) {
@@ -103,18 +67,42 @@ export function useOutdatedDependencies(
10367 return
10468 }
10569
106- const entries = Object . entries ( deps )
107- const batchResults = await mapWithConcurrency (
108- entries ,
109- async ( [ name , constraint ] ) => {
110- const info = await checkDependencyOutdated ( cachedFetch , $npmRegistry , name , constraint )
111- return [ name , info ] as const
112- } ,
113- 5 ,
70+ const semverEntries = Object . entries ( deps ) . filter (
71+ ( [ , constraint ] ) => ! isNonSemverConstraint ( constraint ) ,
72+ )
73+
74+ if ( semverEntries . length === 0 ) {
75+ outdated . value = { }
76+ return
77+ }
78+
79+ const packageNames = semverEntries . map ( ( [ name ] ) => name )
80+
81+ const chunks : string [ ] [ ] = [ ]
82+ for ( let i = 0 ; i < packageNames . length ; i += BATCH_SIZE ) {
83+ chunks . push ( packageNames . slice ( i , i + BATCH_SIZE ) )
84+ }
85+ const batchResults = await Promise . all (
86+ chunks . map ( chunk => getVersionsBatch ( chunk , { throw : false } ) ) ,
11487 )
88+ const allVersionData = batchResults . flat ( )
89+
90+ // Build a lookup map from package name to version data
91+ const versionMap = new Map < string , PackageVersionsInfo > ( )
92+ for ( const data of allVersionData ) {
93+ if ( 'error' in data ) continue
94+ versionMap . set ( data . name , data )
95+ }
11596
11697 const results : Record < string , OutdatedDependencyInfo > = { }
117- for ( const [ name , info ] of batchResults ) {
98+ for ( const [ name , constraint ] of semverEntries ) {
99+ const data = versionMap . get ( name )
100+ if ( ! data ) continue
101+
102+ const latestTag = data . distTags . latest
103+ if ( ! latestTag ) continue
104+
105+ const info = resolveOutdated ( data . versions , latestTag , constraint )
118106 if ( info ) {
119107 results [ name ] = info
120108 }
0 commit comments