diff --git a/get_history/index.ts b/get_history/index.ts index fe2a0b3..cb335ff 100644 --- a/get_history/index.ts +++ b/get_history/index.ts @@ -1,19 +1,70 @@ -function requireEnv(key: string): string { - const value = process.env[key]; - if (!value) throw new Error(`Missing required env var: ${key}`); - return value; +import { mkdir } from "node:fs/promises"; +import { dirname } from "node:path"; + +function parseArgs(args: string[]): { outputPath: string } { + let outputPath = "../static/activity.json"; + + for (let i = 0; i < args.length; i++) { + const arg = args[i]; + + if (arg === "--help" || arg === "-h") { + console.log( + "Usage: bun run index.ts [--output ]\n\nOptions:\n -o, --output Where to write activity.json (default: ../static/activity.json)", + ); + process.exit(0); + } + + if (arg === "--output" || arg === "-o") { + const value = args[i + 1]; + if (!value) throw new Error(`${arg} requires a path`); + outputPath = value; + i++; + continue; + } + + if (arg?.startsWith("--output=")) { + outputPath = arg.slice("--output=".length); + continue; + } + + throw new Error(`Unknown argument: ${arg}`); + } + + return { outputPath }; } +const cli = parseArgs(Bun.argv.slice(2)); + const config = { - githubApiKey: requireEnv("GH_API_KEY"), - forgejoApiKey: requireEnv("FJ_API_KEY"), - forgejoUsername: requireEnv("FJ_USER"), - forgejoURL: requireEnv("FJ_URL"), - gitlabApiKey: process.env["GL_API_KEY"], - gitlabUsername: process.env["GL_USER"], + githubApiKey: process.env["GH_API_KEY"] ?? "", + forgejoApiKey: process.env["FJ_API_KEY"] ?? "", + forgejoUsername: process.env["FJ_USER"] ?? "", + forgejoURL: process.env["FJ_URL"] ?? "https://codeberg.org", + gitlabApiKey: process.env["GL_API_KEY"] ?? "", + gitlabUsername: process.env["GL_USER"] ?? "", gitlabURL: process.env["GL_URL"] ?? "https://gitlab.com", }; +function areGhVarsPresent() { + return ( + config.githubApiKey?.trim().length > 0 && + config.githubApiKey?.trim().length > 0 + ); +} + +function areFjVarsPresent() { + return ( + config.forgejoApiKey?.trim().length > 0 && + config.forgejoUsername?.trim().length > 0 + ); +} + +function areGlVarsPresent() { + return ( + config.gitlabApiKey?.trim().length > 0 && + config.gitlabUsername?.trim().length > 0 + ); +} const now = new Date(); const oneYearAgo = new Date(); oneYearAgo.setFullYear(oneYearAgo.getFullYear() - 1); @@ -142,7 +193,8 @@ function parseGitHubContributions(data: any): ActivityMap { 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; + if (day.contributionCount > 0) + calendarMap[day.date] = day.contributionCount; } } @@ -221,7 +273,7 @@ async function getForgejoActivityMap(): Promise { ); } - const activities: any[] = await response.json(); + const activities = (await response.json()) as any[]; if (activities.length === 0) break; const pageMap = parseForgejoActivity(activities); @@ -312,7 +364,6 @@ function parseGitLabEvents(events: any[]): ActivityMap { async function getGitLabActivityMap(): Promise { if (!config.gitlabApiKey || !config.gitlabUsername) { - console.log("[GitLab] Skipping: GL_API_KEY or GL_USER not set"); return {}; } @@ -321,35 +372,23 @@ async function getGitLabActivityMap(): Promise { const map: ActivityMap = {}; const afterDate = oneYearAgo.toISOString().split("T")[0]!; - console.log(`[GitLab] Fetching events for user "${config.gitlabUsername}" from ${config.gitlabURL} after ${afterDate}`); - while (true) { const url = `${config.gitlabURL}/api/v4/users/${config.gitlabUsername}/events?action=pushed&after=${afterDate}&per_page=${pageLimit}&page=${page}`; - console.log(`[GitLab] GET ${url}`); const response = await fetch(url, { headers: { "PRIVATE-TOKEN": config.gitlabApiKey }, }); - console.log(`[GitLab] Response: ${response.status} ${response.statusText}`); - if (!response.ok) { - const body = await response.text(); - console.error(`[GitLab] Error body: ${body}`); - throw new Error(`GitLab API error: ${response.status} ${response.statusText}`); - } - - const events: any[] = await response.json(); - console.log(`[GitLab] Page ${page}: received ${events.length} events`); - - if (events.length > 0) { - console.log(`[GitLab] First event sample:`, JSON.stringify(events[0], null, 2)); + throw new Error( + `GitLab API error: ${response.status} ${response.statusText}`, + ); } + const events = (await response.json()) as any[]; if (events.length === 0) break; const parsed = parseGitLabEvents(events); - console.log(`[GitLab] Page ${page}: parsed ${Object.keys(parsed).length} days of activity`); mergeInto(map, parsed); if (events.length < pageLimit) break; @@ -360,10 +399,23 @@ async function getGitLabActivityMap(): Promise { } async function makeActivityMap(): Promise { + const getActivity = async ( + varsPresentCheck: () => boolean, + getActivityMap: () => Promise, + hubName: string, + ) => { + if (varsPresentCheck()) { + console.log(`Fetching ${hubName} activity`); + return getActivityMap(); + } else { + console.log(`${hubName} not configured, skipping...`); + return {}; + } + }; const [ghMap, fjMap, glMap] = await Promise.all([ - getGitHubActivityMap(), - getForgejoActivityMap(), - getGitLabActivityMap(), + getActivity(areGhVarsPresent, getGitHubActivityMap, "Github"), + getActivity(areFjVarsPresent, getForgejoActivityMap, "Forgejo"), + getActivity(areGlVarsPresent, getGitLabActivityMap, "Gitlab"), ]); const merged = deduplicateAcrossPlatforms(ghMap, fjMap); @@ -371,10 +423,8 @@ async function makeActivityMap(): Promise { } const activityMap = await makeActivityMap(); -await Bun.write( - "../static/activity.json", - JSON.stringify(activityMap, null, 2), -); +await mkdir(dirname(cli.outputPath), { recursive: true }); +await Bun.write(cli.outputPath, JSON.stringify(activityMap, null, 2)); console.log( - `Wrote ${Object.keys(activityMap).length} days of activity to out/activity.json`, + `Wrote ${Object.keys(activityMap).length} days of activity to ${cli.outputPath}`, );