Skip to content

Commit 8b6d617

Browse files
authored
fix: use fast-npm-meta to get latest versions (#664)
1 parent 828fe54 commit 8b6d617

File tree

18 files changed

+219
-213
lines changed

18 files changed

+219
-213
lines changed

app/components/Package/Versions.vue

Lines changed: 4 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
<script setup lang="ts">
2-
import type { PackageVersionInfo, PackumentVersion } from '#shared/types'
2+
import type { PackageVersionInfo, SlimVersion } from '#shared/types'
33
import { compare } from 'semver'
44
import type { RouteLocationRaw } from 'vue-router'
55
import { fetchAllPackageVersions } from '~/composables/useNpmRegistry'
@@ -14,7 +14,7 @@ import {
1414
1515
const props = defineProps<{
1616
packageName: string
17-
versions: Record<string, PackumentVersion>
17+
versions: Record<string, SlimVersion>
1818
distTags: Record<string, string>
1919
time: Record<string, string>
2020
}>()
@@ -31,13 +31,6 @@ interface VersionDisplay {
3131
deprecated?: string
3232
}
3333
34-
// Check if a version has provenance/attestations
35-
function hasProvenance(version: PackumentVersion | undefined): boolean {
36-
if (!version?.dist) return false
37-
const dist = version.dist as { attestations?: unknown }
38-
return !!dist.attestations
39-
}
40-
4134
// Build route object for package version link
4235
function versionRoute(version: string): RouteLocationRaw {
4336
return {
@@ -53,10 +46,7 @@ const versionToTags = computed(() => buildVersionToTagsMap(props.distTags))
5346
// Deduplicates so each version appears only once, with all its tags
5447
const allTagRows = computed(() => {
5548
// Group tags by version with their metadata
56-
const versionMap = new Map<
57-
string,
58-
{ tags: string[]; versionData: PackumentVersion | undefined }
59-
>()
49+
const versionMap = new Map<string, { tags: string[]; versionData: SlimVersion | undefined }>()
6050
for (const [tag, version] of Object.entries(props.distTags)) {
6151
const existing = versionMap.get(version)
6252
if (existing) {
@@ -88,7 +78,7 @@ const allTagRows = computed(() => {
8878
version,
8979
time: props.time[version],
9080
tags,
91-
hasProvenance: hasProvenance(versionData),
81+
hasProvenance: versionData?.hasProvenance,
9282
deprecated: versionData?.deprecated,
9383
} as VersionDisplay,
9484
}))

app/composables/useNpmRegistry.ts

Lines changed: 49 additions & 59 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,17 @@
11
import type {
22
Packument,
3-
PackumentVersion,
43
SlimPackument,
54
NpmSearchResponse,
65
NpmSearchResult,
76
NpmDownloadCount,
87
NpmPerson,
98
PackageVersionInfo,
109
} from '#shared/types'
10+
import { getVersions } from 'fast-npm-meta'
11+
import type { ResolvedPackageVersion } from 'fast-npm-meta'
1112
import type { ReleaseType } from 'semver'
1213
import { mapWithConcurrency } from '#shared/utils/async'
1314
import { maxSatisfying, prerelease, major, minor, diff, gt, compare } from 'semver'
14-
import { isExactVersion } from '~/utils/versions'
1515
import { extractInstallScriptsInfo } from '~/utils/install-scripts'
1616
import type { CachedFetchFunction } from '#shared/utils/fetch-cache-config'
1717

@@ -93,17 +93,6 @@ async function fetchBulkDownloads(
9393
return downloads
9494
}
9595

96-
/**
97-
* Encode a package name for use in npm registry URLs.
98-
* Handles scoped packages (e.g., @scope/name -> @scope%2Fname).
99-
*/
100-
export function encodePackageName(name: string): string {
101-
if (name.startsWith('@')) {
102-
return `@${encodeURIComponent(name.slice(1))}`
103-
}
104-
return encodeURIComponent(name)
105-
}
106-
10796
/** Number of recent versions to include in initial payload */
10897
const RECENT_VERSIONS_COUNT = 5
10998

@@ -138,20 +127,28 @@ function transformPackument(pkg: Packument, requestedVersion?: string | null): S
138127
}
139128

140129
// Build filtered versions object with install scripts info per version
141-
const filteredVersions: Record<string, PackumentVersion> = {}
130+
const filteredVersions: Record<string, SlimVersion> = {}
131+
let versionData: SlimPackumentVersion | null = null
142132
for (const v of includedVersions) {
143133
const version = pkg.versions[v]
144134
if (version) {
145-
// Strip readme from each version, extract install scripts info
146-
const { readme: _readme, scripts, ...slimVersion } = version
147-
148-
// Extract install scripts info (which scripts exist + npx deps)
149-
const installScripts = scripts ? extractInstallScriptsInfo(scripts) : null
150-
135+
if (version.version === requestedVersion) {
136+
// Strip readme from each version, extract install scripts info
137+
const { readme: _readme, scripts, ...slimVersion } = version
138+
139+
// Extract install scripts info (which scripts exist + npx deps)
140+
const installScripts = scripts ? extractInstallScriptsInfo(scripts) : null
141+
versionData = {
142+
...slimVersion,
143+
installScripts: installScripts ?? undefined,
144+
}
145+
}
151146
filteredVersions[v] = {
152-
...slimVersion,
153-
installScripts: installScripts ?? undefined,
154-
} as PackumentVersion
147+
...((version?.dist as { attestations?: unknown }) ? { hasProvenance: true } : {}),
148+
version: version.version,
149+
deprecated: version.deprecated,
150+
tags: version.tags as string[],
151+
}
155152
}
156153
}
157154

@@ -177,10 +174,28 @@ function transformPackument(pkg: Packument, requestedVersion?: string | null): S
177174
'keywords': pkg.keywords,
178175
'repository': pkg.repository,
179176
'bugs': pkg.bugs,
177+
'requestedVersion': versionData,
180178
'versions': filteredVersions,
181179
}
182180
}
183181

182+
export function useResolvedVersion(
183+
packageName: MaybeRefOrGetter<string>,
184+
requestedVersion: MaybeRefOrGetter<string | null>,
185+
) {
186+
return useFetch(
187+
() => {
188+
const version = toValue(requestedVersion)
189+
return version
190+
? `https://npm.antfu.dev/${toValue(packageName)}@${version}`
191+
: `https://npm.antfu.dev/${toValue(packageName)}`
192+
},
193+
{
194+
transform: (data: ResolvedPackageVersion) => data.version,
195+
},
196+
)
197+
}
198+
184199
export function usePackage(
185200
name: MaybeRefOrGetter<string>,
186201
requestedVersion?: MaybeRefOrGetter<string | null>,
@@ -196,8 +211,7 @@ export function usePackage(
196211
})
197212
const reqVer = toValue(requestedVersion)
198213
const pkg = transformPackument(r, reqVer)
199-
const resolvedVersion = getResolvedVersion(pkg, reqVer)
200-
return { ...pkg, resolvedVersion, isStale }
214+
return { ...pkg, isStale }
201215
},
202216
)
203217

@@ -210,26 +224,6 @@ export function usePackage(
210224
return asyncData
211225
}
212226

213-
function getResolvedVersion(pkg: SlimPackument, reqVer?: string | null): string | null {
214-
if (!pkg || !reqVer) return null
215-
216-
// 1. Check if it's already an exact version in pkg.versions
217-
if (isExactVersion(reqVer) && pkg.versions[reqVer]) {
218-
return reqVer
219-
}
220-
221-
// 2. Check if it's a dist-tag (latest, next, beta, etc.)
222-
const tagVersion = pkg['dist-tags']?.[reqVer]
223-
if (tagVersion) {
224-
return tagVersion
225-
}
226-
227-
// 3. Try to resolve as a semver range
228-
const versions = Object.keys(pkg.versions)
229-
const resolved = maxSatisfying(versions, reqVer)
230-
return resolved
231-
}
232-
233227
export function usePackageDownloads(
234228
name: MaybeRefOrGetter<string>,
235229
period: MaybeRefOrGetter<'last-day' | 'last-week' | 'last-month' | 'last-year'> = 'last-week',
@@ -600,32 +594,28 @@ export function useOrgPackages(orgName: MaybeRefOrGetter<string>) {
600594
const allVersionsCache = new Map<string, Promise<PackageVersionInfo[]>>()
601595

602596
/**
603-
* Fetch all versions of a package from the npm registry.
597+
* Fetch all versions of a package using fast-npm-meta API.
604598
* Returns version info sorted by version (newest first).
605599
* Results are cached to avoid duplicate requests.
606600
*
607601
* Note: This is a standalone async function for use in event handlers.
608602
* For composable usage, use useAllPackageVersions instead.
603+
*
604+
* @see https://github.com/antfu/fast-npm-meta
609605
*/
610606
export async function fetchAllPackageVersions(packageName: string): Promise<PackageVersionInfo[]> {
611607
const cached = allVersionsCache.get(packageName)
612608
if (cached) return cached
613609

614610
const promise = (async () => {
615-
const encodedName = encodePackageName(packageName)
616-
// Use regular $fetch for client-side calls (this is called on user interaction)
617-
const data = await $fetch<{
618-
versions: Record<string, { deprecated?: string }>
619-
time: Record<string, string>
620-
}>(`${NPM_REGISTRY}/${encodedName}`)
621-
622-
return Object.entries(data.versions)
623-
.filter(([v]) => data.time[v])
624-
.map(([version, versionData]) => ({
611+
const data = await getVersions(packageName, { metadata: true })
612+
613+
return Object.entries(data.versionsMeta)
614+
.map(([version, meta]) => ({
625615
version,
626-
time: data.time[version],
627-
hasProvenance: false, // Would need to check dist.attestations for each version
628-
deprecated: versionData.deprecated,
616+
time: meta.time,
617+
hasProvenance: meta.provenance === 'trustedPublisher' || meta.provenance === true,
618+
deprecated: meta.deprecated,
629619
}))
630620
.sort((a, b) => compare(b.version, a.version))
631621
})()

app/composables/usePackageComparison.ts

Lines changed: 1 addition & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import type { FacetValue, ComparisonFacet, ComparisonPackage } from '#shared/types'
2+
import { encodePackageName } from '#shared/utils/npm'
23
import type { PackageAnalysisResponse } from './usePackageAnalysis'
34

45
export interface PackageComparisonData {
@@ -222,13 +223,6 @@ export function usePackageComparison(packageNames: MaybeRefOrGetter<string[]>) {
222223
}
223224
}
224225

225-
function encodePackageName(name: string): string {
226-
if (name.startsWith('@')) {
227-
return `@${encodeURIComponent(name.slice(1))}`
228-
}
229-
return encodeURIComponent(name)
230-
}
231-
232226
function computeFacetValue(facet: ComparisonFacet, data: PackageComparisonData): FacetValue | null {
233227
switch (facet) {
234228
case 'downloads':

0 commit comments

Comments
 (0)