diff --git a/.gitignore b/.gitignore deleted file mode 100644 index cab0d2f..0000000 --- a/.gitignore +++ /dev/null @@ -1,2 +0,0 @@ -test.html -activity.json diff --git a/assets/css/heatmap.css b/assets/css/heatmap.css deleted file mode 100644 index 9fcc765..0000000 --- a/assets/css/heatmap.css +++ /dev/null @@ -1,6 +0,0 @@ -:root{ - --color-empty: #F5F5F5; - --color-low: #BDD7EE; - --color-mid: #2E86C1; - --color-high: #1A5276 -} diff --git a/assets/js/build_heatmap.js b/assets/js/build_heatmap.js deleted file mode 100644 index 549374d..0000000 --- a/assets/js/build_heatmap.js +++ /dev/null @@ -1,73 +0,0 @@ -export function flattenData(raw) { - const result = {}; - for (const [date, repos] of Object.entries(raw)) { - result[date] = Object.values(repos).reduce((sum, n) => sum + n, 0); - } - return result; -} - -export function constructWeeks() { - const today = new Date(); - today.setHours(0, 0, 0, 0); - - const start = new Date(today); - start.setDate(today.getDate() - 52 * 7); - start.setDate(start.getDate() - start.getDay()); - - const weeks = []; - const cur = new Date(start); - while (cur <= today) { - const week = []; - for (let d = 0; d < 7; d++) { - const day = new Date(cur); - week.push(day <= today ? day : null); - cur.setDate(cur.getDate() + 1); - } - weeks.push(week); - } - return weeks; -} - -function getColor(count) { - if (count === 0) return "var(--color-empty)"; - if (count <= 3) return "var(--color-low)"; - if (count <= 10) return "var(--color-mid)"; - return "var(--color-high)"; -} - -export function render(weeks, counts) { - const CELL = 13; - const GAP = 2; - const SHIFT = CELL + GAP; - - const svgNS = "http://www.w3.org/2000/svg"; - const svg = document.createElementNS(svgNS, "svg"); - - svg.setAttribute("width", weeks.length * SHIFT + 30); - svg.setAttribute("height", SHIFT * 7 + 20); - - weeks.forEach((week, col) => { - week.forEach((day, row) => { - if (day === null) { - return; - } - const key = day.toISOString().slice(0, 10); // Get the key in yyyy-mm-dd format - - const count = counts[key] || 0; - - const rect = document.createElementNS(svgNS, "rect"); - rect.setAttribute("x", col * SHIFT + 30); - rect.setAttribute("y", row * SHIFT); - rect.setAttribute("height", CELL); - rect.setAttribute("width", CELL); - rect.setAttribute("fill", getColor(count)); - rect.setAttribute("rx", 2); - rect.setAttribute("data-date", key); - rect.setAttribute("data-count", count); - - svg.appendChild(rect); - }); - - document.getElementById("heatmap").appendChild(svg); - }); -} diff --git a/get_history/.gitignore b/get_history/.gitignore deleted file mode 100644 index a14702c..0000000 --- a/get_history/.gitignore +++ /dev/null @@ -1,34 +0,0 @@ -# dependencies (bun install) -node_modules - -# output -out -dist -*.tgz - -# code coverage -coverage -*.lcov - -# logs -logs -_.log -report.[0-9]_.[0-9]_.[0-9]_.[0-9]_.json - -# dotenv environment variable files -.env -.env.development.local -.env.test.local -.env.production.local -.env.local - -# caches -.eslintcache -.cache -*.tsbuildinfo - -# IntelliJ based IDEs -.idea - -# Finder (MacOS) folder config -.DS_Store diff --git a/get_history/bun.lock b/get_history/bun.lock deleted file mode 100644 index 978a13a..0000000 --- a/get_history/bun.lock +++ /dev/null @@ -1,26 +0,0 @@ -{ - "lockfileVersion": 1, - "configVersion": 1, - "workspaces": { - "": { - "name": "get_history", - "devDependencies": { - "@types/bun": "latest", - }, - "peerDependencies": { - "typescript": "^5", - }, - }, - }, - "packages": { - "@types/bun": ["@types/bun@1.3.13", "", { "dependencies": { "bun-types": "1.3.13" } }, "sha512-9fqXWk5YIHGGnUau9TEi+qdlTYDAnOj+xLCmSTwXfAIqXr2x4tytJb43E9uCvt09zJURKXwAtkoH4nLQfzeTXw=="], - - "@types/node": ["@types/node@25.6.2", "", { "dependencies": { "undici-types": "~7.19.0" } }, "sha512-sokuT28dxf9JT5Kady1fsXOvI4HVpjZa95NKT5y9PNTIrs2AsobR4GFAA90ZG8M+nxVRLysCXsVj6eGC7Vbrlw=="], - - "bun-types": ["bun-types@1.3.13", "", { "dependencies": { "@types/node": "*" } }, "sha512-QXKeHLlOLqQX9LgYaHJfzdBaV21T63HhFJnvuRCcjZiaUDpbs5ED1MgxbMra71CsryN/1dAoXuJJJwIv/2drVA=="], - - "typescript": ["typescript@5.9.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw=="], - - "undici-types": ["undici-types@7.19.2", "", {}, "sha512-qYVnV5OEm2AW8cJMCpdV20CDyaN3g0AjDlOGf1OW4iaDEx8MwdtChUp4zu4H0VP3nDRF/8RKWH+IPp9uW0YGZg=="], - } -} diff --git a/get_history/index.ts b/get_history/index.ts deleted file mode 100644 index 92e868d..0000000 --- a/get_history/index.ts +++ /dev/null @@ -1,248 +0,0 @@ -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 oneYearAgo = new Date(); -oneYearAgo.setFullYear(oneYearAgo.getFullYear() - 1); -const formattedOneYearAgo = oneYearAgo.toISOString().replace("Z", "+00:00"); - -const ghQuery = ` - { - user(login: "aselimov") { - contributionsCollection { - commitContributionsByRepository(maxRepositories: 100) { - repository { - nameWithOwner - isMirror - isPrivate - } - 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 repos = - data?.data?.user?.contributionsCollection - ?.commitContributionsByRepository ?? []; - - for (const repoEntry of repos) { - const repoName: string = repoEntry.repository.nameWithOwner; - const isMirror: boolean = repoEntry.repository.isMirror; - const isPrivate: boolean = repoEntry.repository.isPrivate; - - if (isMirror) continue; - - const displayName = isPrivate ? "private" : repoName; - - 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]![displayName] = (result[day]![displayName] ?? 0) + count; - } - } - - 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 makeActivityMap(): Promise { - const ghResponse = await fetch("https://api.github.com/graphql", { - method: "POST", - headers: { - "Content-Type": "application/json", - Authorization: `Bearer ${config.githubApiKey}`, - }, - body: JSON.stringify({ query: ghQuery }), - }); - - if (!ghResponse.ok) { - throw new Error( - `GitHub API error: ${ghResponse.status} ${ghResponse.statusText}`, - ); - } - - const ghData = await ghResponse.json(); - const ghMap = parseGitHubContributions(ghData); - const fjMap = await 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`, -); diff --git a/get_history/package.json b/get_history/package.json deleted file mode 100644 index 3d6e7ac..0000000 --- a/get_history/package.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "name": "get_history", - "module": "index.ts", - "type": "module", - "private": true, - "devDependencies": { - "@types/bun": "latest" - }, - "peerDependencies": { - "typescript": "^5" - } -} diff --git a/get_history/tsconfig.json b/get_history/tsconfig.json deleted file mode 100644 index b2e7497..0000000 --- a/get_history/tsconfig.json +++ /dev/null @@ -1,30 +0,0 @@ -{ - "compilerOptions": { - // Environment setup & latest features - "lib": ["ESNext"], - "target": "ESNext", - "module": "Preserve", - "moduleDetection": "force", - "jsx": "react-jsx", - "allowJs": true, - "types": ["bun"], - - // Bundler mode - "moduleResolution": "bundler", - "allowImportingTsExtensions": true, - "verbatimModuleSyntax": true, - "noEmit": true, - - // Best practices - "strict": true, - "skipLibCheck": true, - "noFallthroughCasesInSwitch": true, - "noUncheckedIndexedAccess": true, - "noImplicitOverride": true, - - // Some stricter flags (disabled by default) - "noUnusedLocals": false, - "noUnusedParameters": false, - "noPropertyAccessFromIndexSignature": false - } -} diff --git a/hugo.toml b/hugo.toml deleted file mode 100644 index 19c8846..0000000 --- a/hugo.toml +++ /dev/null @@ -1,3 +0,0 @@ -baseURL = 'https://example.org/' -locale = 'en-us' -title = 'My New Hugo Project'