Location via proxy:   [ UP ]  
[Report a bug]   [Manage cookies]                
Skip to content

Commit

Permalink
chore: Add NPM download stats
Browse files Browse the repository at this point in the history
  • Loading branch information
franky47 committed Feb 11, 2024
1 parent df5e894 commit 2b0344b
Show file tree
Hide file tree
Showing 7 changed files with 147 additions and 3 deletions.
1 change: 1 addition & 0 deletions packages/docs/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@
"recharts": "^2.11.0",
"remark-smartypants": "^2.1.0",
"semver": "^7.6.0",
"server-only": "^0.0.1",
"tailwind-merge": "^2.2.1",
"tailwindcss": "^3.4.1",
"tailwindcss-animate": "^1.0.7",
Expand Down
44 changes: 44 additions & 0 deletions packages/docs/src/app/(pages)/stats/_components/downloads.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import { Download } from 'lucide-react'
import { twMerge } from 'tailwind-merge'
import { fetchNpmPackage } from '../lib/npm'
import { SvgCurveGraph } from './svg-curve-graph'

type DownloadsGraphProps = React.ComponentProps<'section'> & {}

const formatter = new Intl.NumberFormat('en-GB', {
compactDisplay: 'short',
notation: 'compact'
})

export async function DownloadsGraph({
className,
...props
}: DownloadsGraphProps) {
const [nuqs, nextUseQueryState] = await Promise.all([
fetchNpmPackage('nuqs'),
fetchNpmPackage('next-usequerystate')
])
const last30Days = nuqs.last30Days.reduce((acc, x, i) => {
acc[i] = x + nextUseQueryState.last30Days[i]
return acc
}, [] as number[])
const totalDownloads = nuqs.allTime + nextUseQueryState.allTime
return (
<section className={twMerge('relative', className)} {...props}>
<SvgCurveGraph
data={last30Days}
lastDate={nuqs.lastDate}
height={200}
summaryValue={last30Days.reduce((sum, x) => sum + x)}
className="text-red-500"
/>
<p className="absolute left-2 top-2 flex items-center gap-2 text-2xl font-semibold tabular-nums md:text-6xl">
<Download
aria-label="NPM package downloads"
className="inline-block md:h-12 md:w-12"
/>{' '}
{formatter.format(totalDownloads)}
</p>
</section>
)
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ export async function StarHistoryGraph({
lastDate={stars.bins[0].date}
height={200}
summaryValue={stars.count}
className="text-blue-500"
className="text-yellow-500"
/>
<p className="absolute left-2 top-2 flex items-center gap-2 text-2xl font-semibold tabular-nums md:text-6xl">
<Star
Expand Down
70 changes: 70 additions & 0 deletions packages/docs/src/app/(pages)/stats/lib/npm.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
import dayjs from 'dayjs'
import 'server-only'

export type NpmPackageStatsData = {
allTime: number
last30Days: number[]
lastDate: Date
}

// const regexp = /https:\/\/npmjs\.com\/package\/([\w.-]+|@[\w.-]+\/[\w.-]+)/gm

type RangeResponse = {
downloads: Array<{
downloads: number
day: string
}>
}

async function getLastNDays(
pkg: string,
n: number
): Promise<{ downloads: number[]; date: string }> {
const start = dayjs().subtract(n, 'day').format('YYYY-MM-DD')
const end = dayjs().subtract(1, 'day').endOf('day').format('YYYY-MM-DD')
const url = `https://api.npmjs.org/downloads/range/${start}:${end}/${pkg}`
const { downloads } = await get<RangeResponse>(url)
return {
downloads: downloads.map(d => d.downloads),
date: end
}
}

async function getAllTime(pkg: string): Promise<number> {
let downloads: number = 0
const now = dayjs()
let start = dayjs('2015-01-10') // NPM stats epoch
let end = start.add(18, 'month')
while (start.isBefore(now)) {
const url = `https://api.npmjs.org/downloads/range/${start.format(
'YYYY-MM-DD'
)}:${end.format('YYYY-MM-DD')}/${pkg}`
const res = await get<RangeResponse>(url)
downloads += res.downloads.reduce((sum, d) => sum + d.downloads, 0)
start = end
end = start.add(18, 'month')
}
return downloads
}

export async function fetchNpmPackage(
pkg: string
): Promise<NpmPackageStatsData> {
const [allTime, { downloads: last30Days, date: lastDate }] =
await Promise.all([getAllTime(pkg), getLastNDays(pkg, 30)])
return {
allTime,
lastDate: new Date(lastDate),
last30Days
}
}

async function get<T = any>(url: string) {
const res = await fetch(url, {
next: {
revalidate: 86_400,
tags: ['npm']
}
})
return (await res.json()) as T
}
Loading

0 comments on commit 2b0344b

Please sign in to comment.