Skip to content
306 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 'claude/cool-newton-7nm8r9' (#36): real GitHub Actions on g1t, managed deploy workflows, g1t/deploy and the Sysbox runner 0.2.0. The merge keeps both sides' webhook events and API operations, says agent images need tar, guards the job engine's stop to Unix so the runner builds on Windows again, and keeps the moves tests' previews from going idle as the calendar passes their fixture's date.1/**
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",
76 /** What other services were asked, as `SERVICE method`. */
77 calls: [] as string[],
78 /** What `resolve_settings` was asked for. */
79 resolved: [] as { environment: string; trusted: boolean }[],
80 };
81 const project = () => ({
82 id: PROJECT,
83 workspace: "acme",
84 slug: "web",
85 name: "web",
86 primary: true,
87 source: { kind: "hosted", repoId: REPO, repo: { namespace: "acme", name: "web" }, defaultBranch: "main", rootDir: "site" },
88 });
89 const ok = (value: unknown) => ({ ok: true, value });
90 const service = (name: string, answer: (method: string, args: any) => unknown) => ({
91 async fetch(input: string | Request, init?: RequestInit) {
92 const url = typeof input === "string" ? input : input.url;
93 const method = new URL(url).pathname.split("/").pop()!;
94 state.calls.push(`${name} ${method}`);
95 const args = init?.body ? JSON.parse(String(init.body)) : {};
96 return Response.json((await answer(method, args)) ?? ok(null));
97 },
98 });
99 const env = {
100 DB: d1(sqlite),
101 CLOUDFLARE_API_TOKEN: "test",
102 CLOUDFLARE_ACCOUNT_ID: "acct",
103 DISPATCH_NAMESPACE: "g1t-deployments",
104 SITE: "https://g1t.sh",
105 REPOS: service("REPOS", (method) => {
106 if (method === "get") return ok({ id: REPO, namespace: "acme", name: "web", isPrivate: true, defaultBranch: "main" });
107 return ok([]);
108 }),
109 PROJECTS: service("PROJECTS", (method) => (method === "by_repo" ? [project()] : ok(project()))),
110 IDENTITY: service("IDENTITY", (method, args) => {
111 if (method === "get_workspace") return { id: `wsp_${args.slug}`, slug: args.slug };
112 if (method === "collaborator_permission") return ok({ role: state.collaborator });
113 return ok(null);
114 }),
115 BILLING: service("BILLING", (method) => {
116 if (method === "has_feature") return state.plan ? ok(true) : { ok: false, error: { code: "payment_required", message: "Deployments need the Team plan." } };
117 if (method === "check_limit") return ok({ state: state.limit, message: state.limit === "stopped" ? "The workspace reached its spend limit." : null });
118 if (method === "prices") return { prices: [] };
119 return ok(null);
120 }),
121 WORK: service("WORK", (method) => {
122 if (method === "get_pull") return ok({ pull: { ...state.pull, number: 9, author: { username: "eve", kind: "user" } } });
123 return ok(null);
124 }),
125 ACTIONS: service("ACTIONS", (method, args) => {
126 if (method === "run") {
127 return ok({ run: state.run, jobs: [{ id: "job_1", status: state.jobStatus }], notes: [], approval: state.approval });
128 }
129 if (method === "resolve_settings") {
130 state.resolved.push({ environment: args.environment, trusted: args.trusted });
131 return { secrets: { API_KEY: "s3cret-value" }, variables: { PUBLIC_URL: "https://example.com" } };
132 }
133 return ok(null);
134 }),
135 RUNNER: service("RUNNER", () => ok(true)),
136 };
137 return { state, env };
138}
139
140let w: ReturnType<typeof world>;
141const realFetch = globalThis.fetch;
142
143beforeEach(() => {
144 w = world();
145 globalThis.fetch = (async (input: string | Request) => {
146 const url = new URL(typeof input === "string" ? input : input.url);
147 w.state.calls.push(`CLOUDFLARE ${url.pathname}`);
148 if (url.pathname.endsWith("/graphql")) return Response.json({ data: { viewer: { accounts: [{}] } } });
149 return Response.json({ success: true, result: { jwt: "done", buckets: [] }, result_info: { total_pages: 1 } });
150 }) as typeof fetch;
151 w.state.sqlite
152 .prepare(
153 `INSERT INTO settings (project_id, repo_id, workspace, slug, enabled, previews, production, idle_days, updated_at)
154 VALUES (?, ?, 'acme', 'web', 1, 1, 1, 7, '2026-10-05T00:00:00Z')`,
155 )
156 .run(PROJECT, REPO);
157});
158
159process.on("exit", () => {
160 globalThis.fetch = realFetch;
161});
162
163const jobActor = (scopes: string[] = ["contents:read", "deployments:write"]) => ({
164 id: "wsp_acme",
165 username: "acme",
166 kind: "workspace",
167 verified: true,
168 workspaces: [{ slug: "acme", role: "member" }],
169 token: { token_id: "tok_job", repo: "acme/web", job: { run_id: "run_1", job_id: "job_1" }, scopes },
170});
171
172async function post(path: string, body: unknown): Promise<any> {
173 const response = await worker.fetch(
174 new Request(`https://deployments${path}`, { method: "POST", body: JSON.stringify(body) }),
175 w.env as never,
176 {} as never,
177 );
178 return response.json();
179}
180
181const open = (body: Record<string, unknown> = {}) =>
182 post("/rpc/start_from_workflow", { actor: jobActor(), repo: { namespace: "acme", name: "web" }, ...body });
183
184const rows = (sql: string, ...params: unknown[]) =>
185 (w.state.sqlite.prepare(sql).all(...(params as never[])) as any[]).map((row) => ({ ...row }));
186
187test("a push to the production branch opens a production build the job runs, with no sandbox and nothing reserved", async () => {
188 const answer = await open();
189 assert.equal(answer.ok, true, JSON.stringify(answer));
190 const build = answer.value;
191 assert.equal(build.kind, "production");
192 assert.equal(build.project, "web");
193 assert.equal(build.commit, SHA);
194 assert.equal(build.url, "https://web-acme.g1t.page");
195 assert.equal(build.trusted, true);
196 assert.equal(build.root_dir, "site");
197 assert.deepEqual(build.build_secrets, { API_KEY: "s3cret-value" });
198 assert.deepEqual(build.build_env, { PUBLIC_URL: "https://example.com" });
199 assert.match(build.token, /^[0-9a-f]{64}$/);
200 assert.deepEqual(rows("SELECT kind, status, run_id, job_id, created_by, trusted FROM deployments"), [
201 { kind: "production", status: "queued", run_id: "run_1", job_id: "job_1", created_by: "ada", trusted: 1 },
202 ]);
203 // The job runs it: no sandbox, and no compute reserved for it.
204 assert.ok(!w.state.calls.includes("RUNNER start_deploy"));
205 assert.ok(!w.state.calls.some((call) => call === "BILLING reserve" || call === "BILLING entitlements"));
206 // The commit's status says it is building.
207 assert.ok(w.state.calls.includes("WORK set_commit_status"));
208});
209
210test("the job reports with the build's token, and its seconds are not charged again", async () => {
211 const { value: build } = await open();
212 assert.equal((await post(`/jobs/${build.deploy_id}/started`, { token: build.token })).ok, true);
213 const finished = await post(`/jobs/${build.deploy_id}/finish`, {
214 token: build.token,
215 worker: { compatibilityDate: "2026-09-26", vars: {}, notFoundHandling: "single-page-application" },
216 completionJwt: "done",
217 buildSeconds: 42,
218 detected: "static",
219 });
220 assert.equal(finished.ok, true, JSON.stringify(finished));
221 assert.equal(rows("SELECT error FROM deployments")[0].error, null);
222 assert.deepEqual(rows("SELECT status, build_seconds, detected FROM deployments"), [{ status: "ready", build_seconds: 42, detected: "static" }]);
223 assert.deepEqual(rows("SELECT script, kind FROM apps"), [{ script: "web-acme", kind: "production" }]);
224 assert.ok(!w.state.calls.includes("BILLING charge_feature"));
225 assert.deepEqual(rows("SELECT build_seconds FROM meters WHERE build_seconds > 0"), []);
226 // The token was for that build alone, until it finished.
227 assert.equal((await post(`/jobs/${build.deploy_id}/started`, { token: build.token })).ok, false);
228});
229
230test("a sandbox's build is still charged by the second; a workflow job's never is", async () => {
231 // A classic build: what a push opens, built by g1t's sandbox.
232 const { createHash } = await import("node:crypto");
233 const token = "f".repeat(64);
234 w.state.sqlite
235 .prepare(
236 `INSERT INTO deployments (id, project_id, workspace, slug, repo_id, repo, kind, branch, number, commit_sha, script, status,
237 token_hash, trusted, created_by, created_at)
238 VALUES ('dpl_sandbox', ?, 'acme', 'web', ?, 'acme/web', 'preview', 'other', NULL, ?, 'web-git-other-acme', 'building', ?, 1, 'g1t', '2026-10-12T00:00:00Z')`,
239 )
240 .run(PROJECT, REPO, SHA, createHash("sha256").update(token).digest("hex"));
241 const finish = { worker: { compatibilityDate: "2026-09-26", vars: {} }, completionJwt: "done", buildSeconds: 30 };
242 assert.equal((await post("/jobs/dpl_sandbox/finish", { token, ...finish })).ok, true);
243 assert.equal(w.state.calls.filter((call) => call === "BILLING charge_feature").length, 1);
244 assert.deepEqual(rows("SELECT build_seconds FROM meters"), [{ build_seconds: 30 }]);
245 // A workflow job's build of the same project, finished and then failed: not charged.
246 const { value: done } = await open();
247 assert.equal((await post(`/jobs/${done.deploy_id}/finish`, { token: done.token, ...finish })).ok, true);
248 const { value: failed } = await open();
249 await post(`/jobs/${failed.deploy_id}/fail`, { token: failed.token, message: "npm run build failed", buildSeconds: 99 });
250 assert.equal(w.state.calls.filter((call) => call === "BILLING charge_feature").length, 1);
251 assert.deepEqual(rows("SELECT build_seconds FROM meters"), [{ build_seconds: 30 }]);
252 // What runs after the build is the same app as any: in apps, which the sweep meters.
253 assert.deepEqual(rows("SELECT script, kind FROM apps ORDER BY script"), [
254 { script: "web-acme", kind: "production" },
255 { script: "web-git-other-acme", kind: "preview" },
256 ]);
257});
258
259test("another branch builds its preview; a pull request's run builds the pull request's", async () => {
260 w.state.run = { ...w.state.run, ref: "refs/heads/feature/x" };
261 const branch = (await open()).value;
262 assert.deepEqual([branch.kind, branch.branch, branch.number], ["preview", "feature/x", null]);
263 w.state.run = { ...w.state.run, event: "pull_request", ref: "refs/pull/9/merge", pull: 9 };
264 const pull = (await open()).value;
265 assert.deepEqual([pull.kind, pull.branch, pull.number, pull.trusted], ["preview", "feature", 9, true]);
266 assert.deepEqual(w.state.resolved.map((r) => r.environment), ["preview", "preview"]);
267});
268
269test("a pull request from a fork, or one that had to be approved, gets no build secrets", async () => {
270 w.state.run = { ...w.state.run, event: "pull_request", ref: "refs/pull/9/merge", pull: 9 };
271 w.state.pull = { ...w.state.pull, branch: null, fork: { namespace: "eve", name: "web" } };
272 const fork = (await open()).value;
273 assert.deepEqual([fork.branch, fork.trusted, fork.build_secrets], ["pr-9", false, {}]);
274 w.state.pull = { ...w.state.pull, branch: "feature", fork: null };
275 w.state.approval = { state: "approved" };
276 const approved = (await open()).value;
277 assert.deepEqual([approved.trusted, approved.build_secrets], [false, {}]);
278 assert.deepEqual(w.state.resolved.map((r) => r.trusted), [false, false]);
279});
280
281test("it is refused without a job's write token, after the job, without the plan, or with previews off", async () => {
282 const refused = async (body: Record<string, unknown>) => (await open(body)).error?.code;
283 assert.equal(await refused({ actor: jobActor(["deployments:read"]) }), "forbidden");
284 assert.equal(await refused({ actor: { ...jobActor(), token: { token_id: "tok_person", scopes: null } } }), "forbidden");
285 assert.equal(await refused({ repo: { namespace: "acme", name: "api" } }), "forbidden");
286 assert.equal(await refused({ project: "docs" }), "not_found");
287 w.state.jobStatus = "completed";
288 assert.equal(await refused({}), "conflict");
289 w.state.jobStatus = "in_progress";
290 w.state.run = { ...w.state.run, ref: "refs/tags/v1.0.0" };
291 assert.equal(await refused({}), "invalid");
292 w.state.run = { ...w.state.run, ref: "refs/heads/main" };
293 w.state.plan = false;
294 const unpaid = await open();
295 assert.deepEqual([unpaid.ok, unpaid.error.code], [false, "payment_required"]);
296 assert.deepEqual(rows("SELECT status FROM deployments"), [{ status: "skipped" }]);
297 w.state.plan = true;
298 w.state.limit = "stopped";
299 const stopped = await open();
300 assert.deepEqual([stopped.ok, stopped.error.code, stopped.error.message], [false, "payment_required", "The workspace reached its spend limit."]);
301 w.state.limit = "ok";
302 w.state.sqlite.prepare("UPDATE settings SET previews = 0").run();
303 w.state.run = { ...w.state.run, ref: "refs/heads/feature" };
304 assert.equal(await refused({}), "conflict");
305 assert.ok(!w.state.calls.includes("RUNNER start_deploy"));
306});

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