Skip to content

Commit f9134bf

Browse files
committed
Efficient caching
1 parent 6badb85 commit f9134bf

1 file changed

Lines changed: 115 additions & 6 deletions

File tree

.github/workflows/deploy.yml

Lines changed: 115 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -22,21 +22,130 @@ jobs:
2222
runs-on: ubuntu-latest
2323
steps:
2424
- uses: actions/checkout@v4
25+
with:
26+
fetch-depth: 0
2527
- uses: ruby/setup-ruby@v1
2628
with:
2729
ruby-version: "3.3"
2830
bundler-cache: true
29-
- run: bundle exec jekyll build
31+
- name: Build previous site
32+
id: previous-build
33+
continue-on-error: true
34+
run: |
35+
set -euo pipefail
36+
37+
if [ "${{ github.event_name }}" = "push" ] && [ "${{ github.event.before }}" != "0000000000000000000000000000000000000000" ]; then
38+
previous_ref="${{ github.event.before }}"
39+
else
40+
previous_ref="HEAD^"
41+
fi
42+
43+
git worktree add --detach "$RUNNER_TEMP/previous-site" "$previous_ref"
44+
bundle exec jekyll build \
45+
--source "$RUNNER_TEMP/previous-site" \
46+
--destination "$RUNNER_TEMP/previous-site-build"
47+
- name: Build current site
48+
run: bundle exec jekyll build
49+
- name: Find changed routes
50+
id: changed-routes
51+
env:
52+
SITE_URL: https://cb341.dev
53+
run: |
54+
set -euo pipefail
55+
56+
if [ "${{ steps.previous-build.outcome }}" != "success" ]; then
57+
: > "$RUNNER_TEMP/purge-urls.txt"
58+
echo "count=0" >> "$GITHUB_OUTPUT"
59+
echo "No previous build available; skipping route-specific Cloudflare purge."
60+
exit 0
61+
fi
62+
63+
python3 <<'PY'
64+
import hashlib
65+
import os
66+
from pathlib import Path
67+
68+
previous = Path(os.environ["RUNNER_TEMP"]) / "previous-site-build"
69+
current = Path("_site")
70+
output = Path(os.environ["RUNNER_TEMP"]) / "purge-urls.txt"
71+
site_url = os.environ["SITE_URL"].rstrip("/")
72+
73+
def digest(path):
74+
h = hashlib.sha256()
75+
with path.open("rb") as file:
76+
for chunk in iter(lambda: file.read(1024 * 1024), b""):
77+
h.update(chunk)
78+
return h.hexdigest()
79+
80+
def files(root):
81+
return {
82+
path.relative_to(root)
83+
for path in root.rglob("*")
84+
if path.is_file()
85+
}
86+
87+
def url_for(path):
88+
parent = path.parent.as_posix()
89+
if path.name == "index.html" and parent == ".":
90+
return "/"
91+
92+
if path.name == "index.html":
93+
return f"/{parent}/"
94+
95+
return f"/{path.as_posix()}"
96+
97+
changed = []
98+
for path in sorted(files(previous) | files(current)):
99+
before = previous / path
100+
after = current / path
101+
if not before.exists() or not after.exists() or digest(before) != digest(after):
102+
changed.append(f"{site_url}{url_for(path)}")
103+
104+
output.write_text("\n".join(changed) + ("\n" if changed else ""))
105+
106+
with Path(os.environ["GITHUB_OUTPUT"]).open("a") as github_output:
107+
github_output.write(f"count={len(changed)}\n")
108+
109+
print(f"Found {len(changed)} changed route(s).")
110+
for url in changed:
111+
print(url)
112+
PY
30113
- uses: actions/configure-pages@v5
31114
- uses: actions/upload-pages-artifact@v3
32115
with:
33116
path: _site/
34117
- id: deployment
35118
uses: actions/deploy-pages@v4
36119
- name: Purge Cloudflare cache
37-
if: steps.deployment.outcome == 'success'
120+
if: steps.deployment.outcome == 'success' && steps.changed-routes.outputs.count != '0'
121+
env:
122+
CLOUDFLARE_ZONE_ID: ${{ secrets.CLOUDFLARE_ZONE_ID }}
123+
CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }}
38124
run: |
39-
curl -s -X POST "https://api.cloudflare.com/client/v4/zones/${{ secrets.CLOUDFLARE_ZONE_ID }}/purge_cache" \
40-
-H "Authorization: Bearer ${{ secrets.CLOUDFLARE_API_TOKEN }}" \
41-
-H "Content-Type: application/json" \
42-
--data '{"purge_everything":true}'
125+
set -euo pipefail
126+
127+
python3 <<'PY'
128+
import json
129+
import os
130+
import subprocess
131+
from pathlib import Path
132+
133+
urls = [
134+
line.strip()
135+
for line in (Path(os.environ["RUNNER_TEMP"]) / "purge-urls.txt").read_text().splitlines()
136+
if line.strip()
137+
]
138+
139+
endpoint = f"https://api.cloudflare.com/client/v4/zones/{os.environ['CLOUDFLARE_ZONE_ID']}/purge_cache"
140+
headers = [
141+
"-H", f"Authorization: Bearer {os.environ['CLOUDFLARE_API_TOKEN']}",
142+
"-H", "Content-Type: application/json",
143+
]
144+
145+
for start in range(0, len(urls), 30):
146+
payload = json.dumps({"files": urls[start:start + 30]})
147+
subprocess.run(
148+
["curl", "-sS", "-X", "POST", endpoint, *headers, "--data", payload],
149+
check=True,
150+
)
151+
PY

0 commit comments

Comments
 (0)