Skip to content

Where people and agents ship software together. The open-source git platform for the whole job: issues, agents, checks and deploys to the edge.

213 linesCodeBlameRaw
1/**
2 * The parts of g1t the page lists, and how each is checked. Every address
3 * comes from the Worker's vars, so an installation of its own names its own
4 * hosts; a part whose address is empty is not part of that installation
5 * and is left off the page.
6 *
7 * Checks go over the public internet, as people reach g1t, so they measure
8 * what people see. Billing has no public address of its own, so it is the
9 * one part checked through a service binding, when there is one.
10 */
11
12/** The Worker's vars that say where things are. */
13export type Targets = {
14 /** The site: `https://g1t.sh`. */
15 SITE_URL: string;
16 /** The API: `https://api.g1t.sh`. */
17 API_URL: string;
18 MCP_URL: string;
19 DOCS_URL: string;
20 /** Where deployed apps live: `https://g1t.page`. */
21 PAGES_URL: string;
22 /** A public repository that always exists, `owner/name`, to fetch refs of. */
23 PROBE_REPO: string;
24 /** The site as browsers reach it, when the checks reach it another way. */
25 PUBLIC_SITE_URL?: string;
26};
27
28/** One request a check makes, and the answer that means the part works. */
29export type Step = {
30 url: string;
31 headers?: Record<string, string>;
32 /** The status that means it works; any 2xx when absent. */
33 expect?: number;
34 /**
35 * A page people open in a browser: asked for with a browser's user agent
36 * (probe.ts `BROWSER_USER_AGENT`), so the site streams it as it does for
37 * people, and the time is to the first byte, not to a crawler's full render.
38 */
39 browser?: boolean;
40};
41
42export type Check =
43 | { kind: "http"; steps: Step[] }
44 /** Billing, through its binding: reading its price book. */
45 | { kind: "billing" }
46 /** Git storage, through the repos service binding: how the store answered lately. */
47 | { kind: "storage" }
48 /** No check yet: the page says so rather than showing green. */
49 | { kind: "none" };
50
51export type ComponentInfo = {
52 key: string;
53 name: string;
54 address: string;
55 checks: string;
56 /** Whether g1t as a whole is down when this is. */
57 core: boolean;
58 check: Check;
59 /** Slower than this, it counts as degraded; `SLOW_MS` when absent. */
60 slowMs?: number;
61};
62
63/**
64 * The page-speed budget the status page holds the site to: the slower of a
65 * public project page and Explore, to the first byte of the answer, from a
66 * Cloudflare data centre. CONTRIBUTING.md, "Speed", has the site's targets.
67 */
68export const SPEED_BUDGET_MS = 800;
69
70/** A token no one holds: the API asks identity about it and refuses it. */
71export const NO_TOKEN = `g1t_${"0".repeat(40)}`;
72
73function host(url: string): string {
74 try {
75 return new URL(url).host;
76 } catch {
77 return url;
78 }
79}
80
81const trim = (url: string | undefined) => (url ?? "").trim().replace(/\/+$/, "");
82
83/** Git storage counts as degraded when its calls took longer than this on average. */
84export const STORAGE_SLOW_MS = 1500;
85
86/** The parts, in the order the page lists them. `storage`: whether the repos service is bound. */
87export function components(vars: Partial<Targets>, billing = true, storage = false): ComponentInfo[] {
88 const site = trim(vars.SITE_URL);
89 const api = trim(vars.API_URL);
90 const mcp = trim(vars.MCP_URL);
91 const docs = trim(vars.DOCS_URL);
92 const pages = trim(vars.PAGES_URL);
93 const repo = (vars.PROBE_REPO ?? "").trim();
94 // What the page shows as the site's address: where people reach it.
95 const shown = host(trim(vars.PUBLIC_SITE_URL) || site);
96 const list: (ComponentInfo | null)[] = [
97 site
98 ? {
99 key: "site",
100 name: "Website and sign-in",
101 address: shown,
102 checks: api ? "The sign-in page loading, and the account service answering an access token through the API." : "The sign-in page loading.",
103 core: true,
104 check: {
105 kind: "http",
106 steps: [
107 { url: `${site}/login`, browser: true },
108 ...(api ? [{ url: `${api}/v1/user`, headers: { authorization: `Bearer ${NO_TOKEN}` }, expect: 401 }] : []),
109 ],
110 },
111 }
112 : null,
113 api
114 ? {
115 key: "api",
116 name: "API",
117 address: host(api),
118 checks: "The API's front page.",
119 core: true,
120 check: { kind: "http", steps: [{ url: `${api}/` }] },
121 }
122 : null,
123 site && repo
124 ? {
125 key: "git",
126 name: "Git and repositories",
127 address: `${shown}/<owner>/<repo>.git`,
128 checks: "Listing the branches of a public repository over HTTPS, the first step of every clone.",
129 core: true,
130 check: { kind: "http", steps: [{ url: `${site}/${repo}.git/info/refs?service=git-upload-pack` }] },
131 }
132 : null,
133 storage && site
134 ? {
135 key: "storage",
136 name: "Git storage",
137 address: "Where every repository is kept",
138 checks: `How g1t's git storage answered over the last five minutes, as g1t saw it: failures, rate limits, and how long calls took. Rate limited, or slower than ${STORAGE_SLOW_MS} ms on average, counts as degraded.`,
139 core: false,
140 check: { kind: "storage" },
141 slowMs: STORAGE_SLOW_MS,
142 }
143 : null,
144 site && repo
145 ? {
146 key: "speed",
147 name: "Page speed",
148 address: `${shown}/${repo}`,
149 checks: `A public project page and Explore each answering within ${SPEED_BUDGET_MS} ms. Slower counts as degraded.`,
150 core: false,
151 check: {
152 kind: "http",
153 steps: [
154 { url: `${site}/${repo}`, browser: true },
155 { url: `${site}/explore`, browser: true },
156 ],
157 },
158 slowMs: SPEED_BUDGET_MS,
159 }
160 : null,
161 mcp
162 ? {
163 key: "mcp",
164 name: "MCP server",
165 address: host(mcp),
166 checks: "The MCP server's description.",
167 core: false,
168 check: { kind: "http", steps: [{ url: `${mcp}/`, headers: { accept: "application/json" } }] },
169 }
170 : null,
171 docs
172 ? {
173 key: "docs",
174 name: "Documentation",
175 address: host(docs),
176 checks: "The documentation's front page.",
177 core: false,
178 check: { kind: "http", steps: [{ url: `${docs}/` }] },
179 }
180 : null,
181 pages
182 ? {
183 key: "deployments",
184 name: "Deployments",
185 address: `*.${host(pages)}`,
186 checks: `${host(pages)} answering. Each deployed app is not checked one by one.`,
187 core: false,
188 check: { kind: "http", steps: [{ url: `${pages}/` }] },
189 }
190 : null,
191 pages
192 ? {
193 key: "sandboxes",
194 name: "Sandboxes",
195 address: "Checks, workflows and builds",
196 checks: "Starting a sandbox costs money and takes seconds, so it is not checked from here yet.",
197 core: false,
198 check: { kind: "none" },
199 }
200 : null,
201 billing && site
202 ? {
203 key: "billing",
204 name: "Billing",
205 address: `${shown}/<workspace>/-/billing`,
206 checks: "The billing service reading its price book. Stripe itself is not checked.",
207 core: false,
208 check: { kind: "billing" },
209 }
210 : null,
211 ];
212 return list.filter((c): c is ComponentInfo => c != null);
213}