[READ-ONLY] a fast, modern browser for the npm registry
at main 125 lines 3.4 kB view raw
1import type { PackageVersionsInfo } from 'fast-npm-meta' 2import { getVersionsBatch } from 'fast-npm-meta' 3import { maxSatisfying, prerelease, major, minor, diff, gt } from 'semver' 4import { 5 type OutdatedDependencyInfo, 6 isNonSemverConstraint, 7 constraintIncludesPrerelease, 8} from '~/utils/npm/outdated-dependencies' 9 10const BATCH_SIZE = 50 11 12function resolveOutdated( 13 versions: string[], 14 latestTag: string, 15 constraint: string, 16): OutdatedDependencyInfo | null { 17 if (constraint === 'latest') { 18 return { 19 resolved: latestTag, 20 latest: latestTag, 21 majorsBehind: 0, 22 minorsBehind: 0, 23 diffType: null, 24 } 25 } 26 27 let filteredVersions = versions 28 if (!constraintIncludesPrerelease(constraint)) { 29 filteredVersions = versions.filter(v => !prerelease(v)) 30 } 31 32 const resolved = maxSatisfying(filteredVersions, constraint) 33 if (!resolved) return null 34 35 if (resolved === latestTag) return null 36 37 // Resolved is newer than latest (e.g. ^2.0.0-rc when latest is 1.x) 38 if (gt(resolved, latestTag)) { 39 return null 40 } 41 42 const diffType = diff(resolved, latestTag) 43 const majorsBehind = major(latestTag) - major(resolved) 44 const minorsBehind = majorsBehind === 0 ? minor(latestTag) - minor(resolved) : 0 45 46 return { 47 resolved, 48 latest: latestTag, 49 majorsBehind, 50 minorsBehind, 51 diffType, 52 } 53} 54 55/** 56 * Check for outdated dependencies via fast-npm-meta batch version lookups. 57 * Returns a reactive map of dependency name to outdated info. 58 */ 59export function useOutdatedDependencies( 60 dependencies: MaybeRefOrGetter<Record<string, string> | undefined>, 61) { 62 const outdated = shallowRef<Record<string, OutdatedDependencyInfo>>({}) 63 64 async function fetchOutdatedInfo(deps: Record<string, string> | undefined) { 65 if (!deps || Object.keys(deps).length === 0) { 66 outdated.value = {} 67 return 68 } 69 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 })), 87 ) 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 } 96 97 const results: Record<string, OutdatedDependencyInfo> = {} 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) 106 if (info) { 107 results[name] = info 108 } 109 } 110 111 outdated.value = results 112 } 113 114 watch( 115 () => toValue(dependencies), 116 deps => { 117 fetchOutdatedInfo(deps).catch(() => { 118 // Network failure or fast-npm-meta outage — leave stale results in place 119 }) 120 }, 121 { immediate: true }, 122 ) 123 124 return outdated 125}