diff --git a/app/components/PackageDownloadStats.vue b/app/components/PackageDownloadStats.vue new file mode 100644 index 0000000000..639a63c816 --- /dev/null +++ b/app/components/PackageDownloadStats.vue @@ -0,0 +1,88 @@ + + + + + diff --git a/app/composables/useCharts.ts b/app/composables/useCharts.ts new file mode 100644 index 0000000000..98ccdf127e --- /dev/null +++ b/app/composables/useCharts.ts @@ -0,0 +1,3 @@ +import { createSharedComposable } from '@vueuse/core' + +export const useCharts = createSharedComposable(function useCharts() {}) diff --git a/app/composables/useNpmRegistry.ts b/app/composables/useNpmRegistry.ts index df7affe91e..07d5be42ef 100644 --- a/app/composables/useNpmRegistry.ts +++ b/app/composables/useNpmRegistry.ts @@ -141,6 +141,47 @@ export function usePackageDownloads( ) } +type NpmDownloadsRangeResponse = { + start: string + end: string + package: string + downloads: Array<{ day: string; downloads: number }> +} + +async function fetchNpmDownloadsRange( + packageName: string, + start: string, + end: string, +): Promise { + const encodedName = encodePackageName(packageName) + return await $fetch( + `${NPM_API}/downloads/range/${start}:${end}/${encodedName}`, + ) +} + +export function usePackageWeeklyDownloadEvolution( + name: MaybeRefOrGetter, + options: MaybeRefOrGetter<{ + weeks?: number + endDate?: string + }> = {}, +) { + return useLazyAsyncData( + () => `downloads-weekly-evolution:${toValue(name)}:${JSON.stringify(toValue(options))}`, + async () => { + const packageName = toValue(name) + const { weeks = 12, endDate } = toValue(options) ?? {} + const end = endDate ? new Date(`${endDate}T00:00:00.000Z`) : new Date() + const start = addDays(end, -(weeks * 7) + 1) + const startIso = toIsoDateString(start) + const endIso = toIsoDateString(end) + const range = await fetchNpmDownloadsRange(packageName, startIso, endIso) + const sortedDaily = [...range.downloads].sort((a, b) => a.day.localeCompare(b.day)) + return buildWeeklyEvolutionFromDaily(sortedDaily) + }, + ) +} + const emptySearchResponse = { objects: [], total: 0, diff --git a/app/pages/package/[...name].vue b/app/pages/package/[...name].vue index d8595a5a97..0704deda7e 100644 --- a/app/pages/package/[...name].vue +++ b/app/pages/package/[...name].vue @@ -42,6 +42,7 @@ const orgName = computed(() => { const { data: pkg, status, error } = usePackage(packageName, requestedVersion) const { data: downloads } = usePackageDownloads(packageName, 'last-week') +const { data: weeklyDownloads } = usePackageWeeklyDownloadEvolution(packageName, { weeks: 52 }) // Fetch README for specific version if requested, otherwise latest const { data: readmeData } = useLazyFetch<{ html: string }>( @@ -566,6 +567,9 @@ defineOgImageComponent('Package', { + + +
=3.0.1' + vue: '>=3.3.0' + peerDependenciesMeta: + jspdf: + optional: true + vue-devtools-stub@0.1.0: resolution: {integrity: sha512-RutnB7X8c5hjq39NceArgXg28WZtZpGc3+J16ljMiYnFhKvd8hITxSWQSQ5bvldxMDU6gG5mkxl1MTQLXckVSQ==} @@ -14448,6 +14460,10 @@ snapshots: vue-component-type-helpers@2.2.12: {} + vue-data-ui@3.13.0(vue@3.5.27(typescript@5.9.3)): + dependencies: + vue: 3.5.27(typescript@5.9.3) + vue-devtools-stub@0.1.0: {} vue-flow-layout@0.2.0: {}