aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJoe Mou <dev@mou.fo>2026-09-03 15:07:25 -0400
committerJoe Mou <dev@mou.fo>2026-09-03 23:05:33 -0400
commitb9fb8851bb869a27048e02a51b28ef3aaffdd471 (patch)
treef5a7fe469f83dc0bb0a745e6e3db14b12918d2f4
parenta52481a3884447f94aad2d97e0407112c818a32d (diff)
Clean up renderer, redirects, and build commit
-rw-r--r--.gitignore2
-rw-r--r--CLAUDE.md2
-rw-r--r--scripts/build.ts12
-rw-r--r--src/app.test.ts8
-rw-r--r--src/app.ts63
-rw-r--r--views/layout.eta2
6 files changed, 54 insertions, 35 deletions
diff --git a/.gitignore b/.gitignore
index b8fc600..55b5412 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,4 +1,4 @@
/node_modules/
/.wrangler/
-/src/commit.generated.ts
+/src/build.generated.ts
/src/views.generated.ts
diff --git a/CLAUDE.md b/CLAUDE.md
index 3f0cd61..edb33f5 100644
--- a/CLAUDE.md
+++ b/CLAUDE.md
@@ -24,7 +24,7 @@ pnpm test
- `src/worker.ts` - Cloudflare Workers entry (config in `wrangler.jsonc`)
- `src/scraper.ts` - GitHub HTML fetching and JSON extraction logic
-**Templates:** `views/*.eta` - Eta templates with layout inheritance (`layout.eta` as base). On Workers, runtime code generation is disallowed, so `scripts/build.ts` precompiles templates to `src/views.generated.ts`. The same script records the build's `git describe` output in `src/commit.generated.ts` for the page footer. Both are gitignored and regenerated by the `dev`/`deploy` scripts.
+**Templates:** `views/*.eta` - Eta templates with layout inheritance (`layout.eta` as base). On Workers, runtime code generation is disallowed, so `scripts/build.ts` precompiles templates to `src/views.generated.ts`. The same script records the build's `git describe` output in `src/build.generated.ts` for the page footer. Both are gitignored and regenerated by the `dev`/`deploy` scripts.
**Static files:** `public/static/*`, served by Workers Static Assets.
diff --git a/scripts/build.ts b/scripts/build.ts
index 766e5e8..76d88a0 100644
--- a/scripts/build.ts
+++ b/scripts/build.ts
@@ -3,7 +3,7 @@
// src/views.generated.ts - views/*.eta precompiled into plain functions.
// Workers disallow runtime code generation (`new Function`), so Eta cannot
// compile templates there at request time.
-// src/commit.generated.ts - the commit this build was made from, for the
+// src/build.generated.ts - the commit this build was made from, for the
// page footer. Workers can't shell out to git at request time either.
import { execFileSync } from "node:child_process";
import { Eta } from "eta";
@@ -35,9 +35,9 @@ console.log(`Compiled ${names.length} templates to src/views.generated.ts`);
// Untagged repositories describe as a bare short hash; --dirty marks a build
// made from an unclean working copy.
-let commit = "unknown";
+let buildCommit = "unknown";
try {
- commit = execFileSync("git", ["describe", "--tags", "--always", "--dirty"], {
+ buildCommit = execFileSync("git", ["describe", "--tags", "--always", "--dirty"], {
cwd: root,
encoding: "utf8",
}).trim();
@@ -46,7 +46,7 @@ try {
}
fs.writeFileSync(
- path.join(root, "src", "commit.generated.ts"),
- `// Generated by scripts/build.ts -- do not edit.\nexport const commit = ${JSON.stringify(commit)};\n`,
+ path.join(root, "src", "build.generated.ts"),
+ `// Generated by scripts/build.ts -- do not edit.\nexport const buildCommit = ${JSON.stringify(buildCommit)};\n`,
);
-console.log(`Wrote commit ${commit} to src/commit.generated.ts`);
+console.log(`Wrote commit ${buildCommit} to src/build.generated.ts`);
diff --git a/src/app.test.ts b/src/app.test.ts
index 8519d91..274b2d5 100644
--- a/src/app.test.ts
+++ b/src/app.test.ts
@@ -195,7 +195,10 @@ describe("commit history", () => {
assert.match(html, /href="\/actions\/deploy-pages\/commits\/main">\(top\)</);
assert.match(html, /href="\/actions\/deploy-pages\/commits\/main\/src">src</);
- assert.match(html, /href="\/actions\/deploy-pages\/commits\/main\/src\/lib">lib<\/a>\/index\.js/);
+ assert.match(
+ html,
+ /href="\/actions\/deploy-pages\/commits\/main\/src\/lib">lib<\/a>\/index\.js/,
+ );
});
it("should say so when a path has no history", () => {
@@ -363,9 +366,9 @@ describe("redirects to GitHub", () => {
assert.strictEqual(res.status, 200);
assert.strictEqual(res.headers.get("location"), null);
- assert.strictEqual(res.headers.get("Referrer-Policy"), "no-referrer");
const body = await res.text();
+ assert.match(body, /<meta name="referrer" content="no-referrer">/);
assert.match(
body,
/<meta http-equiv="refresh" content="1; url=https:\/\/github.com\/a\/b\/c\?x=1">/,
@@ -382,6 +385,7 @@ describe("redirects to GitHub", () => {
assert.strictEqual(res.status, 302);
assert.strictEqual(res.headers.get("location"), "https://github.com/a/b/c?x=1");
+ assert.strictEqual(res.headers.get("Referrer-Policy"), "no-referrer");
});
it("should use a meta refresh when the referer is another site", async () => {
diff --git a/src/app.ts b/src/app.ts
index 956a8cb..bbe07fc 100644
--- a/src/app.ts
+++ b/src/app.ts
@@ -1,7 +1,7 @@
import type { Eta } from "eta";
import { type Context, Hono } from "hono";
import type { StatusCode } from "hono/utils/http-status";
-import { commit } from "./commit.generated.ts";
+import { buildCommit } from "./build.generated.ts";
import {
getGitHubBlob,
getGitHubCommits,
@@ -22,7 +22,12 @@ import {
RedirectError,
} from "./scraper.ts";
-// Our URLs mirror GitHub's, so the same path there is the page we scraped.
+declare module "hono" {
+ interface ContextRenderer {
+ (template: string, data?: object): Response;
+ }
+}
+
function githubUrlFor(c: Context) {
const { pathname, search } = new URL(c.req.url);
return `https://github.com${pathname}${search}`;
@@ -31,23 +36,36 @@ function githubUrlFor(c: Context) {
export function createApp(eta: Eta) {
const app = new Hono();
- // Use a meta refresh to avoid redirect loops in certain situations; we become
- // the initiator origin even if we are the target of a redirection. Requests
- // coming from one of our own pages are already past that hazard, and are
- // often subresources (an <img> in a rendered README) that can't do anything
- // with an HTML page, so those get a real HTTP redirect.
- function redirectToGitHub(c: Context, location: string) {
+ app.use(async (c, next) => {
+ // Include render data for layout.eta.
+ c.setRenderer((template, data) =>
+ c.html(eta.render(template, { ...data, githubUrl: githubUrlFor(c), buildCommit })),
+ );
+ await next();
+ });
+
+ // Redirect off-domain.
+ function redirectTo(c: Context, location: string) {
const referer = c.req.header("Referer");
+ // If we are the referer, redirect directly so we can hotlink resources
+ // (e.g., <img> in README).
if (referer && URL.parse(referer)?.origin === new URL(c.req.url).origin) {
+ c.header("Referrer-Policy", "no-referrer");
return c.redirect(location);
}
- c.header("Referrer-Policy", "no-referrer");
- return c.html(eta.render("redirect.eta", { location }));
+ // Otherwise use a meta refresh to make ourself the initiator. This will
+ // avoid redirect loops if we are part of excludedInitiatorDomains in a
+ // declarativeNetRequest.
+ return c.render("redirect.eta", { location });
+ }
+
+ function redirectToGitHub(c: Context) {
+ return redirectTo(c, githubUrlFor(c));
}
app.get("/", async (c) => {
- return c.html(eta.render("home.eta", {}));
+ return c.render("home.eta");
});
async function tryRender<T extends object>(c: Context, template: string, promise: Promise<T>) {
@@ -57,19 +75,19 @@ export function createApp(eta: Eta) {
} catch (e) {
if (e instanceof RedirectError) {
if (e.location.startsWith("https://")) {
- return redirectToGitHub(c, e.location);
+ return redirectTo(c, e.location);
}
return c.redirect(e.location);
} else if (e instanceof GitHubHTTPError) {
c.status(e.status as StatusCode);
const message = `GitHub responded with HTTP ${e.status} ${e.message}`;
- return c.html(eta.render("error.eta", { title: e.message, message }));
+ return c.render("error.eta", { title: e.message, message });
} else {
c.status(500);
- return c.html(eta.render("error.eta", { message: "" + e }));
+ return c.render("error.eta", { message: "" + e });
}
}
- return c.html(eta.render(template, { ...data, githubUrl: githubUrlFor(c), commit }));
+ return c.render(template, data);
}
// For fragments fetched asynchronously by client-side JS (see public/static/refs.js):
@@ -92,7 +110,7 @@ export function createApp(eta: Eta) {
c.status(status);
return c.body(null);
}
- return c.html(eta.render(template, data));
+ return c.render(template, data);
}
app.get("/:owner", async (c) => {
@@ -117,10 +135,7 @@ export function createApp(eta: Eta) {
app.get("/:owner/:repo/raw/:branch/:path{.*}", async (c) => {
const { owner, repo, branch, path } = c.req.param();
- return redirectToGitHub(
- c,
- `https://raw.githubusercontent.com/${owner}/${repo}/${branch}/${path}`,
- );
+ return redirectTo(c, `https://raw.githubusercontent.com/${owner}/${repo}/${branch}/${path}`);
});
// This is not a native GitHub route, but instead it lets us render blob
@@ -165,7 +180,7 @@ export function createApp(eta: Eta) {
app.get("/:owner/:repo/wiki/:page/:oid", async (c) => {
const { owner, repo, page, oid } = c.req.param();
if (page.startsWith("_") || !/^[0-9a-f]{40}$/.test(oid)) {
- return redirectToGitHub(c, githubUrlFor(c));
+ return redirectToGitHub(c);
}
return tryRender(c, "wiki.eta", getGitHubWiki(owner, repo, page, oid));
});
@@ -173,7 +188,7 @@ export function createApp(eta: Eta) {
app.get("/:owner/:repo/wiki/:page", async (c) => {
const { owner, repo, page } = c.req.param();
if (page.startsWith("_")) {
- return redirectToGitHub(c, githubUrlFor(c));
+ return redirectToGitHub(c);
}
return tryRender(c, "wiki.eta", getGitHubWiki(owner, repo, page));
});
@@ -209,7 +224,7 @@ export function createApp(eta: Eta) {
}
// Redirect unhandled search types (like code, which requires sign in anyway).
- return redirectToGitHub(c, githubUrlFor(c));
+ return redirectToGitHub(c);
});
app.get("/:owner/:repo/commits/:branch/:path{.*}?", async (c) => {
@@ -260,7 +275,7 @@ export function createApp(eta: Eta) {
});
app.all("*", async (c) => {
- return redirectToGitHub(c, githubUrlFor(c));
+ return redirectToGitHub(c);
});
return app;
diff --git a/views/layout.eta b/views/layout.eta
index 1ac82cc..37289dd 100644
--- a/views/layout.eta
+++ b/views/layout.eta
@@ -35,7 +35,7 @@ git clone git@github.com:<%= it.repo.owner %>/<%= it.repo.name %>.git</code></pr
<%~ it.body %>
- <footer>Generated by cgithub commit <code><%= it.commit %></code> · <a href="<%= it.githubUrl %>" rel="noreferrer">View original</a></footer>
+ <footer>Generated by cgithub commit <code><%= it.buildCommit %></code> · <a href="<%= it.githubUrl %>" rel="noreferrer">View original</a></footer>
<script>
document.addEventListener("keydown", function(e) {