Skip to content

Commit fca0fb0

Browse files
authored
Merge pull request #260 from eSolia/feat/nightly-rebuild-cron
feat: add Worker cron trigger for nightly rebuild
2 parents 35252fc + 6ce1c3d commit fca0fb0

2 files changed

Lines changed: 124 additions & 0 deletions

File tree

worker/index.ts

Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,119 @@
1+
// Cloudflare Worker entry point.
2+
//
3+
// Two responsibilities:
4+
// 1. fetch() — delegate every request to the Static Assets binding so
5+
// the site is served from _site/ exactly as before.
6+
// 2. scheduled() — once per day, push an empty commit to main via the
7+
// GitHub API. CF Workers Builds (already connected to
8+
// eSolia/blog.esolia.pro) sees the push and rebuilds.
9+
// This refreshes time-dependent build artefacts such as
10+
// `elapseddays` per post (computed in _config.ts).
11+
//
12+
// Manual one-time setup outside this file:
13+
// - Create a fine-grained GitHub PAT scoped to eSolia/blog.esolia.pro only,
14+
// permission: Contents = read & write.
15+
// - Bind it to the Worker:
16+
// deno run -A npm:wrangler@latest secret put GITHUB_TOKEN
17+
//
18+
// Minimal Cloudflare Workers types declared inline to avoid pulling in
19+
// @cloudflare/workers-types and dueling tsconfigs with the Lume Deno setup.
20+
21+
interface Fetcher {
22+
fetch(request: Request): Promise<Response>;
23+
}
24+
25+
interface ExecutionContext {
26+
waitUntil(promise: Promise<unknown>): void;
27+
passThroughOnException(): void;
28+
}
29+
30+
interface ScheduledEvent {
31+
cron: string;
32+
scheduledTime: number;
33+
type: "scheduled";
34+
}
35+
36+
interface Env {
37+
ASSETS: Fetcher;
38+
GITHUB_TOKEN: string;
39+
}
40+
41+
const REPO_OWNER = "eSolia";
42+
const REPO_NAME = "blog.esolia.pro";
43+
const REPO_BRANCH = "main";
44+
const USER_AGENT = "blog-esolia-pro-rebuild-cron";
45+
46+
export default {
47+
fetch(request: Request, env: Env): Promise<Response> {
48+
return env.ASSETS.fetch(request);
49+
},
50+
51+
scheduled(_event: ScheduledEvent, env: Env, ctx: ExecutionContext): void {
52+
ctx.waitUntil(triggerRebuild(env));
53+
},
54+
};
55+
56+
async function triggerRebuild(env: Env): Promise<void> {
57+
const ghHeaders = {
58+
"Authorization": `Bearer ${env.GITHUB_TOKEN}`,
59+
"User-Agent": USER_AGENT,
60+
"Accept": "application/vnd.github+json",
61+
"X-GitHub-Api-Version": "2022-11-28",
62+
};
63+
const apiBase = `https://api.github.com/repos/${REPO_OWNER}/${REPO_NAME}`;
64+
65+
const refResp = await fetch(`${apiBase}/git/ref/heads/${REPO_BRANCH}`, {
66+
headers: ghHeaders,
67+
});
68+
if (!refResp.ok) {
69+
throw new Error(
70+
`get ref failed: ${refResp.status} ${await refResp.text()}`,
71+
);
72+
}
73+
const ref = (await refResp.json()) as { object: { sha: string } };
74+
const parentSha = ref.object.sha;
75+
76+
const commitResp = await fetch(`${apiBase}/git/commits/${parentSha}`, {
77+
headers: ghHeaders,
78+
});
79+
if (!commitResp.ok) {
80+
throw new Error(
81+
`get commit failed: ${commitResp.status} ${await commitResp.text()}`,
82+
);
83+
}
84+
const parent = (await commitResp.json()) as { tree: { sha: string } };
85+
86+
const createResp = await fetch(`${apiBase}/git/commits`, {
87+
method: "POST",
88+
headers: { ...ghHeaders, "Content-Type": "application/json" },
89+
body: JSON.stringify({
90+
message:
91+
"chore: nightly rebuild trigger\n\nInfoSec: no security impact — empty commit fired by Worker cron to refresh time-dependent build artefacts.",
92+
tree: parent.tree.sha,
93+
parents: [parentSha],
94+
author: {
95+
name: "blog-esolia-pro-cron",
96+
email: "noreply@esolia.pro",
97+
date: new Date().toISOString(),
98+
},
99+
}),
100+
});
101+
if (!createResp.ok) {
102+
throw new Error(
103+
`create commit failed: ${createResp.status} ${await createResp.text()}`,
104+
);
105+
}
106+
const newCommit = (await createResp.json()) as { sha: string };
107+
108+
const updateResp = await fetch(`${apiBase}/git/refs/heads/${REPO_BRANCH}`, {
109+
method: "PATCH",
110+
headers: { ...ghHeaders, "Content-Type": "application/json" },
111+
body: JSON.stringify({ sha: newCommit.sha, force: false }),
112+
});
113+
if (!updateResp.ok) {
114+
throw new Error(
115+
`update ref failed: ${updateResp.status} ${await updateResp.text()}`,
116+
);
117+
}
118+
console.log(`Triggered rebuild via empty commit ${newCommit.sha}`);
119+
}

wrangler.jsonc

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,16 @@
22
"$schema": "node_modules/wrangler/config-schema.json",
33
"account_id": "ab2ac8ca4000c79ae4a66377276585be",
44
"name": "blog-esolia-pro",
5+
"main": "./worker/index.ts",
56
"compatibility_date": "2026-05-01",
67
"assets": {
78
"directory": "./_site",
9+
"binding": "ASSETS",
810
"not_found_handling": "404-page"
911
},
12+
"triggers": {
13+
"crons": ["0 17 * * *"]
14+
},
1015
"observability": {
1116
"enabled": true,
1217
"logs": {

0 commit comments

Comments
 (0)