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-7nm8r9 | 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 | ||
| 8 | import assert from "node:assert/strict"; | |
| 9 | import { readdirSync, readFileSync } from "node:fs"; | |
| 10 | import { registerHooks } from "node:module"; | |
| 11 | import { DatabaseSync } from "node:sqlite"; | |
| 12 | import { beforeEach, test } from "node:test"; | |
| 13 | ||
| 14 | registerHooks({ | |
| 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 | }); | |
| 24 | const { default: worker } = await import("./index.ts"); | |
| 25 | ||
| 26 | function 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 | ||
| 59 | const REPO = "rep_web"; | |
| 60 | const PROJECT = "prj_web"; | |
| 61 | const SHA = "4b8d0f2a6c1e3579bd02468ace13579bdf02468a"; | |
| 62 | ||
| 63 | function 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 | ||
| 140 | let w: ReturnType<typeof world>; | |
| 141 | const realFetch = globalThis.fetch; | |
| 142 | ||
| 143 | beforeEach(() => { | |
| 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 | ||
| 159 | process.on("exit", () => { | |
| 160 | globalThis.fetch = realFetch; | |
| 161 | }); | |
| 162 | ||
| 163 | const 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 | ||
| 172 | async 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 | ||
| 181 | const open = (body: Record<string, unknown> = {}) => | |
| 182 | post("/rpc/start_from_workflow", { actor: jobActor(), repo: { namespace: "acme", name: "web" }, ...body }); | |
| 183 | ||
| 184 | const rows = (sql: string, ...params: unknown[]) => | |
| 185 | (w.state.sqlite.prepare(sql).all(...(params as never[])) as any[]).map((row) => ({ ...row })); | |
| 186 | ||
| 187 | test("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 | ||
| 210 | test("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 | ||
| 230 | test("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 | ||
| 259 | test("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 | ||
| 269 | test("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 | ||
| 281 | test("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 | }); | |
| 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. | 307 | |
| 308 | test("a job that ends without finishing its build fails the build at once, and leaves a finished one be", async () => { | |
| 309 | const build = (await open()).value; | |
| 310 | const ended = await post("/rpc/workflow_job_ended", { jobId: "job_1", conclusion: "cancelled" }); | |
| 311 | assert.deepEqual(ended, { ok: true, value: 1 }); | |
| 312 | assert.deepEqual(rows("SELECT id, status, error FROM deployments"), [ | |
| 313 | { id: build.deploy_id, status: "failed", error: "The workflow job was cancelled before the build finished." }, | |
| 314 | ]); | |
| 315 | // Another job's build, and one that is no longer open, are left alone. | |
| 316 | w.state.sqlite.prepare("UPDATE deployments SET job_id = 'job_2', status = 'building', error = NULL").run(); | |
| 317 | assert.deepEqual(await post("/rpc/workflow_job_ended", { jobId: "job_1", conclusion: "success" }), { ok: true, value: 0 }); | |
| 318 | assert.deepEqual(rows("SELECT status FROM deployments"), [{ status: "building" }]); | |
| 319 | w.state.sqlite.prepare("UPDATE deployments SET status = 'ready'").run(); | |
| 320 | assert.deepEqual(await post("/rpc/workflow_job_ended", { jobId: "job_2", conclusion: "success" }), { ok: true, value: 0 }); | |
| 321 | assert.deepEqual(rows("SELECT status FROM deployments"), [{ status: "ready" }]); | |
| 322 | assert.equal((await post("/rpc/workflow_job_ended", {})).error.code, "invalid"); | |
| 323 | }); |
This file's history is long; its oldest lines are credited to the oldest commit read.