website

#astro#js#html#css

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 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
- "build": "eleventy",
7
+ "tf:plan": "terraform -chdir=infra plan",
7
- "postinstall": "patch-package"
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
  };