g1t/services/pages/src/index.ts

101 lines3,870 bytesCodeBlame

Pick any line to see why it is the way it is: the commit, the pull request and issue it came from, and what the agent was thinking.

Deployments: a preview for every pull request, production on g1t.page1/**
2 * The dispatcher for g1t.page: every app deployed by g1t is served here.
3 *
4 * The hostname's first label is the app's script name in the Workers for
5 * Platforms namespace (`pr-12--web--acme.g1t.page` is pull request 12's
6 * preview of acme/web), so a request needs no lookup: the app is fetched
7 * by name and runs only for as long as it answers. An app no one visits
8 * runs nothing and costs nothing.
9 *
10 * Kept apart from g1t.sh, so apps share no cookies or origin with the site
11 * people sign in to.
12 */
13
14type Env = {
15 APPS: DispatchNamespace;
16};
17
18const DOMAIN = "g1t.page";
19
20function page(status: number, title: string, body: string): Response {
21 const html = `<!doctype html>
22<html lang="en">
23<head>
24<meta charset="utf-8">
25<meta name="viewport" content="width=device-width, initial-scale=1">
26<meta name="robots" content="noindex">
27<title>${title} · g1t.page</title>
28<style>
29 :root { color-scheme: dark; --bg: #121214; --fg: #ececf1; --muted: #9a9aa6; --accent: #b4a7f5; }
30 body { margin: 0; min-height: 100vh; display: grid; place-items: center; background: var(--bg); color: var(--fg);
31 font: 16px/1.6 ui-sans-serif, system-ui, -apple-system, "Segoe UI", sans-serif; padding: 0 16px; }
32 main { max-width: 32rem; }
33 h1 { font-size: 1.5rem; margin: 0 0 .5rem; letter-spacing: -.01em; }
34 p { color: var(--muted); margin: .5rem 0; }
35 a { color: var(--accent); }
36 code { font: .9em ui-monospace, SFMono-Regular, Menlo, monospace; color: var(--fg); }
37</style>
38</head>
39<body><main><h1>${title}</h1>${body}</main></body>
40</html>`;
41 return new Response(html, {
42 status,
43 headers: { "content-type": "text/html; charset=utf-8", "x-robots-tag": "noindex" },
44 });
45}
46
47function escape(text: string): string {
48 return text.replace(/[&<>"']/g, (c) => `&#${c.charCodeAt(0)};`);
49}
50
51export default {
52 async fetch(request: Request, env: Env): Promise<Response> {
53 const host = new URL(request.url).hostname.toLowerCase();
54 if (host === DOMAIN) {
55 return page(
56 200,
57 "g1t.page",
58 `<p>Apps deployed by <a href="https://g1t.sh">g1t</a>: every pull request's preview, and each repository's production.</p>
59 <p><a href="https://docs.g1t.sh/guides/deployments/">How deployments work</a></p>`,
60 );
61 }
62 const label = host.endsWith(`.${DOMAIN}`) ? host.slice(0, -DOMAIN.length - 1) : "";
63 if (!/^[a-z0-9-]{1,63}$/.test(label)) {
64 return page(404, "Nothing here", "<p>This is not the address of an app on g1t.page.</p>");
65 }
66 let app: Fetcher;
67 try {
68 app = env.APPS.get(label);
69 } catch {
70 return missing(label);
71 }
72 let response: Response;
73 try {
74 response = await app.fetch(request);
75 } catch (error) {
76 if (/worker not found|script not found|does not exist/i.test(String(error))) return missing(label);
77 return page(
78 502,
79 "The app failed",
80 `<p>The app at <code>${escape(host)}</code> threw an error before it could answer.</p>`,
81 );
82 }
83 // Previews are for the people reviewing a change, not search engines.
84 if (label.startsWith("pr-")) {
85 response = new Response(response.body, response);
86 response.headers.set("x-robots-tag", "noindex");
87 }
88 return response;
89 },
90} satisfies ExportedHandler<Env>;
91
92function missing(label: string): Response {
93 const preview = label.startsWith("pr-");
94 return page(
95 404,
96 preview ? "This preview is not up" : "Nothing deployed here",
97 preview
98 ? `<p>A preview comes down when its pull request is closed or merged, or after its repository's idle days without a visit. Pushing to the pull request, or redeploying it from the repository's Deployments page, brings it back.</p>`
99 : `<p>No app is deployed at <code>${escape(label)}.${DOMAIN}</code>. Deployments are turned on per repository, from its Deployments page on g1t.</p>`,
100 );
101}