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.
| Cleanup: clippy is quiet outside billing, the README says g1t, and http-cache-semantics is 4.3.0 | 1 | /** |
| 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 (`web-git-fix-login-acme.g1t.page` is the fix-login | |
| 6 | * branch's preview of acme's web project), so the app is fetched by name | |
| 7 | * and runs only for as long as it answers. An app no one visits runs | |
| Merge branch 'worktree-agent-ab9543c492a7ed481' into spend-guardrails | 8 | * nothing and costs nothing. Each request it serves is held to |
| 9 | * `APP_LIMITS`, so one app cannot spend without bound. The one lookup is for an address an app | |
| Cleanup: clippy is quiet outside billing, the README says g1t, and http-cache-semantics is 4.3.0 | 10 | * had before its project moved: `DOMAINS` holds a redirect under the old |
| 11 | * hostname for as long as the old name is held, and it is followed before | |
| 12 | * anything the old script would answer (such as a paused notice). | |
| 13 | * | |
| 14 | * Any other hostname is a project's custom domain, reaching here through | |
| 15 | * Cloudflare for SaaS: the `DOMAINS` KV namespace, written by the | |
| 16 | * deployments service, names its app, or the hostname it redirects to. | |
| 17 | * | |
| 18 | * Kept apart from g1t.sh, so apps share no cookies or origin with the site | |
| 19 | * people sign in to. | |
| 20 | */ | |
| 21 | ||
| Projects have Observability: their requests, logs, traces, metrics and errors, from apps on g1t.page with nothing to set up, and from software anywhere else over OpenTelemetry. | 22 | import { |
| 23 | APP_LIMITS, | |
| 24 | DOMAIN, | |
| 25 | FALLBACK, | |
| 26 | REQUEST_ID_HEADER, | |
| 27 | REQUEST_MARK, | |
| 28 | overLimits, | |
| 29 | parseEntry, | |
| 30 | redirectTo, | |
| 31 | route, | |
| 32 | traceFor, | |
| 33 | type AppTelemetry, | |
| 34 | type DomainEntry, | |
| 35 | } from "./route.ts"; | |
| Cleanup: clippy is quiet outside billing, the README says g1t, and http-cache-semantics is 4.3.0 | 36 | |
| 37 | type Env = { | |
| 38 | APPS: DispatchNamespace; | |
| 39 | /** Custom hostname, or an app's old g1t.page hostname, to `{ script, redirect }`. */ | |
| 40 | DOMAINS?: KVNamespace; | |
| 41 | }; | |
| 42 | ||
| 43 | /** How long a custom hostname's entry is kept in this isolate and at the edge. */ | |
| 44 | const ENTRY_TTL_MS = 60 * 1000; | |
| 45 | const entries = new Map<string, { at: number; entry: DomainEntry | null }>(); | |
| 46 | ||
| 47 | function page(status: number, title: string, body: string, site = DOMAIN): Response { | |
| 48 | const html = `<!doctype html> | |
| 49 | <html lang="en"> | |
| 50 | <head> | |
| 51 | <meta charset="utf-8"> | |
| 52 | <meta name="viewport" content="width=device-width, initial-scale=1"> | |
| 53 | <meta name="robots" content="noindex"> | |
| 54 | <title>${escape(title)} · ${escape(site)}</title> | |
| 55 | <style> | |
| 56 | :root { color-scheme: dark; --bg: #121214; --fg: #ececf1; --muted: #9a9aa6; --accent: #b4a7f5; } | |
| 57 | body { margin: 0; min-height: 100vh; display: grid; place-items: center; background: var(--bg); color: var(--fg); | |
| 58 | font: 16px/1.6 ui-sans-serif, system-ui, -apple-system, "Segoe UI", sans-serif; padding: 0 16px; } | |
| 59 | main { max-width: 32rem; } | |
| 60 | h1 { font-size: 1.5rem; margin: 0 0 .5rem; letter-spacing: -.01em; } | |
| 61 | p { color: var(--muted); margin: .5rem 0; } | |
| 62 | a { color: var(--accent); } | |
| 63 | code { font: .9em ui-monospace, SFMono-Regular, Menlo, monospace; color: var(--fg); } | |
| 64 | </style> | |
| 65 | </head> | |
| 66 | <body><main><h1>${escape(title)}</h1>${body}</main></body> | |
| 67 | </html>`; | |
| 68 | return new Response(html, { | |
| 69 | status, | |
| 70 | headers: { "content-type": "text/html; charset=utf-8", "x-robots-tag": "noindex", "cache-control": "no-store" }, | |
| 71 | }); | |
| 72 | } | |
| 73 | ||
| 74 | function escape(text: string): string { | |
| 75 | return text.replace(/[&<>"']/g, (c) => `&#${c.charCodeAt(0)};`); | |
| 76 | } | |
| 77 | ||
| 78 | /** A custom hostname's entry: from this isolate, else KV (cached at the edge too). */ | |
| 79 | async function lookup(env: Env, hostname: string): Promise<DomainEntry | null> { | |
| 80 | const cached = entries.get(hostname); | |
| 81 | if (cached && Date.now() - cached.at < ENTRY_TTL_MS) return cached.entry; | |
| 82 | const value = env.DOMAINS ? await env.DOMAINS.get(hostname, { type: "json", cacheTtl: ENTRY_TTL_MS / 1000 }) : null; | |
| 83 | const entry = parseEntry(value); | |
| 84 | entries.set(hostname, { at: Date.now(), entry }); | |
| 85 | if (entries.size > 5000) entries.delete(entries.keys().next().value!); | |
| 86 | return entry; | |
| 87 | } | |
| 88 | ||
| Projects have Observability: their requests, logs, traces, metrics and errors, from apps on g1t.page with nothing to set up, and from software anywhere else over OpenTelemetry. | 89 | /** |
| 90 | * Runs the app `script` for the request; `shown` is the address named in | |
| 91 | * its error pages. Each request gets an id and a trace, handed to the app | |
| 92 | * (`x-g1t-request-id`, `traceparent`), and one line naming them, the app | |
| 93 | * and its project (`telemetry`), which observability reads from this | |
| 94 | * Worker's tail events and files with what the app's own tell it. | |
| 95 | */ | |
| 96 | async function dispatch( | |
| 97 | env: Env, | |
| 98 | request: Request, | |
| 99 | script: string, | |
| 100 | shown: string, | |
| 101 | missing: () => Response, | |
| 102 | telemetry: AppTelemetry | null, | |
| 103 | ): Promise<Response> { | |
| 104 | const { id, trace, traceparent } = traceFor(request.headers.get("traceparent")); | |
| 105 | console.log(REQUEST_MARK, { id, script, trace_id: trace, app: telemetry }); | |
| 106 | const headers = new Headers(request.headers); | |
| 107 | headers.set(REQUEST_ID_HEADER, id); | |
| 108 | headers.set("traceparent", traceparent); | |
| 109 | request = new Request(request, { headers }); | |
| Cleanup: clippy is quiet outside billing, the README says g1t, and http-cache-semantics is 4.3.0 | 110 | let app: Fetcher; |
| 111 | try { | |
| Merge branch 'worktree-agent-ab9543c492a7ed481' into spend-guardrails | 112 | app = env.APPS.get(script, {}, { limits: APP_LIMITS }); |
| Cleanup: clippy is quiet outside billing, the README says g1t, and http-cache-semantics is 4.3.0 | 113 | } catch { |
| 114 | return missing(); | |
| 115 | } | |
| 116 | try { | |
| 117 | return await app.fetch(request); | |
| 118 | } catch (error) { | |
| 119 | if (/worker not found|script not found|does not exist/i.test(String(error))) return missing(); | |
| Merge branch 'worktree-agent-ab9543c492a7ed481' into spend-guardrails | 120 | if (overLimits(error)) { |
| 121 | return page( | |
| 122 | 503, | |
| 123 | "The app went over its limits", | |
| 124 | `<p>The app at <code>${escape(shown)}</code> used more than ${APP_LIMITS.cpuMs} ms of CPU time, or made more than ${APP_LIMITS.subRequests} requests of its own, answering this request.</p>`, | |
| 125 | shown, | |
| 126 | ); | |
| 127 | } | |
| Cleanup: clippy is quiet outside billing, the README says g1t, and http-cache-semantics is 4.3.0 | 128 | return page( |
| 129 | 502, | |
| 130 | "The app failed", | |
| 131 | `<p>The app at <code>${escape(shown)}</code> threw an error before it could answer.</p>`, | |
| 132 | shown, | |
| 133 | ); | |
| 134 | } | |
| 135 | } | |
| 136 | ||
| 137 | export default { | |
| 138 | async fetch(request: Request, env: Env): Promise<Response> { | |
| 139 | const host = new URL(request.url).hostname; | |
| 140 | const where = route(host); | |
| 141 | switch (where.kind) { | |
| 142 | case "home": | |
| 143 | return page( | |
| 144 | 200, | |
| 145 | "g1t.page", | |
| 146 | `<p>Apps deployed by <a href="https://g1t.sh">g1t</a>: every pull request's preview, and each repository's production.</p> | |
| 147 | <p><a href="https://docs.g1t.sh/guides/deployments/">How deployments work</a></p>`, | |
| 148 | ); | |
| 149 | case "fallback": | |
| 150 | return page( | |
| 151 | 200, | |
| 152 | "Custom domains on g1t", | |
| 153 | `<p>Point a custom domain here, with a CNAME to <code>${FALLBACK}</code>, to serve a project's production on it. Add the domain first, under the project's Settings, Deployments, on g1t.</p> | |
| 154 | <p><a href="https://docs.g1t.sh/guides/deployments/#custom-domains">How custom domains work</a></p>`, | |
| 155 | ); | |
| 156 | case "invalid": | |
| 157 | return page(404, "Nothing here", "<p>This is not the address of an app on g1t.page.</p>"); | |
| 158 | case "custom": | |
| 159 | return custom(request, env, where.hostname); | |
| 160 | case "app": { | |
| 161 | const label = where.label; | |
| 162 | // An address the app had before its project moved (its workspace or | |
| 163 | // repository was renamed or transferred) redirects to where it is | |
| 164 | // now, whatever the app it named answers, paused or not. | |
| Projects have Observability: their requests, logs, traces, metrics and errors, from apps on g1t.page with nothing to set up, and from software anywhere else over OpenTelemetry. | 165 | const { moved, telemetry } = await appRedirect(request, env, label); |
| Cleanup: clippy is quiet outside billing, the README says g1t, and http-cache-semantics is 4.3.0 | 166 | if (moved) return moved; |
| Projects have Observability: their requests, logs, traces, metrics and errors, from apps on g1t.page with nothing to set up, and from software anywhere else over OpenTelemetry. | 167 | let response = await dispatch(env, request, label, `${label}.${DOMAIN}`, () => missing(label), telemetry); |
| Cleanup: clippy is quiet outside billing, the README says g1t, and http-cache-semantics is 4.3.0 | 168 | // Previews are for the people reviewing a change, not search engines. |
| 169 | if (isPreview(label)) { | |
| 170 | response = new Response(response.body, response); | |
| 171 | response.headers.set("x-robots-tag", "noindex"); | |
| 172 | } | |
| 173 | return response; | |
| 174 | } | |
| 175 | } | |
| 176 | }, | |
| 177 | } satisfies ExportedHandler<Env>; | |
| 178 | ||
| 179 | /** | |
| 180 | * The redirect for an app's old address, if the deployments service left | |
| 181 | * one (in `DOMAINS`, under the old hostname, as `{ script, redirect }`), | |
| 182 | * else null. A lookup that fails serves the app as it is rather than an | |
| 183 | * error. | |
| 184 | */ | |
| Projects have Observability: their requests, logs, traces, metrics and errors, from apps on g1t.page with nothing to set up, and from software anywhere else over OpenTelemetry. | 185 | async function appRedirect(request: Request, env: Env, label: string): Promise<{ moved: Response | null; telemetry: AppTelemetry | null }> { |
| Cleanup: clippy is quiet outside billing, the README says g1t, and http-cache-semantics is 4.3.0 | 186 | const hostname = `${label}.${DOMAIN}`; |
| 187 | let entry: DomainEntry | null; | |
| 188 | try { | |
| 189 | entry = await lookup(env, hostname); | |
| 190 | } catch (error) { | |
| 191 | console.error("could not read redirect", label, error); | |
| Projects have Observability: their requests, logs, traces, metrics and errors, from apps on g1t.page with nothing to set up, and from software anywhere else over OpenTelemetry. | 192 | return { moved: null, telemetry: null }; |
| Cleanup: clippy is quiet outside billing, the README says g1t, and http-cache-semantics is 4.3.0 | 193 | } |
| Projects have Observability: their requests, logs, traces, metrics and errors, from apps on g1t.page with nothing to set up, and from software anywhere else over OpenTelemetry. | 194 | // Otherwise the entry is the app's own: what its requests are filed under. |
| 195 | if (!entry?.redirect || entry.redirect === hostname) return { moved: null, telemetry: entry?.telemetry ?? null }; | |
| 196 | const moved = new Response(null, { | |
| Cleanup: clippy is quiet outside billing, the README says g1t, and http-cache-semantics is 4.3.0 | 197 | status: 301, |
| 198 | headers: { | |
| 199 | location: redirectTo(request.url, entry.redirect), | |
| 200 | "x-robots-tag": "noindex", | |
| 201 | "cache-control": "public, max-age=3600", | |
| 202 | }, | |
| 203 | }); | |
| Projects have Observability: their requests, logs, traces, metrics and errors, from apps on g1t.page with nothing to set up, and from software anywhere else over OpenTelemetry. | 204 | return { moved, telemetry: null }; |
| Cleanup: clippy is quiet outside billing, the README says g1t, and http-cache-semantics is 4.3.0 | 205 | } |
| 206 | ||
| 207 | /** A custom domain: its app, or a 308 to the hostname it is paired with. */ | |
| 208 | async function custom(request: Request, env: Env, hostname: string): Promise<Response> { | |
| 209 | let entry: DomainEntry | null; | |
| 210 | try { | |
| 211 | entry = await lookup(env, hostname); | |
| 212 | } catch (error) { | |
| 213 | console.error("could not read domain", hostname, error); | |
| 214 | return page(503, "Try again in a moment", `<p><code>${escape(hostname)}</code> could not be looked up just now.</p>`, hostname); | |
| 215 | } | |
| 216 | if (!entry) { | |
| 217 | return page( | |
| 218 | 404, | |
| 219 | "This domain is not set up", | |
| 220 | `<p><code>${escape(hostname)}</code> points at g1t, but no project serves it. Its owner adds it under the project's Settings, Deployments, on <a href="https://g1t.sh">g1t</a>.</p>`, | |
| 221 | hostname, | |
| 222 | ); | |
| 223 | } | |
| 224 | if (entry.redirect && entry.redirect !== hostname) { | |
| 225 | return new Response(null, { | |
| 226 | status: 308, | |
| 227 | headers: { location: redirectTo(request.url, entry.redirect), "cache-control": "public, max-age=300" }, | |
| 228 | }); | |
| 229 | } | |
| Projects have Observability: their requests, logs, traces, metrics and errors, from apps on g1t.page with nothing to set up, and from software anywhere else over OpenTelemetry. | 230 | // The app's own entry names its project, for observability. |
| 231 | const own = await lookup(env, `${entry.script}.${DOMAIN}`).catch(() => null); | |
| 232 | return dispatch( | |
| 233 | env, | |
| 234 | request, | |
| 235 | entry.script, | |
| 236 | hostname, | |
| 237 | () => | |
| 238 | page( | |
| 239 | 404, | |
| 240 | "Nothing deployed here yet", | |
| 241 | `<p><code>${escape(hostname)}</code> serves a project's production, and it is not up. Deploying the project brings it here.</p>`, | |
| 242 | hostname, | |
| 243 | ), | |
| 244 | own?.redirect ? null : (own?.telemetry ?? null), | |
| Cleanup: clippy is quiet outside billing, the README says g1t, and http-cache-semantics is 4.3.0 | 245 | ); |
| 246 | } | |
| 247 | ||
| 248 | /** A branch's preview: `<project>-git-<branch>-<workspace>`. */ | |
| 249 | function isPreview(label: string): boolean { | |
| 250 | return label.includes("-git-"); | |
| 251 | } | |
| 252 | ||
| 253 | function missing(label: string): Response { | |
| 254 | const preview = isPreview(label); | |
| 255 | return page( | |
| 256 | 404, | |
| 257 | preview ? "This preview is not up" : "Nothing deployed here", | |
| 258 | preview | |
| 259 | ? `<p>A preview comes down when its pull request is closed or merged, or after its project's idle days without a visit. Pushing to the branch, or redeploying it from the project's Deployments page, brings it back.</p>` | |
| 260 | : `<p>No app is deployed at <code>${escape(label)}.${DOMAIN}</code>. Deployments are turned on per project, from its Settings on g1t.</p>`, | |
| 261 | ); | |
| 262 | } |
This file's history is long; its oldest lines are credited to the oldest commit read.