Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 7 additions & 2 deletions cloudflare/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -360,7 +360,11 @@ jobs that are queued, running, failed, or waiting on an agent's questions, a
tool permission, or an account limit.

`dev/github.ts` fakes GitHub for every outbound request, so nothing leaves
localhost and no real credential is used. Notifications are off, since a push
localhost and no real credential is used. It models what publication needs:
commits and trees, fast-forward branch updates on the upstream and its service
fork, and pull request creation, so a runner's job publishes end to end. Like
GitHub, it scopes each installation token to the repositories and permissions
it was minted for. Notifications are off, since a push
would reach a real push service. Sign in with GitHub completes as the
seeded maintainer. From a shell, sign in with a cookie jar:

Expand All @@ -370,7 +374,8 @@ curl -sL -c jar -b jar -H 'Origin: http://localhost:8787' -d provider=github htt

Check phone and desktop widths with the browser's device emulation. The Worker
tests run the same seed (`test/seed.test.ts`), so it follows schema and route
changes, and later end-to-end tests can start from it.
changes, and later end-to-end tests can start from it. The publication tests
(`test/publish.test.ts`) use the same fake with a smaller world of their own.

## Checks

Expand Down
556 changes: 461 additions & 95 deletions cloudflare/dev/github.ts

Large diffs are not rendered by default.

47 changes: 38 additions & 9 deletions cloudflare/dev/seed.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,22 @@
// through the Worker's own routes against the fake GitHub, so they follow the
// current code; only runners and jobs, which need a live runner, are written
// to D1 directly.
import { type Repo, type World, commitOf, databaseId, headOf, webhookSignature } from "./github";
import {
type Comment,
type Pull,
type Repo,
type World,
commitOf,
databaseId,
headOf,
webhookSignature,
} from "./github";

const quest = (title: string, goal: string, required: string[] = []) =>
`# ${title}\n\n## Goal\n\n${goal}\n` + (required.length ? `\n## Required\n\n${required.join("\n")}\n` : "");
/** A discussion of `count` short comments. */
const remarks = (count: number, author: string): Comment[] =>
Array.from({ length: count }, (_, i) => ({ author, body: `Seen it too (${i + 1}).`, at: "2026-10-19T12:00:00Z" }));

/** A fresh world: `seed` adds to it, and the fake GitHub answers from it. */
export function world(): World {
Expand Down Expand Up @@ -68,12 +80,21 @@ export function world(): World {
],
issues: [
{ number: 2, title: "Old crash on startup", author: "reporter", closed: true },
{ number: 3, title: "Crash when the config file is empty", author: "reporter", comments: 2 },
{
number: 3,
title: "Crash when the config file is empty",
author: "reporter",
body: "The app crashes on startup when `config.toml` exists but is empty.",
comments: [
{ author: "maintainer", body: "Reproduced on main.", at: "2026-10-19T12:00:00Z" },
{ author: null, body: "Same with a file holding only whitespace.", at: "2026-10-19T13:00:00Z" },
],
},
{
number: 7,
title: "Settings page loses unsaved changes when the session expires while the browser tab sits in the background on a phone",
author: "phone-user",
comments: 5,
comments: remarks(5, "phone-user"),
},
{ number: 8, title: "Add a dark mode toggle", author: "night-owl" },
],
Expand All @@ -86,7 +107,7 @@ export function world(): World {
checks: "SUCCESS",
review: "APPROVED",
reviews: [{ state: "APPROVED", author: "reviewer" }],
comments: 3,
comments: remarks(3, "reviewer"),
merge: "CLEAN",
},
{
Expand Down Expand Up @@ -148,12 +169,15 @@ export function world(): World {
merge: "CLEAN",
},
],
claims: ["quest/m0/docs"],
// A claim pushed without a pull request.
branches: { "quest/m0/docs": "d".repeat(40) },
// Changed outside its waiting job, which can start over.
fork: { "quest/m1/fonts": "f".repeat(40) },
commits: {},
trees: {},
};
// kix opened this one for a job that then failed before recording it.
rocket.pulls.push({
// kix opened this one from its fork branch for a job that then failed before recording it.
const imported: Pull = {
number: 20,
title: "Import a project from a ZIP archive",
author: "quest-dev[bot]",
Expand All @@ -162,7 +186,9 @@ export function world(): World {
checks: "PENDING",
review: null,
draft: true,
});
};
rocket.pulls.push(imported);
rocket.fork[imported.branch] = headOf(rocket, imported);
const archive: Repo = {
id: 9102,
owner: "example",
Expand All @@ -183,12 +209,15 @@ export function world(): World {
],
issues: [{ number: 1, title: "Archive the old site", author: "reporter" }],
pulls: [],
claims: [],
branches: {},
fork: {},
commits: {},
trees: {},
};
return {
app: { id: 123, slug: "quest-dev" },
installation: 9201,
forkInstallation: 9202,
forks: "quest-dev-forks",
users: [{ id: 9001, login: "dev-maintainer", name: "Dev Maintainer", email: "maintainer@example.test" }],
repos: [rocket, archive],
Expand Down
6 changes: 5 additions & 1 deletion cloudflare/dev/serve.ts
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,11 @@ const mf = new Miniflare(
// Every outbound request goes to the fake; nothing reaches the network.
outboundService: async (request) => {
const body = ["GET", "HEAD"].includes(request.method) ? undefined : await request.text();
const response = await github(request.url, { method: request.method, body });
const response = await github(request.url, {
method: request.method,
headers: [...request.headers],
body,
});
return new MiniflareResponse(await response.arrayBuffer(), {
status: response.status,
headers: [...response.headers],
Expand Down
Loading
Loading