website
git clone https://git.pyrossh.dev/website
木 Personal website of pyrossh. Built with astrojs, shiki, vite.
1c2fa8e
— pyrossh
2026-08-11T18:46:18+05:30
fix README screenshots
- eleventy.config.js +15 -0
- infra/terraform.tfstate +7 -7
- infra/terraform.tfstate.backup +29 -7
- package.json +1 -1
- src/_data/localRepos.js +2 -2
- src/_data/repoFiles.js +1 -5
- src/_helpers/gitRepos.js +45 -0
eleventy.config.js
CHANGED
|
@@ -3,6 +3,7 @@ import path from "node:path";
|
|
|
3
3
|
import syntaxHighlight from "@11ty/eleventy-plugin-syntaxhighlight";
|
|
4
4
|
import pluginRss from "@11ty/eleventy-plugin-rss";
|
|
5
5
|
import { fileIcon, folderIcon, iconSpriteDefs } from "./src/_helpers/fileIcons.js";
|
|
6
|
+
import { discoverLocalRepos, findReadmeImagePaths } from "./src/_helpers/gitRepos.js";
|
|
6
7
|
|
|
7
8
|
// theme-switcher (src/components/theme-switcher.js) used to be a Web
|
|
8
9
|
// Component (via @elenajs/core + an @elenajs/ssr build-time transform
|
|
@@ -56,6 +57,20 @@ export default function (eleventyConfig) {
|
|
|
56
57
|
"node_modules/htmx.org/dist/htmx.min.js": "assets/vendor/htmx.min.js",
|
|
57
58
|
});
|
|
58
59
|
|
|
60
|
+
// README images referenced by a repo-relative path (e.g. `shots/shot1.png`)
|
|
61
|
+
// only render on GitHub because GitHub serves the file itself. Copying
|
|
62
|
+
// just the handful of files each README actually references to a `raw/`
|
|
63
|
+
// route keeps them real, loadable URLs without inlining every screenshot
|
|
64
|
+
// as base64 into the README's HTML (see resolveReadmeImages in
|
|
65
|
+
// gitRepos.js, which points <img> at these same paths).
|
|
66
|
+
for (const repo of discoverLocalRepos()) {
|
|
67
|
+
for (const relPath of findReadmeImagePaths(repo.readme, repo.dir)) {
|
|
68
|
+
eleventyConfig.addPassthroughCopy({
|
|
69
|
+
[path.join(repo.dir, relPath)]: `repos/${repo.id}/raw/${relPath}`,
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
59
74
|
eleventyConfig.addPlugin(syntaxHighlight);
|
|
60
75
|
eleventyConfig.addPlugin(pluginRss);
|
|
61
76
|
|
infra/terraform.tfstate
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 4,
|
|
3
3
|
"terraform_version": "1.13.3",
|
|
4
|
-
"serial":
|
|
4
|
+
"serial": 65,
|
|
5
5
|
"lineage": "6cb48eaa-b069-089b-fce3-e277626defab",
|
|
6
6
|
"outputs": {
|
|
7
7
|
"bucket_name": {
|
|
@@ -155,12 +155,12 @@
|
|
|
155
155
|
{
|
|
156
156
|
"schema_version": 0,
|
|
157
157
|
"attributes": {
|
|
158
|
-
"id": "
|
|
158
|
+
"id": "68b15cd2-01d4-a423-1a46-a72b890a2181",
|
|
159
159
|
"input": null,
|
|
160
160
|
"output": null,
|
|
161
161
|
"triggers_replace": {
|
|
162
162
|
"value": [
|
|
163
|
-
"2026-08-
|
|
163
|
+
"2026-08-09T10:20:29Z"
|
|
164
164
|
],
|
|
165
165
|
"type": [
|
|
166
166
|
"tuple",
|
|
@@ -184,12 +184,12 @@
|
|
|
184
184
|
{
|
|
185
185
|
"schema_version": 0,
|
|
186
186
|
"attributes": {
|
|
187
|
-
"id": "
|
|
187
|
+
"id": "2965dfb2-b48d-0edd-bec4-a60d0143c24b",
|
|
188
188
|
"input": null,
|
|
189
189
|
"output": null,
|
|
190
190
|
"triggers_replace": {
|
|
191
191
|
"value": [
|
|
192
|
-
"2026-08-
|
|
192
|
+
"2026-08-09T10:23:13Z"
|
|
193
193
|
],
|
|
194
194
|
"type": [
|
|
195
195
|
"tuple",
|
|
@@ -218,12 +218,12 @@
|
|
|
218
218
|
{
|
|
219
219
|
"schema_version": 0,
|
|
220
220
|
"attributes": {
|
|
221
|
-
"id": "
|
|
221
|
+
"id": "7b4cd3c4-6c8b-a4b7-b350-5404beca57c9",
|
|
222
222
|
"input": null,
|
|
223
223
|
"output": null,
|
|
224
224
|
"triggers_replace": {
|
|
225
225
|
"value": [
|
|
226
|
-
"2026-08-
|
|
226
|
+
"2026-08-09T10:20:46Z"
|
|
227
227
|
],
|
|
228
228
|
"type": [
|
|
229
229
|
"tuple",
|
infra/terraform.tfstate.backup
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 4,
|
|
3
3
|
"terraform_version": "1.13.3",
|
|
4
|
-
"serial":
|
|
4
|
+
"serial": 58,
|
|
5
5
|
"lineage": "6cb48eaa-b069-089b-fce3-e277626defab",
|
|
6
6
|
"outputs": {
|
|
7
7
|
"bucket_name": {
|
|
@@ -18,6 +18,28 @@
|
|
|
18
18
|
}
|
|
19
19
|
},
|
|
20
20
|
"resources": [
|
|
21
|
+
{
|
|
22
|
+
"mode": "managed",
|
|
23
|
+
"type": "cloudflare_r2_bucket",
|
|
24
|
+
"name": "apps",
|
|
25
|
+
"provider": "provider[\"registry.terraform.io/cloudflare/cloudflare\"]",
|
|
26
|
+
"instances": [
|
|
27
|
+
{
|
|
28
|
+
"schema_version": 500,
|
|
29
|
+
"attributes": {
|
|
30
|
+
"account_id": "dfdb8ca4e419990852eb214c5b50bd5e",
|
|
31
|
+
"creation_date": "2026-08-07T14:09:28.899Z",
|
|
32
|
+
"id": "apps",
|
|
33
|
+
"jurisdiction": "default",
|
|
34
|
+
"location": "APAC",
|
|
35
|
+
"name": "apps",
|
|
36
|
+
"storage_class": "Standard"
|
|
37
|
+
},
|
|
38
|
+
"sensitive_attributes": [],
|
|
39
|
+
"identity_schema_version": 0
|
|
40
|
+
}
|
|
41
|
+
]
|
|
42
|
+
},
|
|
21
43
|
{
|
|
22
44
|
"mode": "managed",
|
|
23
45
|
"type": "cloudflare_r2_bucket",
|
|
@@ -133,12 +155,12 @@
|
|
|
133
155
|
{
|
|
134
156
|
"schema_version": 0,
|
|
135
157
|
"attributes": {
|
|
136
|
-
"id": "
|
|
158
|
+
"id": "08bd1d67-88db-0cb1-5d58-db6264f7fe13",
|
|
137
159
|
"input": null,
|
|
138
160
|
"output": null,
|
|
139
161
|
"triggers_replace": {
|
|
140
162
|
"value": [
|
|
141
|
-
"2026-08-
|
|
163
|
+
"2026-08-07T14:09:27Z"
|
|
142
164
|
],
|
|
143
165
|
"type": [
|
|
144
166
|
"tuple",
|
|
@@ -162,12 +184,12 @@
|
|
|
162
184
|
{
|
|
163
185
|
"schema_version": 0,
|
|
164
186
|
"attributes": {
|
|
165
|
-
"id": "
|
|
187
|
+
"id": "3ec957d7-b52d-b06a-a7ff-92284b8ad15a",
|
|
166
188
|
"input": null,
|
|
167
189
|
"output": null,
|
|
168
190
|
"triggers_replace": {
|
|
169
191
|
"value": [
|
|
170
|
-
"2026-08-
|
|
192
|
+
"2026-08-07T14:10:17Z"
|
|
171
193
|
],
|
|
172
194
|
"type": [
|
|
173
195
|
"tuple",
|
|
@@ -196,12 +218,12 @@
|
|
|
196
218
|
{
|
|
197
219
|
"schema_version": 0,
|
|
198
220
|
"attributes": {
|
|
199
|
-
"id": "
|
|
221
|
+
"id": "fc1bdc6f-6098-7963-e883-53a937260a47",
|
|
200
222
|
"input": null,
|
|
201
223
|
"output": null,
|
|
202
224
|
"triggers_replace": {
|
|
203
225
|
"value": [
|
|
204
|
-
"2026-08-
|
|
226
|
+
"2026-08-07T14:09:40Z"
|
|
205
227
|
],
|
|
206
228
|
"type": [
|
|
207
229
|
"tuple",
|
package.json
CHANGED
|
@@ -25,4 +25,4 @@
|
|
|
25
25
|
"@iconify-json/vscode-icons": "^1.2.68",
|
|
26
26
|
"playwright": "^1.62.1"
|
|
27
27
|
}
|
|
28
|
-
}
|
|
28
|
+
}
|
src/_data/localRepos.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import MarkdownIt from "markdown-it";
|
|
2
|
-
import { discoverLocalRepos } from "../_helpers/gitRepos.js";
|
|
2
|
+
import { discoverLocalRepos, resolveReadmeImages } from "../_helpers/gitRepos.js";
|
|
3
3
|
import { highlightFenceCode } from "../_helpers/highlightCode.js";
|
|
4
4
|
|
|
5
5
|
const md = new MarkdownIt({ html: true, linkify: true, highlight: highlightFenceCode });
|
|
@@ -8,6 +8,6 @@ export default async () => {
|
|
|
8
8
|
const repos = discoverLocalRepos();
|
|
9
9
|
return repos.map((repo) => ({
|
|
10
10
|
...repo,
|
|
11
|
-
readmeHtml: repo.readme ? md.render(repo.readme) : undefined,
|
|
11
|
+
readmeHtml: repo.readme ? resolveReadmeImages(md.render(repo.readme), repo.id, repo.dir) : undefined,
|
|
12
12
|
}));
|
|
13
13
|
};
|
src/_data/repoFiles.js
CHANGED
|
@@ -10,17 +10,13 @@ import {
|
|
|
10
10
|
getFileHistory,
|
|
11
11
|
getHeadSha,
|
|
12
12
|
isDirty,
|
|
13
|
+
toDataUri,
|
|
13
14
|
} from "../_helpers/gitRepos.js";
|
|
14
15
|
import { highlightCode } from "../_helpers/highlightCode.js";
|
|
15
16
|
import { mapPool } from "../_helpers/pool.js";
|
|
16
17
|
|
|
17
18
|
const CONCURRENCY = 8;
|
|
18
19
|
|
|
19
|
-
const toDataUri = (buf, ext) => {
|
|
20
|
-
const mime = ext === "svg" ? "image/svg+xml" : `image/${ext}`;
|
|
21
|
-
return `data:${mime};base64,${buf.toString("base64")}`;
|
|
22
|
-
};
|
|
23
|
-
|
|
24
20
|
const renderFile = (repoDir, file) => {
|
|
25
21
|
const full = path.join(repoDir, file.path);
|
|
26
22
|
if (BINARY_EXTENSIONS.has(file.ext)) return { kind: "binary" };
|
src/_helpers/gitRepos.js
CHANGED
|
@@ -274,6 +274,51 @@ const readReadme = (repoDir) => {
|
|
|
274
274
|
return undefined;
|
|
275
275
|
};
|
|
276
276
|
|
|
277
|
+
export const toDataUri = (buf, ext) => {
|
|
278
|
+
const mime = ext === "svg" ? "image/svg+xml" : `image/${ext}`;
|
|
279
|
+
return `data:${mime};base64,${buf.toString("base64")}`;
|
|
280
|
+
};
|
|
281
|
+
|
|
282
|
+
const isAbsoluteSrc = (src) => /^([a-z][a-z0-9+.-]*:)?\/\//i.test(src) || src.startsWith("data:");
|
|
283
|
+
|
|
284
|
+
const IMG_SRC_PATTERNS = [
|
|
285
|
+
/!\[[^\]]*\]\(\s*([^)\s]+)(?:\s+"[^"]*")?\s*\)/g,
|
|
286
|
+
/<img\b[^>]*?\bsrc\s*=\s*"([^"]*)"/gi,
|
|
287
|
+
];
|
|
288
|
+
|
|
289
|
+
const isLocalRepoImage = (repoDir, src) => {
|
|
290
|
+
if (!src || isAbsoluteSrc(src)) return false;
|
|
291
|
+
const full = path.resolve(repoDir, src);
|
|
292
|
+
if (!(full + path.sep).startsWith(path.resolve(repoDir) + path.sep)) return false;
|
|
293
|
+
const ext = path.extname(src).slice(1).toLowerCase();
|
|
294
|
+
return IMAGE_EXTENSIONS.has(ext) && fs.existsSync(full);
|
|
295
|
+
};
|
|
296
|
+
|
|
297
|
+
// READMEs commonly reference screenshots with a path relative to the repo
|
|
298
|
+
// root (`shots/shot1.png`) rather than a URL — that only renders on GitHub
|
|
299
|
+
// because GitHub serves the file itself. The paths this finds get passed to
|
|
300
|
+
// eleventy.config.js, which copies just those files to a `raw/` route
|
|
301
|
+
// alongside the repo, so resolveReadmeImages can point the README at a real
|
|
302
|
+
// URL instead of inlining the bytes as a data URI (which would bloat every
|
|
303
|
+
// page load of the README with base64).
|
|
304
|
+
export const findReadmeImagePaths = (readme, repoDir) => {
|
|
305
|
+
if (!readme) return [];
|
|
306
|
+
const found = new Set();
|
|
307
|
+
for (const re of IMG_SRC_PATTERNS) {
|
|
308
|
+
for (const match of readme.matchAll(re)) {
|
|
309
|
+
const src = match[1];
|
|
310
|
+
if (isLocalRepoImage(repoDir, src)) found.add(path.posix.normalize(src));
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
return [...found];
|
|
314
|
+
};
|
|
315
|
+
|
|
316
|
+
export const resolveReadmeImages = (html, repoId, repoDir) =>
|
|
317
|
+
html.replace(/(<img\b[^>]*?\bsrc\s*=\s*")([^"]*)(")/gi, (match, pre, src, post) => {
|
|
318
|
+
if (!isLocalRepoImage(repoDir, src)) return match;
|
|
319
|
+
return `${pre}/repos/${repoId}/raw/${path.posix.normalize(src)}${post}`;
|
|
320
|
+
});
|
|
321
|
+
|
|
277
322
|
const readCargoVersion = (repoDir) => {
|
|
278
323
|
const p = path.join(repoDir, "Cargo.toml");
|
|
279
324
|
if (!fs.existsSync(p)) return undefined;
|