website
git clone https://git.pyrossh.dev/website
木 Personal website of pyrossh. Built with astrojs, shiki, vite.
src/_data/repoCommits.js
| 3c79a68 | 1 | import { html as renderDiffHtml, parse as parseDiff } from "diff2html"; |
| ada6e53 | 2 | import { readCache, writeCache } from "../_helpers/dataCache.js"; |
| ada6e53 | 3 | import { discoverLocalRepos, getCommitDiff, getHeadSha } from "../_helpers/gitRepos.js"; |
| ada6e53 | 4 | import { mapPool } from "../_helpers/pool.js"; |
| ada6e53 | 5 | |
| ada6e53 | 6 | const CONCURRENCY = 8; |
| 3c79a68 | 7 | |
| 778e0a4 | 8 | // Some repos have vendored/generated/binary-as-text blobs committed at some |
| 778e0a4 | 9 | // point in their history (a minified bundle, a compiled binary git doesn't |
| 778e0a4 | 10 | // detect as binary, ...) — `git show -p` dumps the whole file as a diff on |
| 778e0a4 | 11 | // every commit that touches it, and diff2html's per-line markup multiplies |
| 778e0a4 | 12 | // that further. Left unguarded, that's easily 100s of MB of HTML per repo, |
| 778e0a4 | 13 | // held for every commit at once across every repo — well past Node's default |
| 778e0a4 | 14 | // heap. Cap it the way GitHub's own diff viewer does: skip rendering past a |
| 778e0a4 | 15 | // size threshold rather than trying to syntax-highlight a multi-MB blob. |
| 778e0a4 | 16 | const MAX_DIFF_SIZE = 300 * 1024; |
| 778e0a4 | 17 | const DIFF_TOO_LARGE_HTML = '<p class="diff-too-large">This diff is too large to display.</p>'; |
| 778e0a4 | 18 | |
| 3c79a68 | 19 | const toDiffHtml = (diffText) => { |
| 3c79a68 | 20 | const idx = diffText.indexOf("diff --git"); |
| 3c79a68 | 21 | const patch = idx >= 0 ? diffText.slice(idx) : ""; |
| 3c79a68 | 22 | if (!patch.trim()) return ""; |
| 778e0a4 | 23 | if (patch.length > MAX_DIFF_SIZE) return DIFF_TOO_LARGE_HTML; |
| 3c79a68 | 24 | try { |
| 3c79a68 | 25 | const json = parseDiff(patch); |
| 3c79a68 | 26 | return renderDiffHtml(json, { drawFileList: true, matching: "lines" }); |
| 3c79a68 | 27 | } catch { |
| 3c79a68 | 28 | return ""; |
| 3c79a68 | 29 | } |
| 3c79a68 | 30 | }; |
| 3c79a68 | 31 | |
| 3c79a68 | 32 | export default async () => { |
| 3c79a68 | 33 | const repos = discoverLocalRepos(); |
| 3c79a68 | 34 | const pages = []; |
| 3c79a68 | 35 | for (const repo of repos) { |
| ada6e53 | 36 | // Unlike repoFiles.js, none of this reads the working tree — `git |
| ada6e53 | 37 | // show` only ever sees committed history — so it's safe to cache on |
| ada6e53 | 38 | // HEAD sha alone, with no working-tree-clean requirement. That |
| ada6e53 | 39 | // matters because this repo (website) is usually dirty while it's |
| ada6e53 | 40 | // being actively worked on, and its commit history is the most |
| ada6e53 | 41 | // expensive part of the build. |
| ada6e53 | 42 | const sha = getHeadSha(repo.dir); |
| ada6e53 | 43 | const cacheKey = `commits-${repo.id}`; |
| ada6e53 | 44 | |
| ada6e53 | 45 | let entries = sha ? readCache(cacheKey, sha) : null; |
| ada6e53 | 46 | if (!entries) { |
| ada6e53 | 47 | entries = await mapPool(repo.commits, CONCURRENCY, async (commit) => { |
| ada6e53 | 48 | const diffText = await getCommitDiff(repo.dir, commit.hash); |
| ada6e53 | 49 | return { commit, diffHtml: toDiffHtml(diffText) }; |
| ada6e53 | 50 | }); |
| ada6e53 | 51 | if (sha) writeCache(cacheKey, sha, entries); |
| 3c79a68 | 52 | } |
| ada6e53 | 53 | |
| 09c6a56 | 54 | // Only `id`/`data`/`version` ever get read off `item.repo` in |
| 09c6a56 | 55 | // commit.njk — not the full `repo` object's `files`/`tree`/`commits`/ |
| 09c6a56 | 56 | // `readme`. Eleventy's paginator deep-clones each page's data slice for |
| 09c6a56 | 57 | // isolation, so embedding the whole repo (every commit's metadata, |
| 09c6a56 | 58 | // every file, the tree, the README) here means that whole structure |
| 09c6a56 | 59 | // gets cloned once per commit page — the actual multiplier behind the |
| 09c6a56 | 60 | // OOM, not just the diff HTML size. |
| 09c6a56 | 61 | const slimRepo = { id: repo.id, data: repo.data, version: repo.version }; |
| 778e0a4 | 62 | for (const entry of entries) pages.push({ repoId: repo.id, repo: slimRepo, ...entry }); |
| 3c79a68 | 63 | } |
| 3c79a68 | 64 | return pages; |
| 3c79a68 | 65 | }; |