From 8ebefec6cdf278bb4dbcc6766b82bcc278624e55 Mon Sep 17 00:00:00 2001 From: btea <2356281422@qq.com> Date: Sat, 21 Mar 2026 20:41:15 +0800 Subject: [PATCH 1/3] feat: version history page display download count --- app/pages/package/[[org]]/[name]/versions.vue | 97 ++++++++++++++++++- .../registry/npmjs-versions/[...pkg].get.ts | 48 +++++++++ server/utils/npm-website-versions.ts | 55 +++++++++++ 3 files changed, 197 insertions(+), 3 deletions(-) create mode 100644 server/api/registry/npmjs-versions/[...pkg].get.ts create mode 100644 server/utils/npm-website-versions.ts diff --git a/app/pages/package/[[org]]/[name]/versions.vue b/app/pages/package/[[org]]/[name]/versions.vue index 8a3b9c507..5330b587b 100644 --- a/app/pages/package/[[org]]/[name]/versions.vue +++ b/app/pages/package/[[org]]/[name]/versions.vue @@ -17,6 +17,16 @@ definePageMeta({ name: 'package-versions', }) +interface NpmWebsiteVersionDownload { + version: string + downloads: number +} + +interface NpmWebsiteVersionsResponse { + weeklyDownloads?: number + versions: NpmWebsiteVersionDownload[] +} + /** Number of flat items (headers + version rows) to render statically during SSR */ const SSR_COUNT = 20 @@ -49,6 +59,47 @@ const distTags = computed(() => versionSummary.value?.distTags ?? {}) const versionStrings = computed(() => versionSummary.value?.versions ?? []) const versionTimes = computed(() => versionSummary.value?.time ?? {}) +const { data: npmWebsiteVersions } = useLazyFetch( + () => `/api/registry/npmjs-versions/${encodeURIComponent(packageName.value)}`, + { + key: () => `npmjs-versions:${packageName.value}`, + deep: false, + default: () => ({ versions: [] }), + getCachedData(key, nuxtApp) { + return nuxtApp.static.data[key] ?? nuxtApp.payload.data[key] + }, + }, +) + +const numberFormatter = useNumberFormatter() +const versionDownloadsMap = computed( + () => + new Map( + (npmWebsiteVersions.value?.versions ?? []).map(({ version, downloads }) => [ + version, + downloads, + ]), + ), +) + +function getVersionDownloads(version: string): number | undefined { + return versionDownloadsMap.value.get(version) +} + +function getGroupDownloads(versions: string[]): number | undefined { + let total = 0 + let hasValue = false + + for (const version of versions) { + const downloads = getVersionDownloads(version) + if (downloads === undefined) continue + total += downloads + hasValue = true + } + + return hasValue ? total : undefined +} + // ─── Phase 2: full metadata (loaded on first group expand) ──────────────────── // Fetches deprecated status, provenance, and exact times needed for version rows. @@ -241,6 +292,14 @@ const flatItems = computed(() => { > +
+ {{ numberFormatter.format(getVersionDownloads(latestTagRow!.version)!) }} +
(() => { + + {{ numberFormatter.format(getVersionDownloads(row.version)!) }} + (() => { {{ item.label }} ({{ item.versions.length }}) - + + {{ numberFormatter.format(getGroupDownloads(item.versions)!) }} + + {{ item.versions[0] }} (() => {
+ + {{ numberFormatter.format(getVersionDownloads(item.version)!) }} + -
+
(() => { {{ item.label }} ({{ item.versions.length }}) - + + {{ numberFormatter.format(getGroupDownloads(item.versions)!) }} + + {{ item.versions[0] }} { + const pkgParam = getRouterParam(event, 'pkg') + if (!pkgParam) { + throw createError({ statusCode: 404, message: 'Package name is required' }) + } + + const packageName = decodeURIComponent(pkgParam) + + try { + const parsed = await fetchNpmVersionDownloadsFromApi(packageName) + + if (parsed.versions.length === 0) { + throw createError({ + statusCode: 502, + message: 'Failed to fetch version download data', + }) + } + + return { + packageName, + source: 'npm-api', + sourceUrl: `https://api.npmjs.org/versions/${encodePackageName(packageName)}/last-week`, + fetchedAt: new Date().toISOString(), + weeklyDownloads: parsed.weeklyDownloads, + versions: parsed.versions, + } + } catch (error: unknown) { + handleApiError(error, { + statusCode: 502, + message: 'Failed to fetch version download data from npm API', + }) + } + }, + { + maxAge: CACHE_MAX_AGE_ONE_HOUR, + swr: true, + getKey: event => { + const pkg = getRouterParam(event, 'pkg') ?? '' + return `npmjs-versions:v2:${pkg}` + }, + }, +) \ No newline at end of file diff --git a/server/utils/npm-website-versions.ts b/server/utils/npm-website-versions.ts new file mode 100644 index 000000000..d44b35e20 --- /dev/null +++ b/server/utils/npm-website-versions.ts @@ -0,0 +1,55 @@ +export interface NpmWebsiteVersionDownload { + version: string + downloads: number +} + +export interface NpmWebsiteVersionsData { + weeklyDownloads?: number + versions: NpmWebsiteVersionDownload[] +} + +interface NpmApiVersionDownloadsResponse { + downloads: Record +} + +interface NpmApiWeeklyDownloadsResponse { + downloads: number +} + +export async function fetchNpmVersionDownloadsFromApi( + packageName: string, +): Promise { + const encodedName = encodePackageName(packageName) + + const [versionsResponse, weeklyDownloadsResponse] = await Promise.all([ + fetch(`https://api.npmjs.org/versions/${encodedName}/last-week`), + fetch(`https://api.npmjs.org/downloads/point/last-week/${encodedName}`), + ]) + + if (!versionsResponse.ok) { + if (versionsResponse.status === 404) { + throw createError({ + statusCode: 404, + message: 'Package not found', + }) + } + + throw createError({ + statusCode: 502, + message: 'Failed to fetch version download data from npm API', + }) + } + + const versionsData = (await versionsResponse.json()) as NpmApiVersionDownloadsResponse + const weeklyDownloadsData = weeklyDownloadsResponse.ok + ? ((await weeklyDownloadsResponse.json()) as NpmApiWeeklyDownloadsResponse) + : null + + return { + weeklyDownloads: weeklyDownloadsData?.downloads, + versions: Object.entries(versionsData.downloads).map(([version, downloads]) => ({ + version, + downloads, + })), + } +} \ No newline at end of file From 6e6bbcb6c08a1d67b510c00e24b0ec36d40bbccc Mon Sep 17 00:00:00 2001 From: "autofix-ci[bot]" <114827586+autofix-ci[bot]@users.noreply.github.com> Date: Sat, 21 Mar 2026 12:44:49 +0000 Subject: [PATCH 2/3] [autofix.ci] apply automated fixes --- server/api/registry/npmjs-versions/[...pkg].get.ts | 6 ++---- server/utils/npm-website-versions.ts | 2 +- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/server/api/registry/npmjs-versions/[...pkg].get.ts b/server/api/registry/npmjs-versions/[...pkg].get.ts index c36692144..60984cfaf 100644 --- a/server/api/registry/npmjs-versions/[...pkg].get.ts +++ b/server/api/registry/npmjs-versions/[...pkg].get.ts @@ -1,7 +1,5 @@ import { CACHE_MAX_AGE_ONE_HOUR } from '#shared/utils/constants' -import { - fetchNpmVersionDownloadsFromApi, -} from '#server/utils/npm-website-versions' +import { fetchNpmVersionDownloadsFromApi } from '#server/utils/npm-website-versions' export default defineCachedEventHandler( async event => { @@ -45,4 +43,4 @@ export default defineCachedEventHandler( return `npmjs-versions:v2:${pkg}` }, }, -) \ No newline at end of file +) diff --git a/server/utils/npm-website-versions.ts b/server/utils/npm-website-versions.ts index d44b35e20..89c9df171 100644 --- a/server/utils/npm-website-versions.ts +++ b/server/utils/npm-website-versions.ts @@ -52,4 +52,4 @@ export async function fetchNpmVersionDownloadsFromApi( downloads, })), } -} \ No newline at end of file +} From c12f5074716eafd66ce83e9aada315fecb10606a Mon Sep 17 00:00:00 2001 From: btea <2356281422@qq.com> Date: Sat, 21 Mar 2026 21:36:16 +0800 Subject: [PATCH 3/3] feat: update --- app/pages/package/[[org]]/[name]/versions.vue | 1 - .../registry/npmjs-versions/[...pkg].get.ts | 15 ++-------- server/utils/npm-website-versions.ts | 30 ++++--------------- 3 files changed, 8 insertions(+), 38 deletions(-) diff --git a/app/pages/package/[[org]]/[name]/versions.vue b/app/pages/package/[[org]]/[name]/versions.vue index 5330b587b..5bb6bbff1 100644 --- a/app/pages/package/[[org]]/[name]/versions.vue +++ b/app/pages/package/[[org]]/[name]/versions.vue @@ -23,7 +23,6 @@ interface NpmWebsiteVersionDownload { } interface NpmWebsiteVersionsResponse { - weeklyDownloads?: number versions: NpmWebsiteVersionDownload[] } diff --git a/server/api/registry/npmjs-versions/[...pkg].get.ts b/server/api/registry/npmjs-versions/[...pkg].get.ts index 60984cfaf..c75eb37fb 100644 --- a/server/api/registry/npmjs-versions/[...pkg].get.ts +++ b/server/api/registry/npmjs-versions/[...pkg].get.ts @@ -11,22 +11,11 @@ export default defineCachedEventHandler( const packageName = decodeURIComponent(pkgParam) try { - const parsed = await fetchNpmVersionDownloadsFromApi(packageName) - - if (parsed.versions.length === 0) { - throw createError({ - statusCode: 502, - message: 'Failed to fetch version download data', - }) - } + const versions = await fetchNpmVersionDownloadsFromApi(packageName) return { packageName, - source: 'npm-api', - sourceUrl: `https://api.npmjs.org/versions/${encodePackageName(packageName)}/last-week`, - fetchedAt: new Date().toISOString(), - weeklyDownloads: parsed.weeklyDownloads, - versions: parsed.versions, + versions, } } catch (error: unknown) { handleApiError(error, { diff --git a/server/utils/npm-website-versions.ts b/server/utils/npm-website-versions.ts index 89c9df171..cc6fcf5bf 100644 --- a/server/utils/npm-website-versions.ts +++ b/server/utils/npm-website-versions.ts @@ -3,28 +3,16 @@ export interface NpmWebsiteVersionDownload { downloads: number } -export interface NpmWebsiteVersionsData { - weeklyDownloads?: number - versions: NpmWebsiteVersionDownload[] -} - interface NpmApiVersionDownloadsResponse { downloads: Record } -interface NpmApiWeeklyDownloadsResponse { - downloads: number -} - export async function fetchNpmVersionDownloadsFromApi( packageName: string, -): Promise { +): Promise { const encodedName = encodePackageName(packageName) - const [versionsResponse, weeklyDownloadsResponse] = await Promise.all([ - fetch(`https://api.npmjs.org/versions/${encodedName}/last-week`), - fetch(`https://api.npmjs.org/downloads/point/last-week/${encodedName}`), - ]) + const versionsResponse = await fetch(`https://api.npmjs.org/versions/${encodedName}/last-week`) if (!versionsResponse.ok) { if (versionsResponse.status === 404) { @@ -41,15 +29,9 @@ export async function fetchNpmVersionDownloadsFromApi( } const versionsData = (await versionsResponse.json()) as NpmApiVersionDownloadsResponse - const weeklyDownloadsData = weeklyDownloadsResponse.ok - ? ((await weeklyDownloadsResponse.json()) as NpmApiWeeklyDownloadsResponse) - : null - return { - weeklyDownloads: weeklyDownloadsData?.downloads, - versions: Object.entries(versionsData.downloads).map(([version, downloads]) => ({ - version, - downloads, - })), - } + return Object.entries(versionsData.downloads).map(([version, downloads]) => ({ + version, + downloads, + })) }