[READ-ONLY] a fast, modern browser for the npm registry
at main 80 lines 2.2 kB view raw
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}