website
git clone https://git.pyrossh.dev/website
木 Personal website of pyrossh. Built with astrojs, shiki, vite.
src/_data/repoFiles.js
| 3c79a68 | 1 | import fs from "node:fs"; |
| 3c79a68 | 2 | import path from "node:path"; |
| ada6e53 | 3 | import { readCache, writeCache } from "../_helpers/dataCache.js"; |
| 3c79a68 | 4 | import { |
| 3c79a68 | 5 | BINARY_EXTENSIONS, |
| 3c79a68 | 6 | IMAGE_EXTENSIONS, |
| 3c79a68 | 7 | LARGE_FILE_SIZE, |
| 3c79a68 | 8 | discoverLocalRepos, |
| 3c79a68 | 9 | getBlame, |
| 3c79a68 | 10 | getFileHistory, |
| ada6e53 | 11 | getHeadSha, |
| ada6e53 | 12 | isDirty, |
| 1c2fa8e | 13 | toDataUri, |
| 3c79a68 | 14 | } from "../_helpers/gitRepos.js"; |
| 3c79a68 | 15 | import { highlightCode } from "../_helpers/highlightCode.js"; |
| ada6e53 | 16 | import { mapPool } from "../_helpers/pool.js"; |
| ada6e53 | 17 | |
| ada6e53 | 18 | const CONCURRENCY = 8; |
| 3c79a68 | 19 | |
| ada6e53 | 20 | const renderFile = (repoDir, file) => { |
| ada6e53 | 21 | const full = path.join(repoDir, file.path); |
| ada6e53 | 22 | if (BINARY_EXTENSIONS.has(file.ext)) return { kind: "binary" }; |
| ada6e53 | 23 | if (file.size > LARGE_FILE_SIZE) return { kind: "large" }; |
| ada6e53 | 24 | if (IMAGE_EXTENSIONS.has(file.ext)) return { kind: "image", src: toDataUri(fs.readFileSync(full), file.ext) }; |
| ada6e53 | 25 | const { html, lang } = highlightCode(fs.readFileSync(full, "utf8"), file.ext); |
| ada6e53 | 26 | return { kind: "text", html, lang }; |
| ada6e53 | 27 | }; |
| ada6e53 | 28 | |
| 3c79a68 | 29 | export default async () => { |
| 3c79a68 | 30 | const repos = discoverLocalRepos(); |
| 3c79a68 | 31 | const pages = []; |
| 3c79a68 | 32 | for (const repo of repos) { |
| ada6e53 | 33 | const sha = getHeadSha(repo.dir); |
| ada6e53 | 34 | const cacheable = sha && !isDirty(repo.dir); |
| ada6e53 | 35 | const cacheKey = `files-${repo.id}`; |
| ada6e53 | 36 | |
| ada6e53 | 37 | let entries = cacheable ? readCache(cacheKey, sha) : null; |
| ada6e53 | 38 | if (!entries) { |
| ada6e53 | 39 | entries = await mapPool(repo.files, CONCURRENCY, async (file) => { |
| ada6e53 | 40 | const render = renderFile(repo.dir, file); |
| ada6e53 | 41 | const [history, blame] = await Promise.all([ |
| ada6e53 | 42 | getFileHistory(repo.dir, file.path), |
| ada6e53 | 43 | render.kind === "text" ? getBlame(repo.dir, file.path) : [], |
| ada6e53 | 44 | ]); |
| ada6e53 | 45 | return { file, render, history, blame }; |
| 3c79a68 | 46 | }); |
| ada6e53 | 47 | if (cacheable) writeCache(cacheKey, sha, entries); |
| 3c79a68 | 48 | } |
| ada6e53 | 49 | |
| 778e0a4 | 50 | // file.njk/file-history.njk/file-blame.njk only ever read `id`/`data`/ |
| 09c6a56 | 51 | // `tree`/`version` off `item.repo` — not `files`/`commits`/`readme`. |
| 09c6a56 | 52 | // See the same note in repoCommits.js: Eleventy clones each page's data |
| 09c6a56 | 53 | // slice, so keeping this slim avoids cloning the repo's full commit/ |
| 09c6a56 | 54 | // file lists on every single file page. |
| 09c6a56 | 55 | const slimRepo = { id: repo.id, data: repo.data, tree: repo.tree, version: repo.version }; |
| 778e0a4 | 56 | for (const entry of entries) pages.push({ repoId: repo.id, repo: slimRepo, ...entry }); |
| 3c79a68 | 57 | } |
| 3c79a68 | 58 | return pages; |
| 3c79a68 | 59 | }; |