forked from
npmx.dev/npmx.dev
[READ-ONLY]
a fast, modern browser for the npm registry
1import { CACHE_MAX_AGE_ONE_HOUR } from '#shared/utils/constants'
2
3const REPO = 'npmx-dev/npmx.dev'
4const GITHUB_HEADERS = {
5 'Accept': 'application/vnd.github.v3+json',
6 'User-Agent': 'npmx',
7} as const
8
9interface GitHubSearchResponse {
10 total_count: number
11}
12
13export interface RepoStats {
14 contributors: number
15 commits: number
16 pullRequests: number
17}
18
19export default defineCachedEventHandler(
20 async (): Promise<RepoStats> => {
21 const [contributorsCount, commitsCount, prsCount] = await Promise.all([
22 fetchPageCount(`https://api.github.com/repos/${REPO}/contributors?per_page=1&anon=false`),
23 fetchPageCount(`https://api.github.com/repos/${REPO}/commits?per_page=1`),
24 fetchSearchCount('issues', `repo:${REPO} is:pr is:merged`),
25 ])
26
27 return {
28 contributors: contributorsCount,
29 commits: commitsCount,
30 pullRequests: prsCount,
31 }
32 },
33 {
34 maxAge: CACHE_MAX_AGE_ONE_HOUR,
35 swr: true,
36 name: 'repo-stats',
37 getKey: () => 'repo-stats',
38 },
39)
40
41/**
42 * Count items by requesting a single result and reading the last page
43 * number from the Link header.
44 */
45async function fetchPageCount(url: string): Promise<number> {
46 const response = await fetch(url, { headers: GITHUB_HEADERS })
47
48 if (!response.ok) {
49 throw createError({ statusCode: response.status, message: `Failed to fetch ${url}` })
50 }
51
52 const link = response.headers.get('link')
53 if (link) {
54 const match = link.match(/[?&]page=(\d+)>;\s*rel="last"/)
55 if (match?.[1]) {
56 return Number.parseInt(match[1], 10)
57 }
58 }
59
60 // No Link header means only one page — count the response body
61 const body = (await response.json()) as unknown[]
62 return body.length
63}
64
65/**
66 * Use the GitHub search API to get a total_count for issues/PRs.
67 */
68async function fetchSearchCount(type: 'issues', query: string): Promise<number> {
69 const response = await fetch(
70 `https://api.github.com/search/${type}?q=${encodeURIComponent(query)}&per_page=1`,
71 { headers: GITHUB_HEADERS },
72 )
73
74 if (!response.ok) {
75 throw createError({ statusCode: response.status, message: `Failed to fetch ${type} count` })
76 }
77
78 const data = (await response.json()) as GitHubSearchResponse
79 return data.total_count
80}