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
7 changes: 7 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -22,9 +22,12 @@ env:
EXECUTOR_DISABLE_ANALYTICS: "1"
EXECUTOR_DISABLE_INTEGRATIONS_FETCH: "1"

# Outside contributions are ported into the private repo, which runs the
# checks there, so fork PRs skip CI here. Jobs that need `changes` skip with it.
jobs:
changes:
name: Changed paths
if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository
runs-on: blacksmith-4vcpu-ubuntu-2404
timeout-minutes: 10
outputs:
Expand Down Expand Up @@ -78,6 +81,7 @@ jobs:

format:
name: Format
if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository
runs-on: blacksmith-4vcpu-ubuntu-2404
timeout-minutes: 10
steps:
Expand All @@ -101,6 +105,7 @@ jobs:

lint:
name: Lint
if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository
runs-on: blacksmith-4vcpu-ubuntu-2404
timeout-minutes: 10
steps:
Expand All @@ -124,6 +129,7 @@ jobs:

typecheck:
name: Typecheck
if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository
runs-on: blacksmith-4vcpu-ubuntu-2404
timeout-minutes: 15
env:
Expand Down Expand Up @@ -165,6 +171,7 @@ jobs:

test:
name: Test
if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository
runs-on: blacksmith-16vcpu-ubuntu-2404
timeout-minutes: 15
# Run eight independent packages at once and cap each Vitest process at two
Expand Down
10 changes: 10 additions & 0 deletions .github/workflows/deploy.yml
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,16 @@ jobs:
CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }}
CLOUDFLARE_ACCOUNT_ID: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}

# The unplaced front Worker on the `executor.sh/*` route (src/edge/front.ts).
# After executor-cloud, so each deploy changes the edge's settings in
# executor-cloud before the Worker in front of it.
- name: Deploy executor.sh front Worker
run: bun run wrangler deploy -c wrangler.edge.jsonc
working-directory: apps/cloud
env:
CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }}
CLOUDFLARE_ACCOUNT_ID: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}

# Deploy marker: one event per deploy into the same Axiom dataset the
# worker traces land in, so a latency step-change lines up with its
# deploy in one query. Skipped (not failed) when the secret is absent,
Expand Down
3 changes: 2 additions & 1 deletion .oxfmtrc.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
"executor-*.tgz",
"**/routeTree.gen.ts",
"**/smoke-harness-bundle.gen.ts",
"apps/cloud/src/services/executor-schema.ts"
"apps/cloud/src/services/executor-schema.ts",
"apps/cloud/src/edge/v2-edge-contract.json"
]
}
112 changes: 112 additions & 0 deletions apps/cloud/src/edge/front.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
import { describe, expect, it } from "@effect/vitest";

import { frontResponse, type FrontEnv } from "./front";

/** A Worker stand-in that records what it receives and answers with `respond`. */
const recordingWorker = (respond: () => Response) => {
const received: Request[] = [];
return {
received,
worker: {
fetch: (request: Request) => {
received.push(request);
return Promise.resolve(respond());
},
},
};
};

const settings = (v2: FrontEnv["V2"], marketing?: FrontEnv["MARKETING"]): FrontEnv => ({
V2: v2,
MARKETING: marketing,
V2_SIGN_UP_URL: "https://app.executor.sh/login?mode=signup",
V2_OAUTH_STATE_PREFIX: "x2.",
V2_ANALYTICS_PROXY_PATH: "/api/0123456789abcdef",
V2_ERROR_TUNNEL_PATH: "/api/fedcba9876543210/submit",
});

const IMMUTABLE = "public, max-age=31536000, immutable";

describe("frontResponse", () => {
it("returns v2's asset with its caching, status and body unchanged, adding only the referrer policy", async () => {
const v2 = recordingWorker(
() =>
new Response("console.log(1)", {
status: 200,
headers: {
"cache-control": IMMUTABLE,
"content-type": "text/javascript",
etag: '"abc"',
},
}),
);

const response = await frontResponse(
new Request("https://executor.sh/_astro/page.abc123.js"),
settings(v2.worker),
);

expect(v2.received.map((request) => request.url)).toEqual([
"https://executor.sh/_astro/page.abc123.js",
]);
expect(response?.status).toBe(200);
expect(response?.headers.get("cache-control")).toBe(IMMUTABLE);
expect(response?.headers.get("content-type")).toBe("text/javascript");
expect(response?.headers.get("etag")).toBe('"abc"');
expect(response?.headers.get("referrer-policy")).toBe("no-referrer");
expect(await response?.text()).toBe("console.log(1)");
});

it("answers v2's redirects itself, as executor-cloud does", async () => {
const v2 = recordingWorker(() => new Response("v2"));

const response = await frontResponse(
new Request("https://executor.sh/sign-up"),
settings(v2.worker),
);

expect(v2.received).toHaveLength(0);
expect(response?.status).toBe(302);
expect(response?.headers.get("location")).toBe("https://app.executor.sh/login?mode=signup");
expect(response?.headers.get("referrer-policy")).toBe("no-referrer");
});

it("sends v1's legal pages to v1's marketing worker", async () => {
const v2 = recordingWorker(() => new Response("v2"));
const marketing = recordingWorker(() => new Response("terms"));

const response = await frontResponse(
new Request("https://executor.sh/terms"),
settings(v2.worker, marketing.worker),
);

expect(v2.received).toHaveLength(0);
expect(marketing.received.map((request) => request.url)).toEqual(["https://executor.sh/terms"]);
expect(await response?.text()).toBe("terms");
expect(response?.headers.get("referrer-policy")).toBe("no-referrer");
});

it("passes on every request executor-cloud serves itself", () => {
const v2 = recordingWorker(() => new Response("v2"));
const marketing = recordingWorker(() => new Response("terms"));
const env = settings(v2.worker, marketing.worker);

for (const request of [
new Request("https://executor.sh/", { headers: { cookie: "wos-session=sealed" } }),
new Request("https://executor.sh/login"),
new Request("https://executor.sh/mcp", { method: "POST" }),
new Request("https://executor.sh/api/auth/callback?code=c&state=s"),
new Request("https://executor.sh/api/oauth/callback?code=c&state=abc"),
new Request("https://executor.sh/acme"),
new Request("https://executor.sh/favicon.ico"),
]) {
expect(frontResponse(request, env), request.url).toBeNull();
}
expect(v2.received).toHaveLength(0);
expect(marketing.received).toHaveLength(0);
});

it("passes everything on when v2's settings are absent", () => {
expect(frontResponse(new Request("https://executor.sh/_astro/page.abc123.js"), {})).toBeNull();
});
});
47 changes: 47 additions & 0 deletions apps/cloud/src/edge/front.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
// ---------------------------------------------------------------------------
// The `executor.sh` front Worker (`wrangler.edge.jsonc`, Worker
// `executor-cloud-edge`).
//
// `executor-cloud` is placed near v1's database (`placement.region` in
// wrangler.jsonc), so every request it runs crosses to Virginia first. The
// requests its edge hands to v2 or to v1's marketing worker never use that
// database, yet each paid the trip: a 2 KB `/_astro/*` file took up to 2.6 s.
// This Worker has no placement and runs the same edge decision
// (./marketing.ts) where the request lands, on a zone route
// `executor.sh/*`. A zone route runs before the Worker on the hostname's
// Custom Domain, which Cloudflare treats as the origin, so a request v1 owns
// goes on with `fetch(request)` to `executor-cloud` unchanged: same URL,
// method, headers, cookies, body stream and redirect handling as before.
//
// `executor-cloud` keeps its own copy of the edge, which answers nothing this
// Worker passes on. Deleting this Worker (or its route) returns `executor.sh`
// to exactly that, which is the rollback.
// ---------------------------------------------------------------------------

import { marketingProxyRequest, v2EdgeResponse, type V2EdgeEnv } from "./marketing";
import { withPrivateReferrerPolicy } from "./referrer-policy";

/** The front Worker's bindings and settings (`wrangler.edge.jsonc`). */
export interface FrontEnv extends V2EdgeEnv {
/** v1's marketing worker, which serves v1's legal pages. */
readonly MARKETING?: { readonly fetch: (request: Request) => Promise<Response> };
}

/**
* Answer an `executor.sh` request that v2 or v1's marketing worker owns,
* exactly as `executor-cloud`'s entry does, including its `no-referrer`
* policy. Returns `null` for every request `executor-cloud` serves itself.
*/
export const frontResponse = (request: Request, env: FrontEnv): Promise<Response> | null => {
const v2 = v2EdgeResponse(request, env);
if (v2) return v2.then(withPrivateReferrerPolicy);
const marketingRequest = marketingProxyRequest(request);
if (marketingRequest && env.MARKETING) {
return env.MARKETING.fetch(marketingRequest).then(withPrivateReferrerPolicy);
}
return null;
};

export default {
fetch: (request, env) => frontResponse(request, env) ?? fetch(request),
} satisfies ExportedHandler<FrontEnv>;
Loading
Loading