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.

g1t.sh
356 linesCodeBlameRaw

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.

Merge branch 'worktree-agent-a4d35ee70c50683d0' into claude/cool-newton-7nm8r91/**
2 * The whole service, on Node, opening builds for g1t Actions jobs
3 * (`start_from_workflow`): a D1 made of node:sqlite with the service's
4 * migrations, the other services as fakes that answer by method, and
5 * Cloudflare's API as a recorded fetch. See moves.flow.test.ts.
6 */
7
8import assert from "node:assert/strict";
9import { readdirSync, readFileSync } from "node:fs";
10import { registerHooks } from "node:module";
11import { DatabaseSync } from "node:sqlite";
12import { beforeEach, test } from "node:test";
13
14registerHooks({
15 resolve(specifier, context, next) {
16 try {
17 return next(specifier, context);
18 } catch (error) {
19 if (specifier.startsWith(".")) return next(`${specifier}.ts`, context);
20 throw error;
21 }
22 },
23});
24const { default: worker } = await import("./index.ts");
25
26function d1(db: DatabaseSync) {
27 const value = (v: unknown) => (v === undefined ? null : typeof v === "boolean" ? Number(v) : v);
28 const statement = (sql: string, params: unknown[] = []) => ({
29 sql,
30 params,
31 bind: (...args: unknown[]) => statement(sql, args.map(value)),
32 async first<T>() {
33 return (db.prepare(sql).get(...(params as never[])) as T) ?? null;
34 },
35 async all<T>() {
36 return { results: db.prepare(sql).all(...(params as never[])) as T[], success: true };
37 },
38 async run() {
39 const r = db.prepare(sql).run(...(params as never[]));
40 return { success: true, meta: { changes: Number(r.changes) } };
41 },
42 });
43 return {
44 prepare: (sql: string) => statement(sql),
45 async batch(statements: ReturnType<typeof statement>[]) {
46 db.exec("BEGIN");
47 try {
48 const out = statements.map((s) => ({ results: db.prepare(s.sql).all(...(s.params as never[])) }));
49 db.exec("COMMIT");
50 return out;
51 } catch (error) {
52 db.exec("ROLLBACK");
53 throw error;
54 }
55 },
56 };
57}
58
59const REPO = "rep_web";
60const PROJECT = "prj_web";
61const SHA = "4b8d0f2a6c1e3579bd02468ace13579bdf02468a";
62
63function world() {
64 const sqlite = new DatabaseSync(":memory:");
65 const dir = new URL("../migrations/", import.meta.url);
66 for (const file of readdirSync(dir).sort()) sqlite.exec(readFileSync(new URL(file, dir), "utf8"));
67 const state = {
68 sqlite,
69 run: { id: "run_1", event: "push", ref: "refs/heads/main", sha: SHA, pull: null as number | null, status: "in_progress", actor: "ada" },
70 jobStatus: "in_progress",
71 approval: null as null | { state: string },
72 pull: { status: "open", headCommit: SHA, branch: "feature" as string | null, fork: null as null | { namespace: string; name: string } },
73 collaborator: "write",
74 plan: true,
75 limit: "ok",
Billing is ready to take real money, and stays on Stripe's test key until the switch. The g1t plan is $21 a month, all-in, with $10 of usage included: the card processing fee is part of the price, and no payment carries a fee line any more (old fee rows still read as they were). Card, billing address, tax IDs, invoices and cancelling are on Stripe's billing page (Manage billing), whose configuration billing keeps in step; Checkout asks for the billing address and a tax ID; g1t's Billing page keeps usage, the spend limit, budget alerts and prepaying. Members other than owners and billing managers no longer see billing details or invoices, from the page or the API.76 /** Billing does not take charges (it is down, say). */
77 chargeFails: false,
Merge branch 'worktree-agent-a4d35ee70c50683d0' into claude/cool-newton-7nm8r978 /** What other services were asked, as `SERVICE method`. */
79 calls: [] as string[],
80 /** What `resolve_settings` was asked for. */
81 resolved: [] as { environment: string; trusted: boolean }[],
82 };
83 const project = () => ({
84 id: PROJECT,
85 workspace: "acme",
86 slug: "web",
87 name: "web",
88 primary: true,
89 source: { kind: "hosted", repoId: REPO, repo: { namespace: "acme", name: "web" }, defaultBranch: "main", rootDir: "site" },
90 });
91 const ok = (value: unknown) => ({ ok: true, value });
92 const service = (name: string, answer: (method: string, args: any) => unknown) => ({
93 async fetch(input: string | Request, init?: RequestInit) {
94 const url = typeof input === "string" ? input : input.url;
95 const method = new URL(url).pathname.split("/").pop()!;
96 state.calls.push(`${name} ${method}`);
97 const args = init?.body ? JSON.parse(String(init.body)) : {};
98 return Response.json((await answer(method, args)) ?? ok(null));
99 },
100 });
101 const env = {
102 DB: d1(sqlite),
103 CLOUDFLARE_API_TOKEN: "test",
104 CLOUDFLARE_ACCOUNT_ID: "acct",
105 DISPATCH_NAMESPACE: "g1t-deployments",
106 SITE: "https://g1t.sh",
107 REPOS: service("REPOS", (method) => {
108 if (method === "get") return ok({ id: REPO, namespace: "acme", name: "web", isPrivate: true, defaultBranch: "main" });
109 return ok([]);
110 }),
111 PROJECTS: service("PROJECTS", (method) => (method === "by_repo" ? [project()] : ok(project()))),
112 IDENTITY: service("IDENTITY", (method, args) => {
113 if (method === "get_workspace") return { id: `wsp_${args.slug}`, slug: args.slug };
114 if (method === "collaborator_permission") return ok({ role: state.collaborator });
115 return ok(null);
116 }),
117 BILLING: service("BILLING", (method) => {
118 if (method === "has_feature") return state.plan ? ok(true) : { ok: false, error: { code: "payment_required", message: "Deployments need the Team plan." } };
119 if (method === "check_limit") return ok({ state: state.limit, message: state.limit === "stopped" ? "The workspace reached its spend limit." : null });
120 if (method === "prices") return { prices: [] };
Billing is ready to take real money, and stays on Stripe's test key until the switch. The g1t plan is $21 a month, all-in, with $10 of usage included: the card processing fee is part of the price, and no payment carries a fee line any more (old fee rows still read as they were). Card, billing address, tax IDs, invoices and cancelling are on Stripe's billing page (Manage billing), whose configuration billing keeps in step; Checkout asks for the billing address and a tax ID; g1t's Billing page keeps usage, the spend limit, budget alerts and prepaying. Members other than owners and billing managers no longer see billing details or invoices, from the page or the API.121 if (method === "charge_feature" && state.chargeFails) return { ok: false, error: { code: "unavailable", message: "billing is down" } };
Merge branch 'worktree-agent-a4d35ee70c50683d0' into claude/cool-newton-7nm8r9122 return ok(null);
123 }),
124 WORK: service("WORK", (method) => {
125 if (method === "get_pull") return ok({ pull: { ...state.pull, number: 9, author: { username: "eve", kind: "user" } } });
126 return ok(null);
127 }),
128 ACTIONS: service("ACTIONS", (method, args) => {
129 if (method === "run") {
130 return ok({ run: state.run, jobs: [{ id: "job_1", status: state.jobStatus }], notes: [], approval: state.approval });
131 }
132 if (method === "resolve_settings") {
133 state.resolved.push({ environment: args.environment, trusted: args.trusted });
134 return { secrets: { API_KEY: "s3cret-value" }, variables: { PUBLIC_URL: "https://example.com" } };
135 }
136 return ok(null);
137 }),
138 RUNNER: service("RUNNER", () => ok(true)),
139 };
140 return { state, env };
141}
142
143let w: ReturnType<typeof world>;
144const realFetch = globalThis.fetch;
145
146beforeEach(() => {
147 w = world();
148 globalThis.fetch = (async (input: string | Request) => {
149 const url = new URL(typeof input === "string" ? input : input.url);
150 w.state.calls.push(`CLOUDFLARE ${url.pathname}`);
151 if (url.pathname.endsWith("/graphql")) return Response.json({ data: { viewer: { accounts: [{}] } } });
152 return Response.json({ success: true, result: { jwt: "done", buckets: [] }, result_info: { total_pages: 1 } });
153 }) as typeof fetch;
154 w.state.sqlite
155 .prepare(
156 `INSERT INTO settings (project_id, repo_id, workspace, slug, enabled, previews, production, idle_days, updated_at)
157 VALUES (?, ?, 'acme', 'web', 1, 1, 1, 7, '2026-10-05T00:00:00Z')`,
158 )
159 .run(PROJECT, REPO);
160});
161
162process.on("exit", () => {
163 globalThis.fetch = realFetch;
164});
165
166const jobActor = (scopes: string[] = ["contents:read", "deployments:write"]) => ({
167 id: "wsp_acme",
168 username: "acme",
169 kind: "workspace",
170 verified: true,
171 workspaces: [{ slug: "acme", role: "member" }],
172 token: { token_id: "tok_job", repo: "acme/web", job: { run_id: "run_1", job_id: "job_1" }, scopes },
173});
174
175async function post(path: string, body: unknown): Promise<any> {
176 const response = await worker.fetch(
177 new Request(`https://deployments${path}`, { method: "POST", body: JSON.stringify(body) }),
178 w.env as never,
179 {} as never,
180 );
181 return response.json();
182}
183
184const open = (body: Record<string, unknown> = {}) =>
185 post("/rpc/start_from_workflow", { actor: jobActor(), repo: { namespace: "acme", name: "web" }, ...body });
186
187const rows = (sql: string, ...params: unknown[]) =>
188 (w.state.sqlite.prepare(sql).all(...(params as never[])) as any[]).map((row) => ({ ...row }));
189
190test("a push to the production branch opens a production build the job runs, with no sandbox and nothing reserved", async () => {
191 const answer = await open();
192 assert.equal(answer.ok, true, JSON.stringify(answer));
193 const build = answer.value;
194 assert.equal(build.kind, "production");
195 assert.equal(build.project, "web");
196 assert.equal(build.commit, SHA);
197 assert.equal(build.url, "https://web-acme.g1t.page");
198 assert.equal(build.trusted, true);
199 assert.equal(build.root_dir, "site");
200 assert.deepEqual(build.build_secrets, { API_KEY: "s3cret-value" });
201 assert.deepEqual(build.build_env, { PUBLIC_URL: "https://example.com" });
202 assert.match(build.token, /^[0-9a-f]{64}$/);
203 assert.deepEqual(rows("SELECT kind, status, run_id, job_id, created_by, trusted FROM deployments"), [
204 { kind: "production", status: "queued", run_id: "run_1", job_id: "job_1", created_by: "ada", trusted: 1 },
205 ]);
206 // The job runs it: no sandbox, and no compute reserved for it.
207 assert.ok(!w.state.calls.includes("RUNNER start_deploy"));
208 assert.ok(!w.state.calls.some((call) => call === "BILLING reserve" || call === "BILLING entitlements"));
209 // The commit's status says it is building.
210 assert.ok(w.state.calls.includes("WORK set_commit_status"));
211});
212
213test("the job reports with the build's token, and its seconds are not charged again", async () => {
214 const { value: build } = await open();
215 assert.equal((await post(`/jobs/${build.deploy_id}/started`, { token: build.token })).ok, true);
216 const finished = await post(`/jobs/${build.deploy_id}/finish`, {
217 token: build.token,
218 worker: { compatibilityDate: "2026-09-26", vars: {}, notFoundHandling: "single-page-application" },
219 completionJwt: "done",
220 buildSeconds: 42,
221 detected: "static",
222 });
223 assert.equal(finished.ok, true, JSON.stringify(finished));
224 assert.equal(rows("SELECT error FROM deployments")[0].error, null);
225 assert.deepEqual(rows("SELECT status, build_seconds, detected FROM deployments"), [{ status: "ready", build_seconds: 42, detected: "static" }]);
226 assert.deepEqual(rows("SELECT script, kind FROM apps"), [{ script: "web-acme", kind: "production" }]);
227 assert.ok(!w.state.calls.includes("BILLING charge_feature"));
228 assert.deepEqual(rows("SELECT build_seconds FROM meters WHERE build_seconds > 0"), []);
229 // The token was for that build alone, until it finished.
230 assert.equal((await post(`/jobs/${build.deploy_id}/started`, { token: build.token })).ok, false);
231});
232
233test("a sandbox's build is still charged by the second; a workflow job's never is", async () => {
234 // A classic build: what a push opens, built by g1t's sandbox.
235 const { createHash } = await import("node:crypto");
236 const token = "f".repeat(64);
237 w.state.sqlite
238 .prepare(
239 `INSERT INTO deployments (id, project_id, workspace, slug, repo_id, repo, kind, branch, number, commit_sha, script, status,
240 token_hash, trusted, created_by, created_at)
241 VALUES ('dpl_sandbox', ?, 'acme', 'web', ?, 'acme/web', 'preview', 'other', NULL, ?, 'web-git-other-acme', 'building', ?, 1, 'g1t', '2026-10-12T00:00:00Z')`,
242 )
243 .run(PROJECT, REPO, SHA, createHash("sha256").update(token).digest("hex"));
244 const finish = { worker: { compatibilityDate: "2026-09-26", vars: {} }, completionJwt: "done", buildSeconds: 30 };
245 assert.equal((await post("/jobs/dpl_sandbox/finish", { token, ...finish })).ok, true);
246 assert.equal(w.state.calls.filter((call) => call === "BILLING charge_feature").length, 1);
247 assert.deepEqual(rows("SELECT build_seconds FROM meters"), [{ build_seconds: 30 }]);
248 // A workflow job's build of the same project, finished and then failed: not charged.
249 const { value: done } = await open();
250 assert.equal((await post(`/jobs/${done.deploy_id}/finish`, { token: done.token, ...finish })).ok, true);
251 const { value: failed } = await open();
252 await post(`/jobs/${failed.deploy_id}/fail`, { token: failed.token, message: "npm run build failed", buildSeconds: 99 });
253 assert.equal(w.state.calls.filter((call) => call === "BILLING charge_feature").length, 1);
254 assert.deepEqual(rows("SELECT build_seconds FROM meters"), [{ build_seconds: 30 }]);
255 // What runs after the build is the same app as any: in apps, which the sweep meters.
256 assert.deepEqual(rows("SELECT script, kind FROM apps ORDER BY script"), [
257 { script: "web-acme", kind: "production" },
258 { script: "web-git-other-acme", kind: "preview" },
259 ]);
260});
261
Billing is ready to take real money, and stays on Stripe's test key until the switch. The g1t plan is $21 a month, all-in, with $10 of usage included: the card processing fee is part of the price, and no payment carries a fee line any more (old fee rows still read as they were). Card, billing address, tax IDs, invoices and cancelling are on Stripe's billing page (Manage billing), whose configuration billing keeps in step; Checkout asks for the billing address and a tax ID; g1t's Billing page keeps usage, the spend limit, budget alerts and prepaying. Members other than owners and billing managers no longer see billing details or invoices, from the page or the API.262test("a build billing cannot charge still goes live and is announced; the sweep charges it once, later", async () => {
263 const { createHash } = await import("node:crypto");
264 const token = "e".repeat(64);
265 w.state.sqlite
266 .prepare(
267 `INSERT INTO deployments (id, project_id, workspace, slug, repo_id, repo, kind, branch, number, commit_sha, script, status,
268 token_hash, trusted, created_by, created_at)
269 VALUES ('dpl_owed', ?, 'acme', 'web', ?, 'acme/web', 'preview', 'other', NULL, ?, 'web-git-other-acme', 'building', ?, 1, 'g1t', '2026-10-12T00:00:00Z')`,
270 )
271 .run(PROJECT, REPO, SHA, createHash("sha256").update(token).digest("hex"));
272 w.state.chargeFails = true;
273 const finished = await post("/jobs/dpl_owed/finish", { token, worker: { compatibilityDate: "2026-09-26", vars: {} }, completionJwt: "done", buildSeconds: 30 });
274 assert.equal(finished.ok, true, JSON.stringify(finished));
275 // Live and said so, though billing refused the charge.
276 assert.deepEqual(rows("SELECT status, build_seconds, build_charged_at FROM deployments"), [{ status: "ready", build_seconds: 30, build_charged_at: null }]);
277 assert.ok(w.state.calls.includes("WORK set_commit_status"));
278 assert.deepEqual(rows("SELECT build_seconds FROM meters WHERE build_seconds > 0"), []);
279 // Still down: the sweep tries and keeps it.
280 await worker.scheduled({} as never, w.env as never);
281 assert.equal(rows("SELECT build_charged_at FROM deployments")[0].build_charged_at, null);
282 // Back: charged once, counted once, and never again.
283 w.state.chargeFails = false;
284 const before = w.state.calls.filter((call) => call === "BILLING charge_feature").length;
285 await worker.scheduled({} as never, w.env as never);
286 await worker.scheduled({} as never, w.env as never);
287 assert.equal(w.state.calls.filter((call) => call === "BILLING charge_feature").length, before + 1);
288 assert.notEqual(rows("SELECT build_charged_at FROM deployments")[0].build_charged_at, null);
289 assert.deepEqual(rows("SELECT build_seconds FROM meters WHERE build_seconds > 0"), [{ build_seconds: 30 }]);
290});
291
Merge branch 'worktree-agent-a4d35ee70c50683d0' into claude/cool-newton-7nm8r9292test("another branch builds its preview; a pull request's run builds the pull request's", async () => {
293 w.state.run = { ...w.state.run, ref: "refs/heads/feature/x" };
294 const branch = (await open()).value;
295 assert.deepEqual([branch.kind, branch.branch, branch.number], ["preview", "feature/x", null]);
296 w.state.run = { ...w.state.run, event: "pull_request", ref: "refs/pull/9/merge", pull: 9 };
297 const pull = (await open()).value;
298 assert.deepEqual([pull.kind, pull.branch, pull.number, pull.trusted], ["preview", "feature", 9, true]);
299 assert.deepEqual(w.state.resolved.map((r) => r.environment), ["preview", "preview"]);
300});
301
302test("a pull request from a fork, or one that had to be approved, gets no build secrets", async () => {
303 w.state.run = { ...w.state.run, event: "pull_request", ref: "refs/pull/9/merge", pull: 9 };
304 w.state.pull = { ...w.state.pull, branch: null, fork: { namespace: "eve", name: "web" } };
305 const fork = (await open()).value;
306 assert.deepEqual([fork.branch, fork.trusted, fork.build_secrets], ["pr-9", false, {}]);
307 w.state.pull = { ...w.state.pull, branch: "feature", fork: null };
308 w.state.approval = { state: "approved" };
309 const approved = (await open()).value;
310 assert.deepEqual([approved.trusted, approved.build_secrets], [false, {}]);
311 assert.deepEqual(w.state.resolved.map((r) => r.trusted), [false, false]);
312});
313
314test("it is refused without a job's write token, after the job, without the plan, or with previews off", async () => {
315 const refused = async (body: Record<string, unknown>) => (await open(body)).error?.code;
316 assert.equal(await refused({ actor: jobActor(["deployments:read"]) }), "forbidden");
317 assert.equal(await refused({ actor: { ...jobActor(), token: { token_id: "tok_person", scopes: null } } }), "forbidden");
318 assert.equal(await refused({ repo: { namespace: "acme", name: "api" } }), "forbidden");
319 assert.equal(await refused({ project: "docs" }), "not_found");
320 w.state.jobStatus = "completed";
321 assert.equal(await refused({}), "conflict");
322 w.state.jobStatus = "in_progress";
323 w.state.run = { ...w.state.run, ref: "refs/tags/v1.0.0" };
324 assert.equal(await refused({}), "invalid");
325 w.state.run = { ...w.state.run, ref: "refs/heads/main" };
326 w.state.plan = false;
327 const unpaid = await open();
328 assert.deepEqual([unpaid.ok, unpaid.error.code], [false, "payment_required"]);
329 assert.deepEqual(rows("SELECT status FROM deployments"), [{ status: "skipped" }]);
330 w.state.plan = true;
331 w.state.limit = "stopped";
332 const stopped = await open();
333 assert.deepEqual([stopped.ok, stopped.error.code, stopped.error.message], [false, "payment_required", "The workspace reached its spend limit."]);
334 w.state.limit = "ok";
335 w.state.sqlite.prepare("UPDATE settings SET previews = 0").run();
336 w.state.run = { ...w.state.run, ref: "refs/heads/feature" };
337 assert.equal(await refused({}), "conflict");
338 assert.ok(!w.state.calls.includes("RUNNER start_deploy"));
339});
Merge GitHub pull request #8 (claude/cool-newton-7nm8r9): Docker deployments on g1t.page behind a switch that stays off until Cloudflare's container APIs are checked live, workflows a workspace manages in its repositories, tarball and zipball downloads, refs/pull/* kept to g1t alone, framework detection for g1t.page builds, and a workflow's event payload matching GitHub's.340
341test("a job that ends without finishing its build fails the build at once, and leaves a finished one be", async () => {
342 const build = (await open()).value;
343 const ended = await post("/rpc/workflow_job_ended", { jobId: "job_1", conclusion: "cancelled" });
344 assert.deepEqual(ended, { ok: true, value: 1 });
345 assert.deepEqual(rows("SELECT id, status, error FROM deployments"), [
346 { id: build.deploy_id, status: "failed", error: "The workflow job was cancelled before the build finished." },
347 ]);
348 // Another job's build, and one that is no longer open, are left alone.
349 w.state.sqlite.prepare("UPDATE deployments SET job_id = 'job_2', status = 'building', error = NULL").run();
350 assert.deepEqual(await post("/rpc/workflow_job_ended", { jobId: "job_1", conclusion: "success" }), { ok: true, value: 0 });
351 assert.deepEqual(rows("SELECT status FROM deployments"), [{ status: "building" }]);
352 w.state.sqlite.prepare("UPDATE deployments SET status = 'ready'").run();
353 assert.deepEqual(await post("/rpc/workflow_job_ended", { jobId: "job_2", conclusion: "success" }), { ok: true, value: 0 });
354 assert.deepEqual(rows("SELECT status FROM deployments"), [{ status: "ready" }]);
355 assert.equal((await post("/rpc/workflow_job_ended", {})).error.code, "invalid");
356});

This file's history is long; its oldest lines are credited to the oldest commit read.