website
git clone https://git.pyrossh.dev/website
木 Personal website of pyrossh. Built with astrojs, shiki, vite.
778e0a4
— pyrossh
2026-08-05T10:34:09+05:30
add infra
Generated files not shown: package-lock.json
- .gitignore +4 -0
- infra/.terraform.lock.hcl +19 -0
- infra/main.tf +44 -0
- infra/outputs.tf +11 -0
- infra/variables.tf +11 -0
- package.json +4 -4
- src/_data/repoCommits.js +21 -1
- src/_data/repoFiles.js +7 -1
.gitignore
CHANGED
|
@@ -6,3 +6,7 @@ dist/
|
|
|
6
6
|
.wrangler/
|
|
7
7
|
playwright-report/
|
|
8
8
|
test-results/
|
|
9
|
+
infra/.terraform/
|
|
10
|
+
infra/*.tfstate
|
|
11
|
+
infra/*.tfstate.*
|
|
12
|
+
infra/terraform.tfvars
|
infra/.terraform.lock.hcl
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
# This file is maintained automatically by "terraform init".
|
|
2
|
+
# Manual edits may be lost in future updates.
|
|
3
|
+
|
|
4
|
+
provider "registry.terraform.io/cloudflare/cloudflare" {
|
|
5
|
+
version = "5.22.0"
|
|
6
|
+
constraints = "~> 5.0"
|
|
7
|
+
hashes = [
|
|
8
|
+
"h1:H3XmcpcMLeDyuGU6QsBXeVOB2CQGnIVRt6PJf7hfrtE=",
|
|
9
|
+
"zh:45f3b7c50254b1da1dc21e77e03cd1e931cab40fb75c7cba822a53ed54cd232e",
|
|
10
|
+
"zh:8437138c0af1a1a557b516ca42dae54499933cc81072396abe7eefd267218f79",
|
|
11
|
+
"zh:9140044a3c909e1a2767c8b2dd95370b8152bcbc7cb26c47e376e81f75512d32",
|
|
12
|
+
"zh:b69a80d2bb33059e93dec94490065fe210c621d8bdf32d745b1d1972aa85abbf",
|
|
13
|
+
"zh:ca4bf16742a7e59319a482f003d32f6f6abbc5fbf862916ce1a136278e6d420d",
|
|
14
|
+
"zh:cd767016ea7382e384e560f6ddb302637ecb1b53ece15c9326032010effe6c33",
|
|
15
|
+
"zh:d952f92ae1a688ed376757127ae1aa3b625571bf1fc606214a5822eae98213e0",
|
|
16
|
+
"zh:f5213814c8ca0d736623438b283143b9a7f98e72ca4992eb906966e7f8cc383b",
|
|
17
|
+
"zh:f809ab383cca0a5f83072981c64208cbd7fa67e986a86ee02dd2c82333221e32",
|
|
18
|
+
]
|
|
19
|
+
}
|
infra/main.tf
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
terraform {
|
|
2
|
+
required_providers {
|
|
3
|
+
cloudflare = {
|
|
4
|
+
source = "cloudflare/cloudflare"
|
|
5
|
+
version = "~> 5.0"
|
|
6
|
+
}
|
|
7
|
+
}
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
provider "cloudflare" {}
|
|
11
|
+
|
|
12
|
+
resource "cloudflare_r2_bucket" "website" {
|
|
13
|
+
account_id = var.cloudflare_account_id
|
|
14
|
+
name = "website"
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
resource "cloudflare_r2_custom_domain" "website" {
|
|
18
|
+
account_id = var.cloudflare_account_id
|
|
19
|
+
bucket_name = cloudflare_r2_bucket.website.name
|
|
20
|
+
domain = "pyrossh.dev"
|
|
21
|
+
zone_id = var.cloudflare_zone_id
|
|
22
|
+
enabled = true
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
# Syncs ../dist into the bucket on every `terraform apply` — `terraform_data`
|
|
26
|
+
# has no real infra state of its own, so `triggers_replace` on a timestamp is
|
|
27
|
+
# the standard way to force its provisioner to re-run every time rather than
|
|
28
|
+
# only on the first apply. `rclone sync` (not `copy`) both uploads changed
|
|
29
|
+
# files and deletes remote objects no longer present locally, so the bucket
|
|
30
|
+
# always matches the latest build exactly.
|
|
31
|
+
#
|
|
32
|
+
# Requires `rclone` on the machine running `terraform apply`, with a remote
|
|
33
|
+
# named "website" configured for R2 (`rclone config`) — an S3-compatible
|
|
34
|
+
# access key/secret scoped to this bucket, not the Cloudflare API token used
|
|
35
|
+
# above.
|
|
36
|
+
resource "terraform_data" "sync_website" {
|
|
37
|
+
triggers_replace = [timestamp()]
|
|
38
|
+
|
|
39
|
+
depends_on = [cloudflare_r2_bucket.website]
|
|
40
|
+
|
|
41
|
+
provisioner "local-exec" {
|
|
42
|
+
command = "rclone sync ${path.module}/../dist website:${cloudflare_r2_bucket.website.name} --checksum --fast-list"
|
|
43
|
+
}
|
|
44
|
+
}
|
infra/outputs.tf
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
output "bucket_name" {
|
|
2
|
+
value = cloudflare_r2_bucket.website.name
|
|
3
|
+
}
|
|
4
|
+
|
|
5
|
+
output "custom_domain_ownership_status" {
|
|
6
|
+
value = cloudflare_r2_custom_domain.website.status.ownership
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
output "custom_domain_ssl_status" {
|
|
10
|
+
value = cloudflare_r2_custom_domain.website.status.ssl
|
|
11
|
+
}
|
infra/variables.tf
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
variable "cloudflare_account_id" {
|
|
2
|
+
description = "Cloudflare account ID"
|
|
3
|
+
type = string
|
|
4
|
+
default = "dfdb8ca4e419990852eb214c5b50bd5e"
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
variable "cloudflare_zone_id" {
|
|
8
|
+
description = "Zone ID for the pyrossh.dev zone"
|
|
9
|
+
type = string
|
|
10
|
+
default = "e95a5d8957b75859c814579deebfce33"
|
|
11
|
+
}
|
package.json
CHANGED
|
@@ -3,8 +3,9 @@
|
|
|
3
3
|
"type": "module",
|
|
4
4
|
"scripts": {
|
|
5
5
|
"dev": "eleventy --serve",
|
|
6
|
+
"build": "node --max-old-space-size=8192 ./node_modules/.bin/eleventy",
|
|
6
|
-
"
|
|
7
|
+
"tf:plan": "terraform -chdir=infra plan",
|
|
7
|
-
"
|
|
8
|
+
"tf:apply": "terraform -chdir=infra apply"
|
|
8
9
|
},
|
|
9
10
|
"dependencies": {
|
|
10
11
|
"diff2html": "^3.4.52",
|
|
@@ -16,7 +17,6 @@
|
|
|
16
17
|
"@11ty/eleventy": "^3.0.0",
|
|
17
18
|
"@11ty/eleventy-plugin-rss": "^2.0.0",
|
|
18
19
|
"@11ty/eleventy-plugin-syntaxhighlight": "^5.0.0",
|
|
19
|
-
"@iconify-json/vscode-icons": "^1.2.68"
|
|
20
|
+
"@iconify-json/vscode-icons": "^1.2.68"
|
|
20
|
-
"patch-package": "^8.0.1"
|
|
21
21
|
}
|
|
22
22
|
}
|
src/_data/repoCommits.js
CHANGED
|
@@ -5,10 +5,22 @@ import { mapPool } from "../_helpers/pool.js";
|
|
|
5
5
|
|
|
6
6
|
const CONCURRENCY = 8;
|
|
7
7
|
|
|
8
|
+
// Some repos have vendored/generated/binary-as-text blobs committed at some
|
|
9
|
+
// point in their history (a minified bundle, a compiled binary git doesn't
|
|
10
|
+
// detect as binary, ...) — `git show -p` dumps the whole file as a diff on
|
|
11
|
+
// every commit that touches it, and diff2html's per-line markup multiplies
|
|
12
|
+
// that further. Left unguarded, that's easily 100s of MB of HTML per repo,
|
|
13
|
+
// held for every commit at once across every repo — well past Node's default
|
|
14
|
+
// heap. Cap it the way GitHub's own diff viewer does: skip rendering past a
|
|
15
|
+
// size threshold rather than trying to syntax-highlight a multi-MB blob.
|
|
16
|
+
const MAX_DIFF_SIZE = 300 * 1024;
|
|
17
|
+
const DIFF_TOO_LARGE_HTML = '<p class="diff-too-large">This diff is too large to display.</p>';
|
|
18
|
+
|
|
8
19
|
const toDiffHtml = (diffText) => {
|
|
9
20
|
const idx = diffText.indexOf("diff --git");
|
|
10
21
|
const patch = idx >= 0 ? diffText.slice(idx) : "";
|
|
11
22
|
if (!patch.trim()) return "";
|
|
23
|
+
if (patch.length > MAX_DIFF_SIZE) return DIFF_TOO_LARGE_HTML;
|
|
12
24
|
try {
|
|
13
25
|
const json = parseDiff(patch);
|
|
14
26
|
return renderDiffHtml(json, { drawFileList: true, matching: "lines" });
|
|
@@ -39,7 +51,15 @@ export default async () => {
|
|
|
39
51
|
if (sha) writeCache(cacheKey, sha, entries);
|
|
40
52
|
}
|
|
41
53
|
|
|
54
|
+
// Only `id`/`data` ever get read off `item.repo` in commit.njk — not the
|
|
55
|
+
// full `repo` object's `files`/`tree`/`commits`/`readme`. Eleventy's
|
|
56
|
+
// paginator deep-clones each page's data slice for isolation, so
|
|
57
|
+
// embedding the whole repo (every commit's metadata, every file, the
|
|
58
|
+
// tree, the README) here means that whole structure gets cloned once
|
|
59
|
+
// per commit page — the actual multiplier behind the OOM, not just the
|
|
60
|
+
// diff HTML size.
|
|
61
|
+
const slimRepo = { id: repo.id, data: repo.data };
|
|
42
|
-
for (const entry of entries) pages.push({ repoId: repo.id, repo, ...entry });
|
|
62
|
+
for (const entry of entries) pages.push({ repoId: repo.id, repo: slimRepo, ...entry });
|
|
43
63
|
}
|
|
44
64
|
return pages;
|
|
45
65
|
};
|
src/_data/repoFiles.js
CHANGED
|
@@ -51,7 +51,13 @@ export default async () => {
|
|
|
51
51
|
if (cacheable) writeCache(cacheKey, sha, entries);
|
|
52
52
|
}
|
|
53
53
|
|
|
54
|
+
// file.njk/file-history.njk/file-blame.njk only ever read `id`/`data`/
|
|
55
|
+
// `tree` off `item.repo` — not `files`/`commits`/`readme`. See the same
|
|
56
|
+
// note in repoCommits.js: Eleventy clones each page's data slice, so
|
|
57
|
+
// keeping this slim avoids cloning the repo's full commit/file lists on
|
|
58
|
+
// every single file page.
|
|
59
|
+
const slimRepo = { id: repo.id, data: repo.data, tree: repo.tree };
|
|
54
|
-
for (const entry of entries) pages.push({ repoId: repo.id, repo, ...entry });
|
|
60
|
+
for (const entry of entries) pages.push({ repoId: repo.id, repo: slimRepo, ...entry });
|
|
55
61
|
}
|
|
56
62
|
return pages;
|
|
57
63
|
};
|