function requireEnv(key: string): string { const value = process.env[key]; if (!value) throw new Error(`Missing required env var: ${key}`); return value; } const config = { githubApiKey: requireEnv("GH_API_KEY"), forgejoApiKey: requireEnv("FJ_API_KEY"), forgejoUsername: requireEnv("FJ_USER"), forgejoURL: requireEnv("FJ_URL"), }; const now = new Date(); const oneYearAgo = new Date(); oneYearAgo.setFullYear(oneYearAgo.getFullYear() - 1); const formattedOneYearAgo = oneYearAgo.toISOString().replace("Z", "+00:00"); function buildGhQuery(from: string, to: string): string { return `{ viewer { contributionsCollection(from: "${from}", to: "${to}") { contributionCalendar { weeks { contributionDays { date contributionCount } } } commitContributionsByRepository(maxRepositories: 100) { repository { nameWithOwner isMirror } contributions(first: 100) { nodes { occurredAt commitCount } } } } } }`; } type DayContributions = { [repo: string]: number; }; type ActivityMap = { [day: string]: DayContributions; }; function mergeInto(target: ActivityMap, source: ActivityMap): void { for (const [day, repos] of Object.entries(source)) { if (!target[day]) target[day] = {}; for (const [repo, count] of Object.entries(repos)) { target[day]![repo] = (target[day]![repo] ?? 0) + count; } } } function repoBaseName(fullName: string): string { return fullName.split("/")[1]?.toLowerCase() ?? fullName.toLowerCase(); } function deduplicateAcrossPlatforms( ghMap: ActivityMap, fjMap: ActivityMap, ): ActivityMap { const ghBaseNames = new Set(); for (const repos of Object.values(ghMap)) { for (const repo of Object.keys(repos)) { if (repo !== "private") ghBaseNames.add(repoBaseName(repo)); } } const sharedBaseNames = new Set(); for (const repos of Object.values(fjMap)) { for (const repo of Object.keys(repos)) { if (repo !== "private" && ghBaseNames.has(repoBaseName(repo))) { sharedBaseNames.add(repoBaseName(repo)); } } } const result: ActivityMap = {}; const allDays = new Set([...Object.keys(ghMap), ...Object.keys(fjMap)]); for (const day of allDays) { result[day] = {}; const ghDay = ghMap[day] ?? {}; const fjDay = fjMap[day] ?? {}; for (const [repo, count] of Object.entries(ghDay)) { const base = repoBaseName(repo); if (repo !== "private" && sharedBaseNames.has(base)) { const fjRepo = Object.keys(fjDay).find((r) => repoBaseName(r) === base); const fjCount = fjRepo ? (fjDay[fjRepo] ?? 0) : 0; if (count >= fjCount) { result[day]![repo] = count; } // else: Forgejo wins, added below } else { result[day]![repo] = (result[day]![repo] ?? 0) + count; } } for (const [repo, count] of Object.entries(fjDay)) { const base = repoBaseName(repo); if (repo !== "private" && sharedBaseNames.has(base)) { const ghRepo = Object.keys(ghDay).find((r) => repoBaseName(r) === base); const ghCount = ghRepo ? (ghDay[ghRepo] ?? 0) : 0; if (count > ghCount) { if (ghRepo) delete result[day]![ghRepo]; result[day]![repo] = count; } // else: GitHub already won } else { result[day]![repo] = (result[day]![repo] ?? 0) + count; } } if (Object.keys(result[day]!).length === 0) { delete result[day]; } } return result; } function parseGitHubContributions(data: any): ActivityMap { const result: ActivityMap = {}; const collection = data?.data?.viewer?.contributionsCollection; const calendarMap: { [day: string]: number } = {}; for (const week of collection?.contributionCalendar?.weeks ?? []) { for (const day of week.contributionDays) { if (day.contributionCount > 0) calendarMap[day.date] = day.contributionCount; } } for (const repoEntry of collection?.commitContributionsByRepository ?? []) { const repoName: string = repoEntry.repository.nameWithOwner; if (repoEntry.repository.isMirror) continue; for (const contribution of repoEntry.contributions.nodes) { const day: string = contribution.occurredAt.split("T")[0]; const count: number = contribution.commitCount; if (!result[day]) result[day] = {}; result[day]![repoName] = (result[day]![repoName] ?? 0) + count; } } for (const [day, total] of Object.entries(calendarMap)) { const known = Object.values(result[day] ?? {}).reduce((a, b) => a + b, 0); const hidden = total - known; if (hidden > 0) { if (!result[day]) result[day] = {}; result[day]!["private"] = (result[day]!["private"] ?? 0) + hidden; } } return result; } function parseForgejoActivity(activities: any[]): ActivityMap { const result: ActivityMap = {}; for (const activity of activities) { if (activity.repo?.mirror) continue; if (activity.op_type !== "commit_repo") continue; const isPrivate: boolean = activity.repo?.private ?? false; const repoFullName: string = activity.repo?.full_name ?? "unknown"; const displayName = isPrivate ? "private" : repoFullName; let content: { Commits?: { Timestamp: string | number }[] }; try { content = JSON.parse(activity.content); } catch { continue; } for (const commit of content.Commits ?? []) { let day: string; if (typeof commit.Timestamp === "number") { day = new Date(commit.Timestamp * 1000).toISOString().split("T")[0]!; } else { day = commit.Timestamp.split("T")[0]!; } if (!result[day]) result[day] = {}; result[day]![displayName] = (result[day]![displayName] ?? 0) + 1; } } return result; } async function getForgejoActivityMap(): Promise { const pageLimit = 50; let page = 1; const map: ActivityMap = {}; while (true) { const response = await fetch( `${config.forgejoURL}/api/v1/users/${config.forgejoUsername}/activities/feeds?limit=${pageLimit}&page=${page}&after=${formattedOneYearAgo}`, { headers: { Authorization: `Bearer ${config.forgejoApiKey}` } }, ); if (!response.ok) { throw new Error( `Forgejo API error: ${response.status} ${response.statusText}`, ); } const activities: any[] = await response.json(); if (activities.length === 0) break; const pageMap = parseForgejoActivity(activities); mergeInto(map, pageMap); if (activities.length < pageLimit) break; page++; } return map; } async function ghFetch(query: string): Promise { const response = await fetch("https://api.github.com/graphql", { method: "POST", headers: { "Content-Type": "application/json", Authorization: `Bearer ${config.githubApiKey}`, }, body: JSON.stringify({ query }), }); if (!response.ok) { throw new Error( `GitHub API error: ${response.status} ${response.statusText}`, ); } return response.json(); } async function getGitHubActivityMap(): Promise { const ranges = Array.from({ length: 12 }, (_, i) => { const to = new Date(now); to.setMonth(to.getMonth() - i); const from = new Date(to); from.setMonth(from.getMonth() - 1); return { from: from.toISOString(), to: to.toISOString() }; }); const results = await Promise.all( ranges.map(({ from, to }) => ghFetch(buildGhQuery(from, to))), ); const map: ActivityMap = {}; for (const data of results) { mergeInto(map, parseGitHubContributions(data)); } return map; } async function makeActivityMap(): Promise { const [ghMap, fjMap] = await Promise.all([ getGitHubActivityMap(), getForgejoActivityMap(), ]); return deduplicateAcrossPlatforms(ghMap, fjMap); } const activityMap = await makeActivityMap(); await Bun.write( "../static/activity.json", JSON.stringify(activityMap, null, 2), ); console.log( `Wrote ${Object.keys(activityMap).length} days of activity to out/activity.json`, );