-
Notifications
You must be signed in to change notification settings - Fork 177
Expand file tree
/
Copy pathcloud.boot.ts
More file actions
146 lines (135 loc) · 5.33 KB
/
Copy pathcloud.boot.ts
File metadata and controls
146 lines (135 loc) · 5.33 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
// The cloud boot recipe — ONE definition shared by the vitest globalsetup
// (ephemeral) and the dev CLI (persistent): WorkOS + Autumn EMULATORS in this
// process plus the app's own dev stack (PGlite dev-db + vite dev) pointed at
// them. The app runs its REAL auth/billing code — real SDKs, real
// sealed-session crypto, real JWKS — against emulated services.
import { rmSync } from "node:fs";
import { resolve } from "node:path";
import { fileURLToPath } from "node:url";
// Vendored fork import (same pattern as mcporter).
import { createEmulator } from "@executor-js/emulate";
import { bootProcesses, waitForHttp } from "./boot";
import { AUTUMN_PLAN_SEED } from "./autumn-plans";
export const cloudDir = fileURLToPath(new URL("../../apps/cloud/", import.meta.url));
export interface CloudBootOptions {
readonly cloudPort: number;
readonly dbPort: number;
readonly workosPort: number;
readonly autumnPort: number;
readonly workosClientId: string;
readonly cookiePassword: string;
/** The URL the app advertises (VITE_PUBLIC_SITE_URL, MCP resource origin). */
readonly publicUrl: string;
/**
* The WorkOS origin the BROWSER must reach (authorize page redirects).
* Defaults to the emulator's loopback URL; set it when a proxy (e.g.
* `tailscale serve` HTTPS) fronts the emulator — the app's auth cookies
* are Secure, so off-localhost access needs https on both sides.
*/
readonly workosPublicUrl?: string;
/** vite --host. Default 127.0.0.1. */
readonly host?: string;
/** Wipe the dev DB before boot (hermetic). Default true. */
readonly fresh?: boolean;
readonly logFile?: string;
/** Extra env for the app's dev stack (e.g. the suite's OTLP exporter). */
readonly extraEnv?: Record<string, string>;
}
export interface CloudBooted {
readonly teardown: () => Promise<void>;
readonly pids: ReadonlyArray<number>;
readonly workosUrl: string;
readonly autumnUrl: string;
}
export const bootCloud = async (options: CloudBootOptions): Promise<CloudBooted> => {
// Fresh dev DB per boot — the WorkOS emulator mints org ids from a
// per-process counter, so a persisted DB from a previous invocation
// collides with the new boot's ids (identities land in polluted orgs /
// org creation 500s).
const dbPath = resolve(cloudDir, ".e2e-stub-db");
if (options.fresh ?? true) rmSync(dbPath, { recursive: true, force: true });
// MCP access tokens minted by the emulator's OAuth server must carry the
// app's client id as audience (what the resource server verifies).
process.env.EMULATE_WORKOS_AUDIENCE = options.workosClientId;
const workos = await createEmulator({
service: "workos",
port: options.workosPort,
...(options.workosPublicUrl ? { baseUrl: options.workosPublicUrl } : {}),
});
const autumn = await createEmulator({
service: "autumn",
port: options.autumnPort,
// Seed the plan catalog so the billing UI (plans, eligibility, trial
// checkout) has real plans to render. Derived from autumn.config.ts.
seed: { autumn: { plans: AUTUMN_PLAN_SEED } },
});
const workosUrl = options.workosPublicUrl ?? workos.url;
const env = {
// Real client, emulated service. The app derives the browser-facing
// authorize URL from WORKOS_API_URL, so it must be the PUBLIC origin.
WORKOS_API_URL: workosUrl,
AUTUMN_API_URL: autumn.url,
WORKOS_API_KEY: "sk_test_emulate",
WORKOS_CLIENT_ID: options.workosClientId,
WORKOS_COOKIE_PASSWORD: options.cookiePassword,
AUTUMN_SECRET_KEY: "am_test_emulate",
ENCRYPTION_KEY: "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef",
DATABASE_URL: `postgresql://postgres:postgres@127.0.0.1:${options.dbPort}/postgres`,
EXECUTOR_DIRECT_DATABASE_URL: "true",
CLOUDFLARE_INCLUDE_PROCESS_ENV: "true",
VITE_PUBLIC_SITE_URL: options.publicUrl,
// The AuthKit domain (MCP OAuth metadata + JWKS) is the emulator too.
MCP_AUTHKIT_DOMAIN: workosUrl,
MCP_RESOURCE_ORIGIN: options.publicUrl,
ALLOW_LOCAL_NETWORK: "true",
// Throwaway PGlite on its own port + dir so it never fights `bun dev`.
DEV_DB_PORT: String(options.dbPort),
DEV_DB_PATH: dbPath,
// Vite rejects unknown Host headers; allow the public hostname when a
// proxy fronts the app.
__VITE_ADDITIONAL_SERVER_ALLOWED_HOSTS: new URL(options.publicUrl).hostname,
...options.extraEnv,
};
const procs = bootProcesses(
[
{
cmd: "bun",
args: ["run", "scripts/dev-db.ts"],
cwd: cloudDir,
env,
logFile: options.logFile,
},
{
cmd: "bunx",
args: [
"vite",
"dev",
"--port",
String(options.cloudPort),
"--strictPort",
"--host",
options.host ?? "127.0.0.1",
],
cwd: cloudDir,
env,
logFile: options.logFile,
},
],
{ label: "cloud" },
);
const teardown = async () => {
await procs.teardown();
await workos.close();
await autumn.close();
};
try {
const local = `http://127.0.0.1:${options.cloudPort}`;
await waitForHttp(local);
// The API plane is ready when login actually redirects to AuthKit.
await waitForHttp(`${local}/api/auth/login`, { expectRedirect: true });
} catch (error) {
await teardown();
throw error;
}
return { teardown, pids: procs.pids, workosUrl, autumnUrl: autumn.url };
};