History

Test against real WorkOS/Autumn SDKs via wire-level emulators (#931)

* Run cloud's real auth/billing against WorkOS + Autumn emulators; delete all hand-rolled test layers - WORKOS_API_URL / AUTUMN_API_URL env seams point the REAL SDKs (incl. the vault plugin via credentials.apiUrl) at the vendored emulators. Production composition is unconditional again — the EXECUTOR_E2E_STUB layer swap and every *.test-layer.ts are deleted. - e2e cloud target: identities minted through the REAL login flow (login → emulator-hosted AuthKit, headless via login_hint → callback → genuine sealed-session cookie), orgs through the real create-organization route. - MCP_AUTHKIT_DOMAIN points at the emulator's OAuth authorization server — cloud MCP OAuth e2e now RUNS (was capability-skipped): 19/19 scenarios, zero skips across both targets. - Readiness probes wait for /api/auth/login to actually 302 (vite serves the SPA for /api/* during cold compile); submenu interactions wait for the loaded sub-content before clicking. * Type the optional apiUrl on cloud's workosCredentials dep * Pin emulate fork: OAuth refresh grant + hosted WorkOS/Autumn on the worker * e2e: consume published @executor-js/emulate and @executor-js/mcporter Import the WorkOS/Autumn emulators and the mcporter MCP client from their published npm packages instead of the vendored submodule dist. The submodules remain for fork development, but nothing imports from vendor/ at runtime, so a fresh clone (and CI) resolves these via the registry — which lets e2e typecheck in CI without building the submodules.

Rhys Sullivan committed 5 months ago

Commit 098bfcb1 parent 17174c427 files · +283 −620Browse files

Unified · Split

27 files
  1. apps/cloud/executor.config.ts +2 −0
  2. apps/cloud/src/app.ts +4 −7
  3. apps/cloud/src/auth/api-keys.test-layer.ts +0 −11
  4. apps/cloud/src/auth/cloud-auth-api.test-context.ts +0 −90
  5. apps/cloud/src/auth/context.test-layer.ts +0 −41
  6. apps/cloud/src/auth/create-organization.e2e.node.test.ts +0 −103
  7. apps/cloud/src/auth/middleware.test-layer.ts +0 −29
  8. apps/cloud/src/auth/workos.test-layer.ts +0 −206
  9. apps/cloud/src/auth/workos.ts +20 −1
  10. apps/cloud/src/engine/execution-stack.ts +1 −0
  11. apps/cloud/src/env-augment.d.ts +5 −0
  12. apps/cloud/src/extensions/billing/route.ts +1 −0
  13. apps/cloud/src/extensions/billing/service.test-layer.ts +0 −36
  14. apps/cloud/src/extensions/billing/service.ts +5 −1
  15. apps/cloud/src/extensions/routes.ts +1 −5
  16. apps/cloud/src/testing/e2e-stub.ts +0 −21
  17. bun.lock +91 −13
  18. e2e/cloud/org-limit.test.ts +7 −3
  19. e2e/cloud/org-switcher.test.ts +3 −1
  20. e2e/package.json +2 −0
  21. e2e/setup/boot.ts +8 −3
  22. e2e/setup/cloud.globalsetup.ts +45 −21
  23. e2e/src/surfaces/mcp.ts +1 −1
  24. e2e/targets/cloud.ts +70 −25
  25. e2e/targets/selfhost.ts +1 −1
  26. packages/plugins/workos-vault/src/sdk/client.ts +15 −0
  27. vendor/emulate +1 −1
apps/cloud/executor.config.ts+2−0
@@ -31,6 +31,8 @@ interface CloudPluginDeps { · 30 unmodified lines
31 readonly workosCredentials?: {31 readonly workosCredentials?: {
32 readonly apiKey: string;32 readonly apiKey: string;
33 readonly clientId: string;33 readonly clientId: string;
34 /** Optional WorkOS API base-URL override (WorkOS emulator in tests/dev). */
35 readonly apiUrl?: string;
34 };36 };
35 /** Pluggable WorkOS Vault HTTP client — set by the test harness to37 /** Pluggable WorkOS Vault HTTP client — set by the test harness to
36 * bypass the real WorkOS API. Production leaves this undefined and38 * bypass the real WorkOS API. Production leaves this undefined and
apps/cloud/src/app.ts+4−7
@@ -5,7 +5,6 @@ import { DbProvider, ExecutorApp } from "@executor-js/api/server"; · 4 unmodified lines
5 5
6import { cloudPlugins } from "./plugins";6import { cloudPlugins } from "./plugins";
7import { CoreSharedServices } from "./auth/workos";7import { CoreSharedServices } from "./auth/workos";
8import { E2E_STUB, E2EStubWorkOSLayer } from "./testing/e2e-stub";
9import { makeCloudExtensionRoutes } from "./extensions/routes";8import { makeCloudExtensionRoutes } from "./extensions/routes";
10import { RequestScopedServicesLive } from "./api/layers";9import { RequestScopedServicesLive } from "./api/layers";
11import { CloudMeteringEngineDecorator } from "./engine/execution-stack-metered";10import { CloudMeteringEngineDecorator } from "./engine/execution-stack-metered";
@@ -56,12 +55,10 @@ import { WorkerTelemetryLive } from "./observability/telemetry"; · 44 unmodified lines
56// the account provider + MCP seam, AND by the per-request identity layer below).55// the account provider + MCP seam, AND by the per-request identity layer below).
57// Lives in `boot`, so `workosIdentityLayer`'s residual `WorkOSClient |56// Lives in `boot`, so `workosIdentityLayer`'s residual `WorkOSClient |
58// ApiKeyService` (the long-lived control plane it reads) resolves from there.57// ApiKeyService` (the long-lived control plane it reads) resolves from there.
59// EXECUTOR_E2E_STUB swaps the WorkOS control plane for an in-memory stub whose58// Tests point the REAL client at a WorkOS emulator via WORKOS_API_URL — no
60// `authenticateSealedSession` resolves to user_1 — so the whole app (session +59// layer substitution.
61// account + SSR) is logged in with no real WorkOS. Off in production.60const apiKeyService = ApiKeyService.WorkOS.pipe(Layer.provide(CoreSharedServices));
62const workOSBase = E2E_STUB ? E2EStubWorkOSLayer : CoreSharedServices;61const controlPlane = Layer.mergeAll(CoreSharedServices, apiKeyService);
63const apiKeyService = ApiKeyService.WorkOS.pipe(Layer.provide(workOSBase));
64const controlPlane = Layer.mergeAll(workOSBase, apiKeyService);
65 62
66// `CloudDbProvider` only reads the per-request `DbService` at runtime; we widen63// `CloudDbProvider` only reads the per-request `DbService` at runtime; we widen
67// its residual type to also carry the boot `AutumnService` the metering64// its residual type to also carry the boot `AutumnService` the metering
apps/cloud/src/auth/api-keys.test-layer.ts+0−11
@@ -1,11 +0,0 @@
1import { Effect, Layer } from "effect";
2
3import { ApiKeyService } from "./api-keys";
4import { ApiKeyManagementError } from "./errors";
5
6export const ApiKeyServiceTestLayer = Layer.succeed(ApiKeyService)({
7 validate: () => Effect.succeed(null),
8 listUserKeys: () => Effect.succeed([]),
9 createUserKey: () => Effect.fail(new ApiKeyManagementError({ cause: "unstubbed" })),
10 revokeUserKey: () => Effect.void,
11});
apps/cloud/src/auth/cloud-auth-api.test-context.ts+0−90
@@ -1,90 +0,0 @@
1import { requestHandler } from "@tanstack/react-start/server";
2import { Context, Effect, Layer } from "effect";
3import { FetchHttpClient, HttpRouter, HttpServer } from "effect/unstable/http";
4import { HttpApiBuilder, HttpApiClient } from "effect/unstable/httpapi";
5
6import {
7 AutumnTestLayer,
8 makeAutumnTestState,
9 type AutumnTestState,
10} from "../extensions/billing/service.test-layer";
11import { ApiKeyServiceTestLayer } from "./api-keys.test-layer";
12import {
13 makeUserStoreTestState,
14 UserStoreTestLayer,
15 type UserStoreTestState,
16} from "./context.test-layer";
17import { CloudAuthPublicHandlers, CloudSessionAuthHandlers, NonProtectedApi } from "./handlers";
18import {
19 makeSessionTestContext,
20 SessionAuthTestLayer,
21 type SessionTestContext,
22} from "./middleware.test-layer";
23import { makeWorkOSTestState, WorkOSTestLayer, type WorkOSTestState } from "./workos.test-layer";
24
25const TEST_BASE_URL = "http://test.local";
26const SESSION_COOKIE = "wos-session=test_session";
27
28export type CloudAuthApiTestState = {
29 readonly workos: WorkOSTestState;
30 readonly autumn: AutumnTestState;
31 readonly userStore: UserStoreTestState;
32 readonly session: SessionTestContext;
33};
34
35export const makeCloudAuthApiTestState = (
36 overrides: Partial<CloudAuthApiTestState> = {},
37): CloudAuthApiTestState => ({
38 workos: makeWorkOSTestState(),
39 autumn: makeAutumnTestState(),
40 userStore: makeUserStoreTestState(),
41 session: makeSessionTestContext(),
42 ...overrides,
43});
44
45const makeCloudAuthApiTestClient = (state: CloudAuthApiTestState) => {
46 const ApiLive = HttpApiBuilder.layer(NonProtectedApi).pipe(
47 Layer.provide(Layer.mergeAll(CloudAuthPublicHandlers, CloudSessionAuthHandlers)),
48 Layer.provide(SessionAuthTestLayer(state.session)),
49 Layer.provideMerge(WorkOSTestLayer(state.workos)),
50 Layer.provideMerge(AutumnTestLayer(state.autumn)),
51 Layer.provideMerge(UserStoreTestLayer(state.userStore)),
52 Layer.provideMerge(ApiKeyServiceTestLayer),
53 Layer.provideMerge(HttpServer.layerServices),
54 Layer.provideMerge(Layer.succeed(HttpRouter.RouterConfig)({ maxParamLength: 1000 })),
55 );
56
57 const handler = HttpRouter.toWebHandler(ApiLive, { disableLogger: true }).handler;
58 const startHandler = requestHandler((request) => handler(request, undefined));
59 const fetchViaHandler: typeof globalThis.fetch = async (input, init) => {
60 const request = input instanceof Request ? input : new Request(input, init);
61 request.headers.set("cookie", SESSION_COOKIE);
62 return await startHandler(request, undefined);
63 };
64 const clientLayer = FetchHttpClient.layer.pipe(
65 Layer.provide(Layer.succeed(FetchHttpClient.Fetch)(fetchViaHandler)),
66 );
67
68 return HttpApiClient.make(NonProtectedApi, { baseUrl: TEST_BASE_URL }).pipe(
69 Effect.provide(clientLayer),
70 );
71};
72
73export type CloudAuthApiTestClient = Effect.Success<ReturnType<typeof makeCloudAuthApiTestClient>>;
74
75export class CloudAuthApiTestContext extends Context.Service<
76 CloudAuthApiTestContext,
77 {
78 readonly state: CloudAuthApiTestState;
79 readonly client: CloudAuthApiTestClient;
80 }
81>()("CloudAuthApiTestContext") {}
82
83export const CloudAuthApiTestContextLayer = (state = makeCloudAuthApiTestState()) =>
84 Layer.effect(
85 CloudAuthApiTestContext,
86 Effect.gen(function* () {
87 const client = yield* makeCloudAuthApiTestClient(state);
88 return { state, client };
89 }),
90 );
apps/cloud/src/auth/context.test-layer.ts+0−41
@@ -1,41 +0,0 @@
1import { Effect, Layer } from "effect";
2
3import { UserStoreService } from "./context";
4
5type UserStore = Parameters<Parameters<UserStoreService["Service"]["use"]>[0]>[0];
6
7export type UserStoreTestState = {
8 readonly upsertedOrganizations: Array<{ readonly id: string; readonly name: string }>;
9};
10
11export const makeUserStoreTestState = (
12 overrides: Partial<UserStoreTestState> = {},
13): UserStoreTestState => ({
14 upsertedOrganizations: [],
15 ...overrides,
16});
17
18const testDate = () => new Date("2026-01-01T00:00:00.000Z");
19
20const makeUserStoreTestService = (state: UserStoreTestState): UserStoreService["Service"] => {
21 const store = {
22 ensureAccount: async (id: string) => ({ id, createdAt: testDate() }),
23 getAccount: async (id: string) => ({ id, createdAt: testDate() }),
24 upsertOrganization: async (org: { readonly id: string; readonly name: string }) => {
25 state.upsertedOrganizations.push(org);
26 return { ...org, createdAt: testDate() };
27 },
28 getOrganization: async (id: string) => ({
29 id,
30 name: id,
31 createdAt: testDate(),
32 }),
33 } satisfies UserStore;
34
35 return {
36 use: (fn) => Effect.promise(() => fn(store)),
37 };
38};
39
40export const UserStoreTestLayer = (state: UserStoreTestState) =>
41 Layer.succeed(UserStoreService)(makeUserStoreTestService(state));
apps/cloud/src/auth/create-organization.e2e.node.test.ts+0−103
@@ -1,103 +0,0 @@
1import { assert, describe, it } from "@effect/vitest";
2import { Effect, Exit } from "effect";
3
4import {
5 CloudAuthApiTestContext,
6 CloudAuthApiTestContextLayer,
7 makeCloudAuthApiTestState,
8} from "./cloud-auth-api.test-context";
9import { makeWorkOSTestMembership, makeWorkOSTestState } from "./workos.test-layer";
10import { makeAutumnTestState } from "../extensions/billing/service.test-layer";
11
12describe("create organization API", () => {
13 it.effect("lets a paid user create another organization through the HTTP API client", () => {
14 const state = makeCloudAuthApiTestState({
15 workos: makeWorkOSTestState({
16 memberships: [
17 makeWorkOSTestMembership("org_free_1", "active"),
18 makeWorkOSTestMembership("org_free_2", "active"),
19 makeWorkOSTestMembership("org_paid", "active"),
20 ],
21 }),
22 autumn: makeAutumnTestState({
23 subscriptionsByOrgId: {
24 org_paid: [{ planId: "team", status: "active" }],
25 },
26 }),
27 });
28
29 return Effect.gen(function* () {
30 const { client } = yield* CloudAuthApiTestContext;
31
32 const result = yield* client.cloudAuth.createOrganization({
33 payload: { name: "Paid Extra Org" },
34 });
35
36 assert.deepEqual(result, { id: "org_created", name: "Paid Extra Org" });
37 assert.deepEqual(state.workos.createdOrganizations, [
38 { id: "org_created", name: "Paid Extra Org" },
39 ]);
40 assert.deepEqual(state.workos.createdMemberships, [
41 { organizationId: "org_created", userId: "user_1", roleSlug: "admin" },
42 ]);
43 assert.deepEqual(state.userStore.upsertedOrganizations, [
44 { id: "org_created", name: "Paid Extra Org" },
45 ]);
46 }).pipe(Effect.provide(CloudAuthApiTestContextLayer(state)));
47 });
48
49 it.effect(
50 "rejects a free-only user at the free organization limit through the HTTP API client",
51 () => {
52 const state = makeCloudAuthApiTestState({
53 workos: makeWorkOSTestState({
54 memberships: [
55 makeWorkOSTestMembership("org_free_1", "active"),
56 makeWorkOSTestMembership("org_free_2", "active"),
57 makeWorkOSTestMembership("org_free_3", "active"),
58 ],
59 }),
60 });
61
62 return Effect.gen(function* () {
63 const { client } = yield* CloudAuthApiTestContext;
64
65 const exit = yield* Effect.exit(
66 client.cloudAuth.createOrganization({
67 payload: { name: "Blocked Org" },
68 }),
69 );
70
71 assert.isTrue(Exit.isFailure(exit));
72 assert.deepEqual(state.workos.createdOrganizations, []);
73 assert.deepEqual(state.workos.createdMemberships, []);
74 assert.deepEqual(state.userStore.upsertedOrganizations, []);
75 }).pipe(Effect.provide(CloudAuthApiTestContextLayer(state)));
76 },
77 );
78
79 it.effect("does not count pending memberships toward the free organization limit", () => {
80 const state = makeCloudAuthApiTestState({
81 workos: makeWorkOSTestState({
82 memberships: [
83 makeWorkOSTestMembership("org_free_1", "active"),
84 makeWorkOSTestMembership("org_free_2", "active"),
85 makeWorkOSTestMembership("org_invited", "pending"),
86 ],
87 }),
88 });
89
90 return Effect.gen(function* () {
91 const { client } = yield* CloudAuthApiTestContext;
92
93 const result = yield* client.cloudAuth.createOrganization({
94 payload: { name: "Below Active Limit" },
95 });
96
97 assert.deepEqual(result, { id: "org_created", name: "Below Active Limit" });
98 assert.deepEqual(state.workos.createdOrganizations, [
99 { id: "org_created", name: "Below Active Limit" },
100 ]);
101 }).pipe(Effect.provide(CloudAuthApiTestContextLayer(state)));
102 });
103});
apps/cloud/src/auth/middleware.test-layer.ts+0−29
@@ -1,29 +0,0 @@
1import { Effect, Layer } from "effect";
2
3import { SessionAuth, SessionContext, SessionCookies, type Session } from "./middleware";
4
5export type SessionTestContext = Session;
6
7export const makeSessionTestContext = (
8 overrides: Partial<SessionTestContext> = {},
9): SessionTestContext => ({
10 accountId: "user_1",
11 email: "test@example.com",
12 name: "Test User",
13 avatarUrl: null,
14 organizationId: "org_existing_1",
15 sealedSession: "test_session",
16 refreshedSession: null,
17 ...overrides,
18});
19
20export const SessionAuthTestLayer = (session: Session = makeSessionTestContext()) =>
21 Layer.succeed(SessionAuth)({
22 cookie: (httpEffect) =>
23 httpEffect.pipe(
24 Effect.provideService(SessionContext, session),
25 // The session handlers driven via this layer don't assert cookie output;
26 // a no-op setter satisfies the SessionCookies the middleware provides.
27 Effect.provideService(SessionCookies, { set: () => {} }),
28 ),
29 });
apps/cloud/src/auth/workos.test-layer.ts+0−206
@@ -1,206 +0,0 @@
1import { Data, Effect, Layer } from "effect";
2import type { Organization, OrganizationMembership, OrganizationRole } from "@workos-inc/node";
3
4import { WorkOSClient, type WorkOSCollectedList } from "./workos";
5
6export type WorkOSTestState = {
7 readonly memberships: readonly OrganizationMembership[];
8 // When set, a successful create adds a unique org id + an active membership,
9 // so a running server's free-org limit trips live across requests. Off by
10 // default — existing tests keep the fixed `org_created` id and static list.
11 readonly growMembershipsOnCreate?: boolean;
12 // When set, the user is resolved from the `wos-session` cookie value and
13 // memberships are keyed per user — so each test/browser picks its own user id
14 // and is isolated on a shared instance (no reset needed), matching the
15 // in-process per-org harness. Implies grow-on-create.
16 readonly multiUser?: boolean;
17 readonly createdOrganizations: Array<{ readonly id: string; readonly name: string }>;
18 readonly createdMemberships: Array<{
19 readonly organizationId: string;
20 readonly userId: string;
21 readonly roleSlug: string | undefined;
22 }>;
23};
24
25export class UnstubbedWorkOSMethod extends Data.TaggedError("UnstubbedWorkOSMethod")<{
26 readonly method: string;
27}> {}
28
29export const makeWorkOSTestState = (overrides: Partial<WorkOSTestState> = {}): WorkOSTestState => ({
30 memberships: [],
31 createdOrganizations: [],
32 createdMemberships: [],
33 ...overrides,
34});
35
36export const WorkOSTestRole: OrganizationRole = {
37 object: "role",
38 id: "role_admin",
39 name: "Admin",
40 slug: "admin",
41 description: null,
42 permissions: [],
43 resourceTypeSlug: "organization",
44 type: "OrganizationRole",
45 createdAt: "2026-01-01T00:00:00.000Z",
46 updatedAt: "2026-01-01T00:00:00.000Z",
47};
48
49export const makeWorkOSTestOrganization = (id: string, name = id): Organization => ({
50 object: "organization",
51 id,
52 name,
53 allowProfilesOutsideOrganization: false,
54 domains: [],
55 createdAt: "2026-01-01T00:00:00.000Z",
56 updatedAt: "2026-01-01T00:00:00.000Z",
57 externalId: null,
58 metadata: {},
59});
60
61export const makeWorkOSTestMembership = (
62 organizationId: string,
63 status: OrganizationMembership["status"],
64) =>
65 ({
66 object: "organization_membership",
67 id: `membership_${organizationId}`,
68 organizationId,
69 organizationName: organizationId,
70 status,
71 userId: "user_1",
72 role: WorkOSTestRole,
73 directoryManaged: false,
74 createdAt: "2026-01-01T00:00:00.000Z",
75 updatedAt: "2026-01-01T00:00:00.000Z",
76 customAttributes: {},
77 }) satisfies OrganizationMembership;
78
79const decode = (value: string): string => {
80 // oxlint-disable-next-line executor/no-try-catch-or-throw -- boundary: tolerate non-URL-encoded cookie values from direct API callers
81 try {
82 return decodeURIComponent(value);
83 } catch {
84 return value;
85 }
86};
87
88const collected = <A>(data: readonly A[]): WorkOSCollectedList<A> => ({
89 object: "list",
90 data: [...data],
91 listMetadata: {
92 before: null,
93 after: null,
94 },
95});
96
97const makeWorkOSTestService = (state: WorkOSTestState): WorkOSClient["Service"] => {
98 const nextOrgId = "org_created";
99 let orgCounter = 0;
100 // Per-user membership buckets (multi-user mode). Closure-scoped, so they live
101 // for the layer's lifetime (= the running server) and isolate users from each
102 // other without a reset.
103 const byUser = new Map<string, OrganizationMembership[]>();
104 const memsFor = (userId: string) => {
105 let m = byUser.get(userId);
106 if (!m) byUser.set(userId, (m = []));
107 return m;
108 };
109 const grows = state.multiUser || state.growMembershipsOnCreate;
110 const service: Partial<WorkOSClient["Service"]> = {
111 listUserMemberships: (userId: string) =>
112 Effect.succeed(collected(state.multiUser ? memsFor(userId) : state.memberships)),
113 createOrganization: (name) =>
114 Effect.sync(() => {
115 const id = grows ? `${nextOrgId}_${++orgCounter}` : nextOrgId;
116 const org = makeWorkOSTestOrganization(id, name);
117 state.createdOrganizations.push({ id: org.id, name: org.name });
118 return org;
119 }),
120 createMembership: (organizationId, userId, roleSlug) =>
121 Effect.sync(() => {
122 state.createdMemberships.push({ organizationId, userId, roleSlug });
123 const membership = makeWorkOSTestMembership(organizationId, "active");
124 if (state.multiUser) memsFor(userId).push(membership);
125 else if (state.growMembershipsOnCreate) {
126 (state.memberships as OrganizationMembership[]).push(membership);
127 }
128 return membership;
129 }),
130 getOrganization: (organizationId: string) =>
131 Effect.succeed(makeWorkOSTestOrganization(organizationId)),
132 // The create-org page polls pending invitations; no invitation flows in
133 // the stub world, so the list is always empty.
134 listInvitationsByEmail: (_email: string) => Effect.succeed(collected([])),
135 getUserOrgMembership: (organizationId: string, _userId: string) =>
136 Effect.succeed(makeWorkOSTestMembership(organizationId, "active")),
137 // Multi-user refresh carries the user id forward + the switched-to org, so the
138 // create-org handler's `verified.organizationId === org.id` check passes and
139 // the user stays consistent across the refresh. The incoming sealed session
140 // may be URL-encoded (set-cookie round-trip through a real browser) and may
141 // already carry an `|org:` suffix from a previous switch — normalize both.
142 refreshSession: (sealedSession, organizationId) =>
143 Effect.succeed(
144 state.multiUser
145 ? `${userOf(sealedSession)}|org:${organizationId}`
146 : `session_${organizationId}`,
147 ),
148 authenticateSealedSession: (sealedSession) => Effect.sync(() => sessionOf(sealedSession)),
149 // The protected-API identity path (`resolveSessionPrincipal`) authenticates
150 // from the Request; mirror the real client's cookie-parse + delegate.
151 authenticateRequest: (request: Request) =>
152 Effect.sync(() => {
153 const match = /(?:^|;\s*)wos-session=([^;]+)/.exec(request.headers.get("cookie") ?? "");
154 return match ? sessionOf(decodeURIComponent(match[1]!)) : null;
155 }),
156 };
157
158 function userOf(sealedSession: string): string {
159 return decode(sealedSession).split("|org:")[0] || "user_1";
160 }
161
162 function sessionOf(sealedSession: string) {
163 if (state.multiUser) {
164 // Cookie is `<userId>` (initial) or `<userId>|org:<orgId>` (after refresh);
165 // a browser round-trip URL-encodes it. The LAST org segment is current.
166 const parts = decode(sealedSession).split("|org:");
167 const userPart = parts[0];
168 const orgPart = parts.length > 1 ? parts[parts.length - 1] : undefined;
169 return {
170 userId: userPart || "user_1",
171 email: "test@example.com",
172 firstName: "Test",
173 lastName: "User",
174 avatarUrl: null,
175 organizationId: orgPart ?? "",
176 sessionId: "session_id",
177 refreshedSession: undefined,
178 };
179 }
180 return {
181 userId: "user_1",
182 email: "test@example.com",
183 firstName: "Test",
184 lastName: "User",
185 avatarUrl: null,
186 organizationId: sealedSession.replace("session_", ""),
187 sessionId: "session_id",
188 refreshedSession: undefined,
189 };
190 }
191
192 return new Proxy(service as WorkOSClient["Service"], {
193 get: (target, prop) => {
194 if (prop in target) return target[prop as keyof typeof target];
195 return () =>
196 Effect.fail(
197 new UnstubbedWorkOSMethod({
198 method: typeof prop === "string" ? prop : (prop.description ?? "symbol"),
199 }),
200 );
201 },
202 });
203};
204
205export const WorkOSTestLayer = (state: WorkOSTestState) =>
206 Layer.succeed(WorkOSClient)(makeWorkOSTestService(state));
apps/cloud/src/auth/workos.ts+20−1
@@ -110,6 +110,25 @@ class WorkOSConfigurationError extends Data.TaggedError("WorkOSConfigurationErro · 109 unmodified lines
110 readonly message: string;110 readonly message: string;
111}> {}111}> {}
112 112
113/**
114 * Optional base-URL override for the WorkOS API (`WORKOS_API_URL`) — points
115 * the REAL SDK at a WorkOS emulator in tests/dev. Unset in production, where
116 * the SDK uses api.workos.com. Sealed-session crypto, JWKS verification, and
117 * every endpoint follow this host, so the whole auth stack runs against the
118 * emulator with zero code substitution.
119 */
120export const workosApiUrlOptions = (
121 url: string | undefined,
122): { apiHostname?: string; port?: number; https?: boolean } => {
123 if (!url) return {};
124 const parsed = new URL(url);
125 return {
126 apiHostname: parsed.hostname,
127 ...(parsed.port ? { port: Number(parsed.port) } : {}),
128 https: parsed.protocol === "https:",
129 };
130};
131
113// ---------------------------------------------------------------------------132// ---------------------------------------------------------------------------
114// Service133// Service
115// ---------------------------------------------------------------------------134// ---------------------------------------------------------------------------
@@ -125,7 +144,7 @@ const make = Effect.gen(function* () { · 9 unmodified lines
125 });144 });
126 }145 }
127 146
128 const workos = new WorkOS({ apiKey, clientId });147 const workos = new WorkOS({ apiKey, clientId, ...workosApiUrlOptions(env.WORKOS_API_URL) });
129 148
130 const use = <A>(fn: (wos: WorkOS) => Promise<A>) =>149 const use = <A>(fn: (wos: WorkOS) => Promise<A>) =>
131 withServiceLogging(150 withServiceLogging(
apps/cloud/src/engine/execution-stack.ts+1−0
@@ -62,6 +62,7 @@ export const CloudPluginsProvider: Layer.Layer<PluginsProvider> = Layer.succeed( · 61 unmodified lines
62 workosCredentials: {62 workosCredentials: {
63 apiKey: env.WORKOS_API_KEY,63 apiKey: env.WORKOS_API_KEY,
64 clientId: env.WORKOS_CLIENT_ID,64 clientId: env.WORKOS_CLIENT_ID,
65 apiUrl: env.WORKOS_API_URL,
65 },66 },
66 }),67 }),
67});68});
apps/cloud/src/env-augment.d.ts+5−0
@@ -26,6 +26,11 @@ declare global { · 25 unmodified lines
26 26
27 // Billing27 // Billing
28 AUTUMN_SECRET_KEY?: string;28 AUTUMN_SECRET_KEY?: string;
29 /** Optional Autumn base-URL override (Autumn emulator in tests/dev). */
30 AUTUMN_API_URL?: string;
31
32 /** Optional WorkOS base-URL override (WorkOS emulator in tests/dev). */
33 WORKOS_API_URL?: string;
29 34
30 // MCP35 // MCP
31 EXECUTOR_MCP_DEBUG?: string;36 EXECUTOR_MCP_DEBUG?: string;
apps/cloud/src/extensions/billing/route.ts+1−0
@@ -57,6 +57,7 @@ const handler = Effect.gen(function* () { · 56 unmodified lines
57 },57 },
58 clientOptions: {58 clientOptions: {
59 secretKey: env.AUTUMN_SECRET_KEY ?? "",59 secretKey: env.AUTUMN_SECRET_KEY ?? "",
60 ...(env.AUTUMN_API_URL ? { serverURL: env.AUTUMN_API_URL } : {}),
60 },61 },
61 pathPrefix: "/api/billing",62 pathPrefix: "/api/billing",
62 }),63 }),
apps/cloud/src/extensions/billing/service.test-layer.ts+0−36
@@ -1,36 +0,0 @@
1import { Effect, Layer } from "effect";
2import type { Autumn } from "autumn-js";
3
4import { AutumnService, type IAutumnService } from "./service";
5
6export type AutumnTestSubscriptionSummary = {
7 readonly planId?: string | null;
8 readonly status?: string | null;
9};
10
11export type AutumnTestState = {
12 readonly subscriptionsByOrgId: Readonly<Record<string, readonly AutumnTestSubscriptionSummary[]>>;
13};
14
15export const makeAutumnTestState = (overrides: Partial<AutumnTestState> = {}): AutumnTestState => ({
16 subscriptionsByOrgId: {},
17 ...overrides,
18});
19
20const makeAutumnTestService = (state: AutumnTestState): IAutumnService => {
21 const fakeClient = {
22 customers: {
23 getOrCreate: async ({ customerId }: { readonly customerId: string }) => ({
24 subscriptions: state.subscriptionsByOrgId[customerId] ?? [],
25 }),
26 },
27 } as Autumn;
28
29 return {
30 use: (fn) => Effect.promise(() => fn(fakeClient)),
31 trackExecution: () => Effect.void,
32 };
33};
34
35export const AutumnTestLayer = (state: AutumnTestState) =>
36 Layer.succeed(AutumnService)(makeAutumnTestService(state));
apps/cloud/src/extensions/billing/service.ts+5−1
@@ -48,7 +48,11 @@ const make = Effect.sync(() => { · 47 unmodified lines
48 } satisfies IAutumnService;48 } satisfies IAutumnService;
49 }49 }
50 50
51 const client = new Autumn({ secretKey });51 // AUTUMN_API_URL points the real SDK at an Autumn emulator in tests/dev.
52 const client = new Autumn({
53 secretKey,
54 ...(env.AUTUMN_API_URL ? { serverURL: env.AUTUMN_API_URL } : {}),
55 });
52 56
53 const use = <A>(fn: (client: Autumn) => Promise<A>) =>57 const use = <A>(fn: (client: Autumn) => Promise<A>) =>
54 Effect.tryPromise({58 Effect.tryPromise({
apps/cloud/src/extensions/routes.ts+1−5
@@ -34,7 +34,6 @@ import { · 33 unmodified lines
34} from "../auth/handlers";34} from "../auth/handlers";
35import { CloudAuthApi, CloudAuthPublicApi } from "../auth/api";35import { CloudAuthApi, CloudAuthPublicApi } from "../auth/api";
36import { OrgAuthLive, SessionAuthLive } from "../auth/middleware-live";36import { OrgAuthLive, SessionAuthLive } from "../auth/middleware-live";
37import { E2E_STUB, E2EStubAutumnLayer } from "../testing/e2e-stub";
38import { OrgApi, OrgHttpApi } from "../org/api";37import { OrgApi, OrgHttpApi } from "../org/api";
39import { OrgHandlers } from "../org/handlers";38import { OrgHandlers } from "../org/handlers";
40import { AutumnService } from "../extensions/billing/service";39import { AutumnService } from "../extensions/billing/service";
@@ -75,11 +74,8 @@ export const makeCloudExtensionRoutes = (rsLive: Layer.Layer<DbService | UserSto · 34 unmodified lines
75 const SessionRoutes = HttpApiBuilder.layer(NonProtectedApi).pipe(74 const SessionRoutes = HttpApiBuilder.layer(NonProtectedApi).pipe(
76 Layer.provide(Layer.mergeAll(CloudAuthPublicHandlers, CloudSessionAuthHandlers)),75 Layer.provide(Layer.mergeAll(CloudAuthPublicHandlers, CloudSessionAuthHandlers)),
77 Layer.provide(requestScopedMiddleware(rsLive).layer),76 Layer.provide(requestScopedMiddleware(rsLive).layer),
78 // SessionAuthLive resolves the user from the wos-session cookie via WorkOSClient
79 // — which is the stub under EXECUTOR_E2E_STUB (app.ts), so the user is the cookie
80 // value. Only Autumn is swapped to the free-plan stub here. Off in production.
81 Layer.provideMerge(SessionAuthLive),77 Layer.provideMerge(SessionAuthLive),
82 Layer.provideMerge(E2E_STUB ? E2EStubAutumnLayer : AutumnService.Default),78 Layer.provideMerge(AutumnService.Default),
83 Layer.provide(apiPrefixedRouter),79 Layer.provide(apiPrefixedRouter),
84 );80 );
85 81
apps/cloud/src/testing/e2e-stub.ts+0−21
@@ -1,21 +0,0 @@
1// Env-gated stub layers that turn `vite dev` into a fully-stubbed, logged-in
2// instance — one target every surface (browser / API / MCP / CLI) can drive,
3// replacing the bespoke in-process harnesses + e2e-server wiring.
4//
5// Enabled by `EXECUTOR_E2E_STUB=1`. NEVER set in production — when unset, the
6// served route composition is byte-for-byte the real `*Live` layers.
7import { WorkOSTestLayer, makeWorkOSTestState } from "../auth/workos.test-layer";
8import { AutumnTestLayer, makeAutumnTestState } from "../extensions/billing/service.test-layer";
9
10export const E2E_STUB = process.env.EXECUTOR_E2E_STUB === "1";
11
12// Multi-user stub WorkOS: the `wos-session` cookie value IS the user id, with
13// per-user membership buckets — so each test/browser picks a fresh user and is
14// isolated on the one shared instance (no reset). Swapped in for
15// `CoreSharedServices`, it authenticates every surface (session routes,
16// /account/me, SSR) with no real WorkOS.
17const workos = makeWorkOSTestState({ memberships: [], multiUser: true });
18const autumn = makeAutumnTestState({}); // no paid subscription → free plan → 3-org limit applies
19
20export const E2EStubWorkOSLayer = WorkOSTestLayer(workos);
21export const E2EStubAutumnLayer = AutumnTestLayer(autumn);
bun.lock+91−13
@@ -321,6 +321,8 @@ · 320 unmodified lines
321 "version": "0.0.3",321 "version": "0.0.3",
322 "dependencies": {322 "dependencies": {
323 "@executor-js/api": "workspace:*",323 "@executor-js/api": "workspace:*",
324 "@executor-js/emulate": "^0.6.0",
325 "@executor-js/mcporter": "^0.11.4",
324 "@executor-js/plugin-graphql": "workspace:*",326 "@executor-js/plugin-graphql": "workspace:*",
325 "@executor-js/plugin-mcp": "workspace:*",327 "@executor-js/plugin-mcp": "workspace:*",
326 "@executor-js/plugin-openapi": "workspace:*",328 "@executor-js/plugin-openapi": "workspace:*",
@@ -1515,6 +1517,8 @@ · 1188 unmodified lines
1515 1517
1516 "@executor-js/e2e": ["@executor-js/e2e@workspace:e2e"],1518 "@executor-js/e2e": ["@executor-js/e2e@workspace:e2e"],
1517 1519
1520 "@executor-js/emulate": ["@executor-js/emulate@0.6.0", "", { "dependencies": { "commander": "^14", "graphql": "^16.9.0", "jose": "^6", "picocolors": "^1.1.1", "yaml": "^2" }, "bin": { "emulate": "dist/index.js" } }, "sha512-GtQsuLRZhD8LKu57IuHsHlLpTgho+JMhkvdFfj/DGsFlm7Gs4KzlUP7fuuZ/zrf/c7ue1urdUNEO1I2y0/a2pg=="],
1521
1518 "@executor-js/example-all-plugins": ["@executor-js/example-all-plugins@workspace:examples/all-plugins"],1522 "@executor-js/example-all-plugins": ["@executor-js/example-all-plugins@workspace:examples/all-plugins"],
1519 1523
1520 "@executor-js/example-docs-sdk-quickstart": ["@executor-js/example-docs-sdk-quickstart@workspace:examples/docs-sdk-quickstart"],1524 "@executor-js/example-docs-sdk-quickstart": ["@executor-js/example-docs-sdk-quickstart@workspace:examples/docs-sdk-quickstart"],
@@ -1539,6 +1543,8 @@ · 18 unmodified lines
1539 1543
1540 "@executor-js/marketing": ["@executor-js/marketing@workspace:apps/marketing"],1544 "@executor-js/marketing": ["@executor-js/marketing@workspace:apps/marketing"],
1541 1545
1546 "@executor-js/mcporter": ["@executor-js/mcporter@0.11.4", "", { "dependencies": { "@iarna/toml": "^2.2.5", "@modelcontextprotocol/sdk": "^1.29.0", "acorn": "^8.16.0", "commander": "^14.0.3", "es-toolkit": "^1.47.0", "jsonc-parser": "^3.3.1", "ora": "^9.4.0", "rolldown": "1.0.1", "zod": "^4.3.6" }, "bin": { "mcporter": "dist/cli.js" } }, "sha512-KewlNnqmzSkJAH7JS/2le9WxPWzL/ND7Pt/G5KGgfrSK9K9cE8UPIsEYZYZUJYoFnbCBzhlU9ugg59ptv1h3Ow=="],
1547
1542 "@executor-js/plugin-desktop-settings": ["@executor-js/plugin-desktop-settings@workspace:packages/plugins/desktop-settings"],1548 "@executor-js/plugin-desktop-settings": ["@executor-js/plugin-desktop-settings@workspace:packages/plugins/desktop-settings"],
1543 1549
1544 "@executor-js/plugin-encrypted-secrets": ["@executor-js/plugin-encrypted-secrets@workspace:packages/plugins/encrypted-secrets"],1550 "@executor-js/plugin-encrypted-secrets": ["@executor-js/plugin-encrypted-secrets@workspace:packages/plugins/encrypted-secrets"],
@@ -1607,6 +1613,8 @@ · 62 unmodified lines
1607 1613
1608 "@hookform/resolvers": ["@hookform/resolvers@5.2.2", "", { "dependencies": { "@standard-schema/utils": "^0.3.0" }, "peerDependencies": { "react-hook-form": "^7.55.0" } }, "sha512-A/IxlMLShx3KjV/HeTcTfaMxdwy690+L/ZADoeaTltLx+CVuzkeVIPuybK3jrRfw7YZnmdKsVVHAlEPIAEUNlA=="],1614 "@hookform/resolvers": ["@hookform/resolvers@5.2.2", "", { "dependencies": { "@standard-schema/utils": "^0.3.0" }, "peerDependencies": { "react-hook-form": "^7.55.0" } }, "sha512-A/IxlMLShx3KjV/HeTcTfaMxdwy690+L/ZADoeaTltLx+CVuzkeVIPuybK3jrRfw7YZnmdKsVVHAlEPIAEUNlA=="],
1609 1615
1616 "@iarna/toml": ["@iarna/toml@2.2.5", "", {}, "sha512-trnsAYxU3xnS1gPHPyU961coFyLkh4gAD/0zQ5mymY4yOZ+CYvsPqUbOFSw0aDM4y0tV7tiFxL/1XfXPNC6IPg=="],
1617
1610 "@iconify/types": ["@iconify/types@2.0.0", "", {}, "sha512-+wluvCrRhXrhyOmRDJ3q8mux9JkKy5SJ/v8ol2tu4FVjyYvtEzkc/3pK15ET6RKg4b4w4BmTk1+gsCUhf21Ykg=="],1618 "@iconify/types": ["@iconify/types@2.0.0", "", {}, "sha512-+wluvCrRhXrhyOmRDJ3q8mux9JkKy5SJ/v8ol2tu4FVjyYvtEzkc/3pK15ET6RKg4b4w4BmTk1+gsCUhf21Ykg=="],
1611 1619
1612 "@iconify/utils": ["@iconify/utils@3.1.0", "", { "dependencies": { "@antfu/install-pkg": "^1.1.0", "@iconify/types": "^2.0.0", "mlly": "^1.8.0" } }, "sha512-Zlzem1ZXhI1iHeeERabLNzBHdOa4VhQbqAcOQaMKuTuyZCpwKbC2R4Dd0Zo3g9EAc+Y4fiarO8HIHRAth7+skw=="],1620 "@iconify/utils": ["@iconify/utils@3.1.0", "", { "dependencies": { "@antfu/install-pkg": "^1.1.0", "@iconify/types": "^2.0.0", "mlly": "^1.8.0" } }, "sha512-Zlzem1ZXhI1iHeeERabLNzBHdOa4VhQbqAcOQaMKuTuyZCpwKbC2R4Dd0Zo3g9EAc+Y4fiarO8HIHRAth7+skw=="],
@@ -3301,7 +3309,7 @@ · 1688 unmodified lines
3301 3309
3302 "es-set-tostringtag": ["es-set-tostringtag@2.1.0", "", { "dependencies": { "es-errors": "^1.3.0", "get-intrinsic": "^1.2.6", "has-tostringtag": "^1.0.2", "hasown": "^2.0.2" } }, "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA=="],3310 "es-set-tostringtag": ["es-set-tostringtag@2.1.0", "", { "dependencies": { "es-errors": "^1.3.0", "get-intrinsic": "^1.2.6", "has-tostringtag": "^1.0.2", "hasown": "^2.0.2" } }, "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA=="],
3303 3311
3304 "es-toolkit": ["es-toolkit@1.45.1", "", {}, "sha512-/jhoOj/Fx+A+IIyDNOvO3TItGmlMKhtX8ISAHKE90c4b/k1tqaqEZ+uUqfpU8DMnW5cgNJv606zS55jGvza0Xw=="],3312 "es-toolkit": ["es-toolkit@1.47.0", "", {}, "sha512-n1GuoD0WEQZMBk5tttoZSqwgyLx01oqa5XsBmCHwPyNe1S9jPBEmtR2pSgp2kJuWE3ciFZ6yRHmY4pM4C3OOkw=="],
3305 3313
3306 "es6-error": ["es6-error@4.1.1", "", {}, "sha512-Um/+FxMr9CISWh0bi5Zv0iOD+4cFh5qLeks1qhAopKVAJw3drgKbKySikp7wGhDL0HPeaja0P5ULZrxLkniUVg=="],3314 "es6-error": ["es6-error@4.1.1", "", {}, "sha512-Um/+FxMr9CISWh0bi5Zv0iOD+4cFh5qLeks1qhAopKVAJw3drgKbKySikp7wGhDL0HPeaja0P5ULZrxLkniUVg=="],
3307 3315
@@ -3851,7 +3859,7 @@ · 543 unmodified lines
3851 3859
3852 "lodash.startcase": ["lodash.startcase@4.4.0", "", {}, "sha512-+WKqsK294HMSc2jEbNgpHpd0JfIBhp7rEV4aqXWqFr6AlXov+SlcgB1Fv01y2kGe3Gc8nMW7VA0SrGuSkRfIEg=="],3860 "lodash.startcase": ["lodash.startcase@4.4.0", "", {}, "sha512-+WKqsK294HMSc2jEbNgpHpd0JfIBhp7rEV4aqXWqFr6AlXov+SlcgB1Fv01y2kGe3Gc8nMW7VA0SrGuSkRfIEg=="],
3853 3861
3854 "log-symbols": ["log-symbols@6.0.0", "", { "dependencies": { "chalk": "^5.3.0", "is-unicode-supported": "^1.3.0" } }, "sha512-i24m8rpwhmPIS4zscNzK6MSEhk0DUWa/8iYQWxhffV8jkI4Phvs3F+quL5xvS0gdQR0FyTCMMH33Y78dDTzzIw=="],3862 "log-symbols": ["log-symbols@7.0.1", "", { "dependencies": { "is-unicode-supported": "^2.0.0", "yoctocolors": "^2.1.1" } }, "sha512-ja1E3yCr9i/0hmBVaM0bfwDjnGy8I/s6PP4DFp+yP+a+mrHO4Rm7DtmnqROTUkHIkqffC84YY7AeqX6oFk0WFg=="],
3855 3863
3856 "long": ["long@5.3.2", "", {}, "sha512-mNAgZ1GmyNhD7AuqnTG3/VQ26o760+ZYBPKjPvugO8+nLbYfX6TVpJPseBvopbdY+qpZ/lKUnmEc1LeZYS3QAA=="],3864 "long": ["long@5.3.2", "", {}, "sha512-mNAgZ1GmyNhD7AuqnTG3/VQ26o760+ZYBPKjPvugO8+nLbYfX6TVpJPseBvopbdY+qpZ/lKUnmEc1LeZYS3QAA=="],
3857 3865
@@ -4147,7 +4155,7 @@ · 289 unmodified lines
4147 4155
4148 "openapi-types": ["openapi-types@12.1.3", "", {}, "sha512-N4YtSYJqghVu4iek2ZUvcN/0aqH1kRDuNqzcycDxhOUpg7GdvLa2F3DgS6yBNhInhv2r/6I0Flkn7CqL8+nIcw=="],4156 "openapi-types": ["openapi-types@12.1.3", "", {}, "sha512-N4YtSYJqghVu4iek2ZUvcN/0aqH1kRDuNqzcycDxhOUpg7GdvLa2F3DgS6yBNhInhv2r/6I0Flkn7CqL8+nIcw=="],
4149 4157
4150 "ora": ["ora@8.2.0", "", { "dependencies": { "chalk": "^5.3.0", "cli-cursor": "^5.0.0", "cli-spinners": "^2.9.2", "is-interactive": "^2.0.0", "is-unicode-supported": "^2.0.0", "log-symbols": "^6.0.0", "stdin-discarder": "^0.2.2", "string-width": "^7.2.0", "strip-ansi": "^7.1.0" } }, "sha512-weP+BZ8MVNnlCm8c0Qdc1WSWq4Qn7I+9CJGm7Qali6g44e/PUzbjNqJX5NJ9ljlNMosfJvg1fKEGILklK9cwnw=="],4158 "ora": ["ora@9.4.0", "", { "dependencies": { "chalk": "^5.6.2", "cli-cursor": "^5.0.0", "cli-spinners": "^3.2.0", "is-interactive": "^2.0.0", "is-unicode-supported": "^2.1.0", "log-symbols": "^7.0.1", "stdin-discarder": "^0.3.2", "string-width": "^8.1.0" } }, "sha512-84cglkRILFxdtA8hAvLNdMrtBpPNBTrQ9/ulg0FA7xLMnD6mifv+enAIeRmvtv+WgdCE+LPGOfQmtJRrVaIVhQ=="],
4151 4159
4152 "outdent": ["outdent@0.5.0", "", {}, "sha512-/jHxFIzoMXdqPzTaCpFzAAWhpkSjZPF4Vsn6jAfNpmbH/ymsmd7Qc6VE9BGn0L6YMj6uwpQLxCECpus4ukKS9Q=="],4160 "outdent": ["outdent@0.5.0", "", {}, "sha512-/jHxFIzoMXdqPzTaCpFzAAWhpkSjZPF4Vsn6jAfNpmbH/ymsmd7Qc6VE9BGn0L6YMj6uwpQLxCECpus4ukKS9Q=="],
4153 4161
@@ -4685,7 +4693,7 @@ · 531 unmodified lines
4685 4693
4686 "std-env": ["std-env@4.1.0", "", {}, "sha512-Rq7ybcX2RuC55r9oaPVEW7/xu3tj8u4GeBYHBWCychFtzMIr86A7e3PPEBPT37sHStKX3+TiX/Fr/ACmJLVlLQ=="],4694 "std-env": ["std-env@4.1.0", "", {}, "sha512-Rq7ybcX2RuC55r9oaPVEW7/xu3tj8u4GeBYHBWCychFtzMIr86A7e3PPEBPT37sHStKX3+TiX/Fr/ACmJLVlLQ=="],
4687 4695
4688 "stdin-discarder": ["stdin-discarder@0.2.2", "", {}, "sha512-UhDfHmA92YAlNnCfhmq0VeNL5bDbiZGg7sZ2IvPsXubGkiNa9EC+tUTsjBRsYUAz87btI6/1wf4XoVvQ3uRnmQ=="],4696 "stdin-discarder": ["stdin-discarder@0.3.2", "", {}, "sha512-eCPu1qRxPVkl5605OTWF8Wz40b4Mf45NY5LQmVPQ599knfs5QhASUm9GbJ5BDMDOXgrnh0wyEdvzmL//YMlw0A=="],
4689 4697
4690 "streamdown": ["streamdown@2.5.0", "", { "dependencies": { "clsx": "^2.1.1", "hast-util-to-jsx-runtime": "^2.3.6", "html-url-attributes": "^3.0.1", "marked": "^17.0.1", "mermaid": "^11.12.2", "rehype-harden": "^1.1.8", "rehype-raw": "^7.0.0", "rehype-sanitize": "^6.0.0", "remark-gfm": "^4.0.1", "remark-parse": "^11.0.0", "remark-rehype": "^11.1.2", "remend": "1.3.0", "tailwind-merge": "^3.4.0", "unified": "^11.0.5", "unist-util-visit": "^5.0.0", "unist-util-visit-parents": "^6.0.0" }, "peerDependencies": { "react": "^18.0.0 || ^19.0.0", "react-dom": "^18.0.0 || ^19.0.0" } }, "sha512-/tTnURfIOxZK/pqJAxsfCvETG/XCJHoWnk3jq9xLcuz6CSpnjjuxSRBTTL4PKGhxiZQf0lqPxGhImdpwcZ2XwA=="],4698 "streamdown": ["streamdown@2.5.0", "", { "dependencies": { "clsx": "^2.1.1", "hast-util-to-jsx-runtime": "^2.3.6", "html-url-attributes": "^3.0.1", "marked": "^17.0.1", "mermaid": "^11.12.2", "rehype-harden": "^1.1.8", "rehype-raw": "^7.0.0", "rehype-sanitize": "^6.0.0", "remark-gfm": "^4.0.1", "remark-parse": "^11.0.0", "remark-rehype": "^11.1.2", "remend": "1.3.0", "tailwind-merge": "^3.4.0", "unified": "^11.0.5", "unist-util-visit": "^5.0.0", "unist-util-visit-parents": "^6.0.0" }, "peerDependencies": { "react": "^18.0.0 || ^19.0.0", "react-dom": "^18.0.0 || ^19.0.0" } }, "sha512-/tTnURfIOxZK/pqJAxsfCvETG/XCJHoWnk3jq9xLcuz6CSpnjjuxSRBTTL4PKGhxiZQf0lqPxGhImdpwcZ2XwA=="],
4691 4699
@@ -5135,12 +5143,20 @@ · 443 unmodified lines
5135 5143
5136 "@esbuild-kit/core-utils/esbuild": ["esbuild@0.18.20", "", { "optionalDependencies": { "@esbuild/android-arm": "0.18.20", "@esbuild/android-arm64": "0.18.20", "@esbuild/android-x64": "0.18.20", "@esbuild/darwin-arm64": "0.18.20", "@esbuild/darwin-x64": "0.18.20", "@esbuild/freebsd-arm64": "0.18.20", "@esbuild/freebsd-x64": "0.18.20", "@esbuild/linux-arm": "0.18.20", "@esbuild/linux-arm64": "0.18.20", "@esbuild/linux-ia32": "0.18.20", "@esbuild/linux-loong64": "0.18.20", "@esbuild/linux-mips64el": "0.18.20", "@esbuild/linux-ppc64": "0.18.20", "@esbuild/linux-riscv64": "0.18.20", "@esbuild/linux-s390x": "0.18.20", "@esbuild/linux-x64": "0.18.20", "@esbuild/netbsd-x64": "0.18.20", "@esbuild/openbsd-x64": "0.18.20", "@esbuild/sunos-x64": "0.18.20", "@esbuild/win32-arm64": "0.18.20", "@esbuild/win32-ia32": "0.18.20", "@esbuild/win32-x64": "0.18.20" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-ceqxoedUrcayh7Y7ZX6NdbbDzGROiyVBgC4PriJThBKSVPWnnFHZAkfI1lJT8QFkOwH4qOS2SJkS4wvpGl8BpA=="],5144 "@esbuild-kit/core-utils/esbuild": ["esbuild@0.18.20", "", { "optionalDependencies": { "@esbuild/android-arm": "0.18.20", "@esbuild/android-arm64": "0.18.20", "@esbuild/android-x64": "0.18.20", "@esbuild/darwin-arm64": "0.18.20", "@esbuild/darwin-x64": "0.18.20", "@esbuild/freebsd-arm64": "0.18.20", "@esbuild/freebsd-x64": "0.18.20", "@esbuild/linux-arm": "0.18.20", "@esbuild/linux-arm64": "0.18.20", "@esbuild/linux-ia32": "0.18.20", "@esbuild/linux-loong64": "0.18.20", "@esbuild/linux-mips64el": "0.18.20", "@esbuild/linux-ppc64": "0.18.20", "@esbuild/linux-riscv64": "0.18.20", "@esbuild/linux-s390x": "0.18.20", "@esbuild/linux-x64": "0.18.20", "@esbuild/netbsd-x64": "0.18.20", "@esbuild/openbsd-x64": "0.18.20", "@esbuild/sunos-x64": "0.18.20", "@esbuild/win32-arm64": "0.18.20", "@esbuild/win32-ia32": "0.18.20", "@esbuild/win32-x64": "0.18.20" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-ceqxoedUrcayh7Y7ZX6NdbbDzGROiyVBgC4PriJThBKSVPWnnFHZAkfI1lJT8QFkOwH4qOS2SJkS4wvpGl8BpA=="],
5137 5145
5146 "@executor-js/emulate/commander": ["commander@14.0.3", "", {}, "sha512-H+y0Jo/T1RZ9qPP4Eh1pkcQcLRglraJaSLoyOtHxu6AapkjWVCy2Sit1QQ4x3Dng8qDlSsZEet7g5Pq06MvTgw=="],
5147
5148 "@executor-js/emulate/jose": ["jose@6.2.2", "", {}, "sha512-d7kPDd34KO/YnzaDOlikGpOurfF0ByC2sEV4cANCtdqLlTfBlw2p14O/5d/zv40gJPbIQxfES3nSx1/oYNyuZQ=="],
5149
5138 "@executor-js/example-all-plugins/typescript": ["typescript@6.0.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-y2TvuxSZPDyQakkFRPZHKFm+KKVqIisdg9/CZwm9ftvKXLP8NRWj38/ODjNbr43SsoXqNuAisEf1GdCxqWcdBw=="],5150 "@executor-js/example-all-plugins/typescript": ["typescript@6.0.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-y2TvuxSZPDyQakkFRPZHKFm+KKVqIisdg9/CZwm9ftvKXLP8NRWj38/ODjNbr43SsoXqNuAisEf1GdCxqWcdBw=="],
5139 5151
5140 "@executor-js/example-promise-sdk/typescript": ["typescript@6.0.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-y2TvuxSZPDyQakkFRPZHKFm+KKVqIisdg9/CZwm9ftvKXLP8NRWj38/ODjNbr43SsoXqNuAisEf1GdCxqWcdBw=="],5152 "@executor-js/example-promise-sdk/typescript": ["typescript@6.0.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-y2TvuxSZPDyQakkFRPZHKFm+KKVqIisdg9/CZwm9ftvKXLP8NRWj38/ODjNbr43SsoXqNuAisEf1GdCxqWcdBw=="],
5141 5153
5142 "@executor-js/fumadb/commander": ["commander@14.0.3", "", {}, "sha512-H+y0Jo/T1RZ9qPP4Eh1pkcQcLRglraJaSLoyOtHxu6AapkjWVCy2Sit1QQ4x3Dng8qDlSsZEet7g5Pq06MvTgw=="],5154 "@executor-js/fumadb/commander": ["commander@14.0.3", "", {}, "sha512-H+y0Jo/T1RZ9qPP4Eh1pkcQcLRglraJaSLoyOtHxu6AapkjWVCy2Sit1QQ4x3Dng8qDlSsZEet7g5Pq06MvTgw=="],
5143 5155
5156 "@executor-js/mcporter/commander": ["commander@14.0.3", "", {}, "sha512-H+y0Jo/T1RZ9qPP4Eh1pkcQcLRglraJaSLoyOtHxu6AapkjWVCy2Sit1QQ4x3Dng8qDlSsZEet7g5Pq06MvTgw=="],
5157
5158 "@executor-js/mcporter/rolldown": ["rolldown@1.0.1", "", { "dependencies": { "@oxc-project/types": "=0.130.0", "@rolldown/pluginutils": "^1.0.0" }, "optionalDependencies": { "@rolldown/binding-android-arm64": "1.0.1", "@rolldown/binding-darwin-arm64": "1.0.1", "@rolldown/binding-darwin-x64": "1.0.1", "@rolldown/binding-freebsd-x64": "1.0.1", "@rolldown/binding-linux-arm-gnueabihf": "1.0.1", "@rolldown/binding-linux-arm64-gnu": "1.0.1", "@rolldown/binding-linux-arm64-musl": "1.0.1", "@rolldown/binding-linux-ppc64-gnu": "1.0.1", "@rolldown/binding-linux-s390x-gnu": "1.0.1", "@rolldown/binding-linux-x64-gnu": "1.0.1", "@rolldown/binding-linux-x64-musl": "1.0.1", "@rolldown/binding-openharmony-arm64": "1.0.1", "@rolldown/binding-wasm32-wasi": "1.0.1", "@rolldown/binding-win32-arm64-msvc": "1.0.1", "@rolldown/binding-win32-x64-msvc": "1.0.1" }, "bin": { "rolldown": "bin/cli.mjs" } }, "sha512-X0KQHljNnEkWNqqiz9zJrGunh1B0HgOxLXvnFpCOcadzcy5qohZ3tqMEUg00vncoRovXuK3ZqCT9KnnKzoInFQ=="],
5159
5144 "@graphql-tools/executor/@graphql-tools/utils": ["@graphql-tools/utils@11.1.0", "", { "dependencies": { "@graphql-typed-document-node/core": "^3.1.1", "@whatwg-node/promise-helpers": "^1.0.0", "cross-inspect": "1.0.1", "tslib": "^2.4.0" }, "peerDependencies": { "graphql": "^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0" } }, "sha512-PtFVG4r8Z2LEBSaPYQMusBiB3o6kjLVJyjCLbnWem/SpSuM21v6LTmgpkXfYU1qpBV2UGsFyuEnSJInl8fR1Ag=="],5160 "@graphql-tools/executor/@graphql-tools/utils": ["@graphql-tools/utils@11.1.0", "", { "dependencies": { "@graphql-typed-document-node/core": "^3.1.1", "@whatwg-node/promise-helpers": "^1.0.0", "cross-inspect": "1.0.1", "tslib": "^2.4.0" }, "peerDependencies": { "graphql": "^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0" } }, "sha512-PtFVG4r8Z2LEBSaPYQMusBiB3o6kjLVJyjCLbnWem/SpSuM21v6LTmgpkXfYU1qpBV2UGsFyuEnSJInl8fR1Ag=="],
5145 5161
5146 "@graphql-tools/merge/@graphql-tools/utils": ["@graphql-tools/utils@11.1.0", "", { "dependencies": { "@graphql-typed-document-node/core": "^3.1.1", "@whatwg-node/promise-helpers": "^1.0.0", "cross-inspect": "1.0.1", "tslib": "^2.4.0" }, "peerDependencies": { "graphql": "^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0" } }, "sha512-PtFVG4r8Z2LEBSaPYQMusBiB3o6kjLVJyjCLbnWem/SpSuM21v6LTmgpkXfYU1qpBV2UGsFyuEnSJInl8fR1Ag=="],5162 "@graphql-tools/merge/@graphql-tools/utils": ["@graphql-tools/utils@11.1.0", "", { "dependencies": { "@graphql-typed-document-node/core": "^3.1.1", "@whatwg-node/promise-helpers": "^1.0.0", "cross-inspect": "1.0.1", "tslib": "^2.4.0" }, "peerDependencies": { "graphql": "^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0" } }, "sha512-PtFVG4r8Z2LEBSaPYQMusBiB3o6kjLVJyjCLbnWem/SpSuM21v6LTmgpkXfYU1qpBV2UGsFyuEnSJInl8fR1Ag=="],
@@ -5161,14 +5177,20 @@ · 14 unmodified lines
5161 5177
5162 "@lobehub/fluent-emoji/emoji-regex": ["emoji-regex@10.6.0", "", {}, "sha512-toUI84YS5YmxW219erniWD0CIVOo46xGKColeNQRgOzDorgBi1v4D71/OFzgD9GO2UGKIv1C3Sp8DAn0+j5w7A=="],5178 "@lobehub/fluent-emoji/emoji-regex": ["emoji-regex@10.6.0", "", {}, "sha512-toUI84YS5YmxW219erniWD0CIVOo46xGKColeNQRgOzDorgBi1v4D71/OFzgD9GO2UGKIv1C3Sp8DAn0+j5w7A=="],
5163 5179
5180 "@lobehub/fluent-emoji/es-toolkit": ["es-toolkit@1.45.1", "", {}, "sha512-/jhoOj/Fx+A+IIyDNOvO3TItGmlMKhtX8ISAHKE90c4b/k1tqaqEZ+uUqfpU8DMnW5cgNJv606zS55jGvza0Xw=="],
5181
5164 "@lobehub/fluent-emoji/lucide-react": ["lucide-react@0.562.0", "", { "peerDependencies": { "react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, "sha512-82hOAu7y0dbVuFfmO4bYF1XEwYk/mEbM5E+b1jgci/udUBEE/R7LF5Ip0CCEmXe8AybRM8L+04eP+LGZeDvkiw=="],5182 "@lobehub/fluent-emoji/lucide-react": ["lucide-react@0.562.0", "", { "peerDependencies": { "react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, "sha512-82hOAu7y0dbVuFfmO4bYF1XEwYk/mEbM5E+b1jgci/udUBEE/R7LF5Ip0CCEmXe8AybRM8L+04eP+LGZeDvkiw=="],
5165 5183
5184 "@lobehub/icons/es-toolkit": ["es-toolkit@1.45.1", "", {}, "sha512-/jhoOj/Fx+A+IIyDNOvO3TItGmlMKhtX8ISAHKE90c4b/k1tqaqEZ+uUqfpU8DMnW5cgNJv606zS55jGvza0Xw=="],
5185
5166 "@lobehub/icons/lucide-react": ["lucide-react@0.469.0", "", { "peerDependencies": { "react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, "sha512-28vvUnnKQ/dBwiCQtwJw7QauYnE7yd2Cyp4tTTJpvglX4EMpbflcdBgrgToX2j71B3YvugK/NH3BGUk+E/p/Fw=="],5186 "@lobehub/icons/lucide-react": ["lucide-react@0.469.0", "", { "peerDependencies": { "react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, "sha512-28vvUnnKQ/dBwiCQtwJw7QauYnE7yd2Cyp4tTTJpvglX4EMpbflcdBgrgToX2j71B3YvugK/NH3BGUk+E/p/Fw=="],
5167 5187
5168 "@lobehub/ui/@base-ui/react": ["@base-ui/react@1.0.0", "", { "dependencies": { "@babel/runtime": "^7.28.4", "@base-ui/utils": "0.2.3", "@floating-ui/react-dom": "^2.1.6", "@floating-ui/utils": "^0.2.10", "reselect": "^5.1.1", "tabbable": "^6.3.0", "use-sync-external-store": "^1.6.0" }, "peerDependencies": { "@types/react": "^17 || ^18 || ^19", "react": "^17 || ^18 || ^19", "react-dom": "^17 || ^18 || ^19" }, "optionalPeers": ["@types/react"] }, "sha512-4USBWz++DUSLTuIYpbYkSgy1F9ZmNG9S/lXvlUN6qMK0P0RlW+6eQmDUB4DgZ7HVvtXl4pvi4z5J2fv6Z3+9hg=="],5188 "@lobehub/ui/@base-ui/react": ["@base-ui/react@1.0.0", "", { "dependencies": { "@babel/runtime": "^7.28.4", "@base-ui/utils": "0.2.3", "@floating-ui/react-dom": "^2.1.6", "@floating-ui/utils": "^0.2.10", "reselect": "^5.1.1", "tabbable": "^6.3.0", "use-sync-external-store": "^1.6.0" }, "peerDependencies": { "@types/react": "^17 || ^18 || ^19", "react": "^17 || ^18 || ^19", "react-dom": "^17 || ^18 || ^19" }, "optionalPeers": ["@types/react"] }, "sha512-4USBWz++DUSLTuIYpbYkSgy1F9ZmNG9S/lXvlUN6qMK0P0RlW+6eQmDUB4DgZ7HVvtXl4pvi4z5J2fv6Z3+9hg=="],
5169 5189
5170 "@lobehub/ui/@radix-ui/react-slot": ["@radix-ui/react-slot@1.2.4", "", { "dependencies": { "@radix-ui/react-compose-refs": "1.1.2" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-Jl+bCv8HxKnlTLVrcDE8zTMJ09R9/ukw4qBs/oZClOfoQk/cOTbDn+NceXfV7j09YPVQUryJPHurafcSg6EVKA=="],5190 "@lobehub/ui/@radix-ui/react-slot": ["@radix-ui/react-slot@1.2.4", "", { "dependencies": { "@radix-ui/react-compose-refs": "1.1.2" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-Jl+bCv8HxKnlTLVrcDE8zTMJ09R9/ukw4qBs/oZClOfoQk/cOTbDn+NceXfV7j09YPVQUryJPHurafcSg6EVKA=="],
5171 5191
5192 "@lobehub/ui/es-toolkit": ["es-toolkit@1.45.1", "", {}, "sha512-/jhoOj/Fx+A+IIyDNOvO3TItGmlMKhtX8ISAHKE90c4b/k1tqaqEZ+uUqfpU8DMnW5cgNJv606zS55jGvza0Xw=="],
5193
5172 "@lobehub/ui/immer": ["immer@11.1.4", "", {}, "sha512-XREFCPo6ksxVzP4E0ekD5aMdf8WMwmdNaz6vuvxgI40UaEiu6q3p8X52aU6GdyvLY3XXX/8R7JOTXStz/nBbRw=="],5194 "@lobehub/ui/immer": ["immer@11.1.4", "", {}, "sha512-XREFCPo6ksxVzP4E0ekD5aMdf8WMwmdNaz6vuvxgI40UaEiu6q3p8X52aU6GdyvLY3XXX/8R7JOTXStz/nBbRw=="],
5173 5195
5174 "@lobehub/ui/marked": ["marked@17.0.6", "", { "bin": { "marked": "bin/marked.js" } }, "sha512-gB0gkNafnonOw0obSTEGZTT86IuhILt2Wfx0mWH/1Au83kybTayroZ/V6nS25mN7u8ASy+5fMhgB3XPNrOZdmA=="],5196 "@lobehub/ui/marked": ["marked@17.0.6", "", { "bin": { "marked": "bin/marked.js" } }, "sha512-gB0gkNafnonOw0obSTEGZTT86IuhILt2Wfx0mWH/1Au83kybTayroZ/V6nS25mN7u8ASy+5fMhgB3XPNrOZdmA=="],
@@ -5297,6 +5319,8 @@ · 122 unmodified lines
5297 5319
5298 "@react-grab/cli/commander": ["commander@14.0.3", "", {}, "sha512-H+y0Jo/T1RZ9qPP4Eh1pkcQcLRglraJaSLoyOtHxu6AapkjWVCy2Sit1QQ4x3Dng8qDlSsZEet7g5Pq06MvTgw=="],5320 "@react-grab/cli/commander": ["commander@14.0.3", "", {}, "sha512-H+y0Jo/T1RZ9qPP4Eh1pkcQcLRglraJaSLoyOtHxu6AapkjWVCy2Sit1QQ4x3Dng8qDlSsZEet7g5Pq06MvTgw=="],
5299 5321
5322 "@react-grab/cli/ora": ["ora@8.2.0", "", { "dependencies": { "chalk": "^5.3.0", "cli-cursor": "^5.0.0", "cli-spinners": "^2.9.2", "is-interactive": "^2.0.0", "is-unicode-supported": "^2.0.0", "log-symbols": "^6.0.0", "stdin-discarder": "^0.2.2", "string-width": "^7.2.0", "strip-ansi": "^7.1.0" } }, "sha512-weP+BZ8MVNnlCm8c0Qdc1WSWq4Qn7I+9CJGm7Qali6g44e/PUzbjNqJX5NJ9ljlNMosfJvg1fKEGILklK9cwnw=="],
5323
5300 "@reduxjs/toolkit/immer": ["immer@11.1.4", "", {}, "sha512-XREFCPo6ksxVzP4E0ekD5aMdf8WMwmdNaz6vuvxgI40UaEiu6q3p8X52aU6GdyvLY3XXX/8R7JOTXStz/nBbRw=="],5324 "@reduxjs/toolkit/immer": ["immer@11.1.4", "", {}, "sha512-XREFCPo6ksxVzP4E0ekD5aMdf8WMwmdNaz6vuvxgI40UaEiu6q3p8X52aU6GdyvLY3XXX/8R7JOTXStz/nBbRw=="],
5301 5325
5302 "@rollup/pluginutils/estree-walker": ["estree-walker@2.0.2", "", {}, "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w=="],5326 "@rollup/pluginutils/estree-walker": ["estree-walker@2.0.2", "", {}, "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w=="],
@@ -5471,6 +5495,8 @@ · 168 unmodified lines
5471 5495
5472 "import-fresh/resolve-from": ["resolve-from@4.0.0", "", {}, "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g=="],5496 "import-fresh/resolve-from": ["resolve-from@4.0.0", "", {}, "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g=="],
5473 5497
5498 "ink/es-toolkit": ["es-toolkit@1.45.1", "", {}, "sha512-/jhoOj/Fx+A+IIyDNOvO3TItGmlMKhtX8ISAHKE90c4b/k1tqaqEZ+uUqfpU8DMnW5cgNJv606zS55jGvza0Xw=="],
5499
5474 "ink/signal-exit": ["signal-exit@3.0.7", "", {}, "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ=="],5500 "ink/signal-exit": ["signal-exit@3.0.7", "", {}, "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ=="],
5475 5501
5476 "ink/type-fest": ["type-fest@5.5.0", "", { "dependencies": { "tagged-tag": "^1.0.0" } }, "sha512-PlBfpQwiUvGViBNX84Yxwjsdhd1TUlXr6zjX7eoirtCPIr08NAmxwa+fcYBTeRQxHo9YC9wwF3m9i700sHma8g=="],5502 "ink/type-fest": ["type-fest@5.5.0", "", { "dependencies": { "tagged-tag": "^1.0.0" } }, "sha512-PlBfpQwiUvGViBNX84Yxwjsdhd1TUlXr6zjX7eoirtCPIr08NAmxwa+fcYBTeRQxHo9YC9wwF3m9i700sHma8g=="],
@@ -5483,8 +5509,6 @@ · 6 unmodified lines
5483 5509
5484 "libsql/detect-libc": ["detect-libc@2.0.2", "", {}, "sha512-UX6sGumvvqSaXgdKGUsgZWqcUyIXZ/vZTrlRT/iobiKhGL0zL4d3osHj3uqllWJK+i+sixDS/3COVEOFbupFyw=="],5510 "libsql/detect-libc": ["detect-libc@2.0.2", "", {}, "sha512-UX6sGumvvqSaXgdKGUsgZWqcUyIXZ/vZTrlRT/iobiKhGL0zL4d3osHj3uqllWJK+i+sixDS/3COVEOFbupFyw=="],
5485 5511
5486 "log-symbols/is-unicode-supported": ["is-unicode-supported@1.3.0", "", {}, "sha512-43r2mRvz+8JRIKnWJ+3j8JtjRKZ6GmjzfaE/qiBJnikNnYv/6bagRJ1kUhNk8R5EX/GkobD+r+sfxCPJsiKBLQ=="],
5487
5488 "matcher/escape-string-regexp": ["escape-string-regexp@4.0.0", "", {}, "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA=="],5512 "matcher/escape-string-regexp": ["escape-string-regexp@4.0.0", "", {}, "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA=="],
5489 5513
5490 "mdast-util-find-and-replace/escape-string-regexp": ["escape-string-regexp@5.0.0", "", {}, "sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw=="],5514 "mdast-util-find-and-replace/escape-string-regexp": ["escape-string-regexp@5.0.0", "", {}, "sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw=="],
@@ -5513,9 +5537,7 @@ · 22 unmodified lines
5513 5537
5514 "ora/cli-cursor": ["cli-cursor@5.0.0", "", { "dependencies": { "restore-cursor": "^5.0.0" } }, "sha512-aCj4O5wKyszjMmDT4tZj93kxyydN/K5zPWSCe6/0AV/AA1pqe5ZBIw0a2ZfPQV7lL5/yb5HsUreJ6UFAF1tEQw=="],5538 "ora/cli-cursor": ["cli-cursor@5.0.0", "", { "dependencies": { "restore-cursor": "^5.0.0" } }, "sha512-aCj4O5wKyszjMmDT4tZj93kxyydN/K5zPWSCe6/0AV/AA1pqe5ZBIw0a2ZfPQV7lL5/yb5HsUreJ6UFAF1tEQw=="],
5515 5539
5516 "ora/string-width": ["string-width@7.2.0", "", { "dependencies": { "emoji-regex": "^10.3.0", "get-east-asian-width": "^1.0.0", "strip-ansi": "^7.1.0" } }, "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ=="],5540 "ora/cli-spinners": ["cli-spinners@3.4.0", "", {}, "sha512-bXfOC4QcT1tKXGorxL3wbJm6XJPDqEnij2gQ2m7ESQuE+/z9YFIWnl/5RpTiKWbMq3EVKR4fRLJGn6DVfu0mpw=="],
5517
5518 "ora/strip-ansi": ["strip-ansi@7.2.0", "", { "dependencies": { "ansi-regex": "^6.2.2" } }, "sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w=="],
5519 5541
5520 "p-locate/p-limit": ["p-limit@2.3.0", "", { "dependencies": { "p-try": "^2.0.0" } }, "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w=="],5542 "p-locate/p-limit": ["p-limit@2.3.0", "", { "dependencies": { "p-try": "^2.0.0" } }, "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w=="],
5521 5543
@@ -5555,6 +5577,8 @@ · 33 unmodified lines
5555 5577
5556 "readdirp/picomatch": ["picomatch@2.3.2", "", {}, "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA=="],5578 "readdirp/picomatch": ["picomatch@2.3.2", "", {}, "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA=="],
5557 5579
5580 "recharts/es-toolkit": ["es-toolkit@1.45.1", "", {}, "sha512-/jhoOj/Fx+A+IIyDNOvO3TItGmlMKhtX8ISAHKE90c4b/k1tqaqEZ+uUqfpU8DMnW5cgNJv606zS55jGvza0Xw=="],
5581
5558 "restore-cursor/signal-exit": ["signal-exit@3.0.7", "", {}, "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ=="],5582 "restore-cursor/signal-exit": ["signal-exit@3.0.7", "", {}, "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ=="],
5559 5583
5560 "rimraf/glob": ["glob@7.2.3", "", { "dependencies": { "fs.realpath": "^1.0.0", "inflight": "^1.0.4", "inherits": "2", "minimatch": "^3.1.1", "once": "^1.3.0", "path-is-absolute": "^1.0.0" } }, "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q=="],5584 "rimraf/glob": ["glob@7.2.3", "", { "dependencies": { "fs.realpath": "^1.0.0", "inflight": "^1.0.4", "inherits": "2", "minimatch": "^3.1.1", "once": "^1.3.0", "path-is-absolute": "^1.0.0" } }, "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q=="],
@@ -5773,6 +5797,40 @@ · 212 unmodified lines
5773 5797
5774 "@esbuild-kit/core-utils/esbuild/@esbuild/win32-x64": ["@esbuild/win32-x64@0.18.20", "", { "os": "win32", "cpu": "x64" }, "sha512-kTdfRcSiDfQca/y9QIkng02avJ+NCaQvrMejlsB3RRv5sE9rRoeBPISaZpKxHELzRxZyLvNts1P27W3wV+8geQ=="],5798 "@esbuild-kit/core-utils/esbuild/@esbuild/win32-x64": ["@esbuild/win32-x64@0.18.20", "", { "os": "win32", "cpu": "x64" }, "sha512-kTdfRcSiDfQca/y9QIkng02avJ+NCaQvrMejlsB3RRv5sE9rRoeBPISaZpKxHELzRxZyLvNts1P27W3wV+8geQ=="],
5775 5799
5800 "@executor-js/mcporter/rolldown/@oxc-project/types": ["@oxc-project/types@0.130.0", "", {}, "sha512-ibD2usx9JRu7f5pu2tMKMI4cpA4NgXJQoYRP4pQ7Pxmn1l6k/53qWtQWZayhYy3X4QZkt90Ot+mJEaeXouio6Q=="],
5801
5802 "@executor-js/mcporter/rolldown/@rolldown/binding-android-arm64": ["@rolldown/binding-android-arm64@1.0.1", "", { "os": "android", "cpu": "arm64" }, "sha512-fJI3I0r3C3Oj/zdBCpaCmBRZYf07xpaq4yCfDDoSFm+beWNzbIl26puW8RraUdugoJw/95zerNOn6jasAhzSmg=="],
5803
5804 "@executor-js/mcporter/rolldown/@rolldown/binding-darwin-arm64": ["@rolldown/binding-darwin-arm64@1.0.1", "", { "os": "darwin", "cpu": "arm64" }, "sha512-cKnAhWEsV7TPcA/5EAteDp6KcJZBQ2G+BqE7zayMMi7kMvwRsbv7WT9aOnn0WNl4SKEIf43vjS31iUPu80nzXg=="],
5805
5806 "@executor-js/mcporter/rolldown/@rolldown/binding-darwin-x64": ["@rolldown/binding-darwin-x64@1.0.1", "", { "os": "darwin", "cpu": "x64" }, "sha512-YKrVwQjIRBPo+5G/u03wGjbdy4q7pyzCe93DK9VJ7zkVmeg8LJ7GbgsiHWdR4xSoe4CAXRD7Bcjgbtr64bkXNg=="],
5807
5808 "@executor-js/mcporter/rolldown/@rolldown/binding-freebsd-x64": ["@rolldown/binding-freebsd-x64@1.0.1", "", { "os": "freebsd", "cpu": "x64" }, "sha512-z/oBsREo46SsFqBwYtFe0kpJeBijAT48O/WXLI4suiCLBkr03RTtTJMCzSdDd2znlh8VJizL09XVkQgk8IZonw=="],
5809
5810 "@executor-js/mcporter/rolldown/@rolldown/binding-linux-arm-gnueabihf": ["@rolldown/binding-linux-arm-gnueabihf@1.0.1", "", { "os": "linux", "cpu": "arm" }, "sha512-ik8q7GM11zxvYxFc2PeDcT6TBvhCQMaUxfph/M5l9sKuTs/Sjg3L+Byw0F7w0ZVLBZmx30P+gG0ECzzN+MFcmQ=="],
5811
5812 "@executor-js/mcporter/rolldown/@rolldown/binding-linux-arm64-gnu": ["@rolldown/binding-linux-arm64-gnu@1.0.1", "", { "os": "linux", "cpu": "arm64" }, "sha512-QoSx2EkyrrdZ6kcyE8stqZ62t0Yra8Fs5ia9lOxJrh6TMQJK7gQKmscdTHf7pOXKREKrVwOtJcQG3qVSfc866A=="],
5813
5814 "@executor-js/mcporter/rolldown/@rolldown/binding-linux-arm64-musl": ["@rolldown/binding-linux-arm64-musl@1.0.1", "", { "os": "linux", "cpu": "arm64" }, "sha512-uwNwFpwKeNiZawfAWBgg0VIztPTV3ihhh1vV334h9ivnNLorxnQMU6Fz8wG1Zb4Qh9LC1/MkcyT3YlDXG3Rsgg=="],
5815
5816 "@executor-js/mcporter/rolldown/@rolldown/binding-linux-ppc64-gnu": ["@rolldown/binding-linux-ppc64-gnu@1.0.1", "", { "os": "linux", "cpu": "ppc64" }, "sha512-zY1bul7OWr7DFBiJ++wofXvnr8B45ce3QsQUhKrIhXsygAh7bTkwyeM1bi1a2g5C/yC/N8TZyGDEoMfm/l9mpg=="],
5817
5818 "@executor-js/mcporter/rolldown/@rolldown/binding-linux-s390x-gnu": ["@rolldown/binding-linux-s390x-gnu@1.0.1", "", { "os": "linux", "cpu": "s390x" }, "sha512-0frlsT/f4Ft6I7SMESTKnF3cZsdicQn1dCMkF/jT9wDLE+gGoiQfv1nmT9e+s7s/fekvvy6tZM2jHvI2tkbJDQ=="],
5819
5820 "@executor-js/mcporter/rolldown/@rolldown/binding-linux-x64-gnu": ["@rolldown/binding-linux-x64-gnu@1.0.1", "", { "os": "linux", "cpu": "x64" }, "sha512-XABVmGp9Tg0WspTVvwduTc4fpqy6JnAUrSQe6OuyqD/03nI7r0O9OWUkMIwFrjKAIqolvqoA4ZrJppgwE0Gxmw=="],
5821
5822 "@executor-js/mcporter/rolldown/@rolldown/binding-linux-x64-musl": ["@rolldown/binding-linux-x64-musl@1.0.1", "", { "os": "linux", "cpu": "x64" }, "sha512-bV4fzswuzVcKD90o/VM6QqKxnxlDq0g2BISDLNVmxrnhpv1DDbyPhCIjYfvzYLV+MvkKKnQt2Q6AO86SEBULUQ=="],
5823
5824 "@executor-js/mcporter/rolldown/@rolldown/binding-openharmony-arm64": ["@rolldown/binding-openharmony-arm64@1.0.1", "", { "os": "none", "cpu": "arm64" }, "sha512-/Mh0Zhq3OP7fVs0kcQHZP6lZEthMGTaSf8UBQYSFEZDWGXXlEC+nJ6EqenaK2t4LBXMe3A+K/G2BVXXdtOr4PQ=="],
5825
5826 "@executor-js/mcporter/rolldown/@rolldown/binding-wasm32-wasi": ["@rolldown/binding-wasm32-wasi@1.0.1", "", { "dependencies": { "@emnapi/core": "1.10.0", "@emnapi/runtime": "1.10.0", "@napi-rs/wasm-runtime": "^1.1.4" }, "cpu": "none" }, "sha512-+1xc9X45l8ufsBAm6Gjvx2qDRIY9lTVt0cgWNcJ+1gdhXvkbxePA60yRTwSTuXL09CMhyJmjpV7E3NoyxbqFQQ=="],
5827
5828 "@executor-js/mcporter/rolldown/@rolldown/binding-win32-arm64-msvc": ["@rolldown/binding-win32-arm64-msvc@1.0.1", "", { "os": "win32", "cpu": "arm64" }, "sha512-1D+UqZdfnuR+Jy1GgMJwi85bD40H21uNmOPRWQhw4oRSuolZ/B5rixZ45DK2KXOTCvmVCecauWgEhbw8bI7tOw=="],
5829
5830 "@executor-js/mcporter/rolldown/@rolldown/binding-win32-x64-msvc": ["@rolldown/binding-win32-x64-msvc@1.0.1", "", { "os": "win32", "cpu": "x64" }, "sha512-INAycaWuhlOK3wk4mRHGsdgwYWmd9cChdPdE9bwWmy6rn9VqVNYNFGhOdXrofXUxwHIncSiPNb8tNm8knDVIeQ=="],
5831
5832 "@executor-js/mcporter/rolldown/@rolldown/pluginutils": ["@rolldown/pluginutils@1.0.1", "", {}, "sha512-2j9bGt5Jh8hj+vPtgzPtl72j0yRxHAyumoo6TNfAjsLB04UtpSvPbPcDcBMxz7n+9CYB0c1GxQFxYRg2jimqGw=="],
5833
5776 "@inquirer/core/wrap-ansi/ansi-styles": ["ansi-styles@4.3.0", "", { "dependencies": { "color-convert": "^2.0.1" } }, "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg=="],5834 "@inquirer/core/wrap-ansi/ansi-styles": ["ansi-styles@4.3.0", "", { "dependencies": { "color-convert": "^2.0.1" } }, "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg=="],
5777 5835
5778 "@inquirer/core/wrap-ansi/string-width": ["string-width@4.2.3", "", { "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", "strip-ansi": "^6.0.1" } }, "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g=="],5836 "@inquirer/core/wrap-ansi/string-width": ["string-width@4.2.3", "", { "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", "strip-ansi": "^6.0.1" } }, "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g=="],
@@ -5809,6 +5867,16 @@ · 30 unmodified lines
5809 5867
5810 "@pierre/diffs/shiki/@shikijs/types": ["@shikijs/types@3.23.0", "", { "dependencies": { "@shikijs/vscode-textmate": "^10.0.2", "@types/hast": "^3.0.4" } }, "sha512-3JZ5HXOZfYjsYSk0yPwBrkupyYSLpAE26Qc0HLghhZNGTZg/SKxXIIgoxOpmmeQP0RRSDJTk1/vPfw9tbw+jSQ=="],5868 "@pierre/diffs/shiki/@shikijs/types": ["@shikijs/types@3.23.0", "", { "dependencies": { "@shikijs/vscode-textmate": "^10.0.2", "@types/hast": "^3.0.4" } }, "sha512-3JZ5HXOZfYjsYSk0yPwBrkupyYSLpAE26Qc0HLghhZNGTZg/SKxXIIgoxOpmmeQP0RRSDJTk1/vPfw9tbw+jSQ=="],
5811 5869
5870 "@react-grab/cli/ora/cli-cursor": ["cli-cursor@5.0.0", "", { "dependencies": { "restore-cursor": "^5.0.0" } }, "sha512-aCj4O5wKyszjMmDT4tZj93kxyydN/K5zPWSCe6/0AV/AA1pqe5ZBIw0a2ZfPQV7lL5/yb5HsUreJ6UFAF1tEQw=="],
5871
5872 "@react-grab/cli/ora/log-symbols": ["log-symbols@6.0.0", "", { "dependencies": { "chalk": "^5.3.0", "is-unicode-supported": "^1.3.0" } }, "sha512-i24m8rpwhmPIS4zscNzK6MSEhk0DUWa/8iYQWxhffV8jkI4Phvs3F+quL5xvS0gdQR0FyTCMMH33Y78dDTzzIw=="],
5873
5874 "@react-grab/cli/ora/stdin-discarder": ["stdin-discarder@0.2.2", "", {}, "sha512-UhDfHmA92YAlNnCfhmq0VeNL5bDbiZGg7sZ2IvPsXubGkiNa9EC+tUTsjBRsYUAz87btI6/1wf4XoVvQ3uRnmQ=="],
5875
5876 "@react-grab/cli/ora/string-width": ["string-width@7.2.0", "", { "dependencies": { "emoji-regex": "^10.3.0", "get-east-asian-width": "^1.0.0", "strip-ansi": "^7.1.0" } }, "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ=="],
5877
5878 "@react-grab/cli/ora/strip-ansi": ["strip-ansi@7.2.0", "", { "dependencies": { "ansi-regex": "^6.2.2" } }, "sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w=="],
5879
5812 "@types/better-sqlite3/@types/node/undici-types": ["undici-types@7.19.2", "", {}, "sha512-qYVnV5OEm2AW8cJMCpdV20CDyaN3g0AjDlOGf1OW4iaDEx8MwdtChUp4zu4H0VP3nDRF/8RKWH+IPp9uW0YGZg=="],5880 "@types/better-sqlite3/@types/node/undici-types": ["undici-types@7.19.2", "", {}, "sha512-qYVnV5OEm2AW8cJMCpdV20CDyaN3g0AjDlOGf1OW4iaDEx8MwdtChUp4zu4H0VP3nDRF/8RKWH+IPp9uW0YGZg=="],
5813 5881
5814 "@types/cacheable-request/@types/node/undici-types": ["undici-types@7.19.2", "", {}, "sha512-qYVnV5OEm2AW8cJMCpdV20CDyaN3g0AjDlOGf1OW4iaDEx8MwdtChUp4zu4H0VP3nDRF/8RKWH+IPp9uW0YGZg=="],5882 "@types/cacheable-request/@types/node/undici-types": ["undici-types@7.19.2", "", {}, "sha512-qYVnV5OEm2AW8cJMCpdV20CDyaN3g0AjDlOGf1OW4iaDEx8MwdtChUp4zu4H0VP3nDRF/8RKWH+IPp9uW0YGZg=="],
@@ -6043,10 +6111,6 @@ · 228 unmodified lines
6043 6111
6044 "ora/cli-cursor/restore-cursor": ["restore-cursor@5.1.0", "", { "dependencies": { "onetime": "^7.0.0", "signal-exit": "^4.1.0" } }, "sha512-oMA2dcrw6u0YfxJQXm342bFKX/E4sG9rbTzO9ptUcR/e8A33cHuvStiYOwH7fszkZlZ1z/ta9AAoPk2F4qIOHA=="],6112 "ora/cli-cursor/restore-cursor": ["restore-cursor@5.1.0", "", { "dependencies": { "onetime": "^7.0.0", "signal-exit": "^4.1.0" } }, "sha512-oMA2dcrw6u0YfxJQXm342bFKX/E4sG9rbTzO9ptUcR/e8A33cHuvStiYOwH7fszkZlZ1z/ta9AAoPk2F4qIOHA=="],
6045 6113
6046 "ora/string-width/emoji-regex": ["emoji-regex@10.6.0", "", {}, "sha512-toUI84YS5YmxW219erniWD0CIVOo46xGKColeNQRgOzDorgBi1v4D71/OFzgD9GO2UGKIv1C3Sp8DAn0+j5w7A=="],
6047
6048 "ora/strip-ansi/ansi-regex": ["ansi-regex@6.2.2", "", {}, "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg=="],
6049
6050 "posthog-js/@opentelemetry/exporter-logs-otlp-http/@opentelemetry/core": ["@opentelemetry/core@2.2.0", "", { "dependencies": { "@opentelemetry/semantic-conventions": "^1.29.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.0.0 <1.10.0" } }, "sha512-FuabnnUm8LflnieVxs6eP7Z383hgQU4W1e3KJS6aOG3RxWxcHyBxH8fDMHNgu/gFx/M2jvTOW/4/PHhLz6bjWw=="],6114 "posthog-js/@opentelemetry/exporter-logs-otlp-http/@opentelemetry/core": ["@opentelemetry/core@2.2.0", "", { "dependencies": { "@opentelemetry/semantic-conventions": "^1.29.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.0.0 <1.10.0" } }, "sha512-FuabnnUm8LflnieVxs6eP7Z383hgQU4W1e3KJS6aOG3RxWxcHyBxH8fDMHNgu/gFx/M2jvTOW/4/PHhLz6bjWw=="],
6051 6115
6052 "posthog-js/@opentelemetry/exporter-logs-otlp-http/@opentelemetry/otlp-exporter-base": ["@opentelemetry/otlp-exporter-base@0.208.0", "", { "dependencies": { "@opentelemetry/core": "2.2.0", "@opentelemetry/otlp-transformer": "0.208.0" }, "peerDependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-gMd39gIfVb2OgxldxUtOwGJYSH8P1kVFFlJLuut32L6KgUC4gl1dMhn+YC2mGn0bDOiQYSk/uHOdSjuKp58vvA=="],6116 "posthog-js/@opentelemetry/exporter-logs-otlp-http/@opentelemetry/otlp-exporter-base": ["@opentelemetry/otlp-exporter-base@0.208.0", "", { "dependencies": { "@opentelemetry/core": "2.2.0", "@opentelemetry/otlp-transformer": "0.208.0" }, "peerDependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-gMd39gIfVb2OgxldxUtOwGJYSH8P1kVFFlJLuut32L6KgUC4gl1dMhn+YC2mGn0bDOiQYSk/uHOdSjuKp58vvA=="],
@@ -6237,6 +6301,10 @@ · 184 unmodified lines
6237 6301
6238 "@electron/universal/minimatch/brace-expansion/balanced-match": ["balanced-match@1.0.2", "", {}, "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="],6302 "@electron/universal/minimatch/brace-expansion/balanced-match": ["balanced-match@1.0.2", "", {}, "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="],
6239 6303
6304 "@executor-js/mcporter/rolldown/@rolldown/binding-wasm32-wasi/@emnapi/core": ["@emnapi/core@1.10.0", "", { "dependencies": { "@emnapi/wasi-threads": "1.2.1", "tslib": "^2.4.0" } }, "sha512-yq6OkJ4p82CAfPl0u9mQebQHKPJkY7WrIuk205cTYnYe+k2Z8YBh11FrbRG/H6ihirqcacOgl2BIO8oyMQLeXw=="],
6305
6306 "@executor-js/mcporter/rolldown/@rolldown/binding-wasm32-wasi/@emnapi/runtime": ["@emnapi/runtime@1.10.0", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-ewvYlk86xUoGI0zQRNq/mC+16R1QeDlKQy21Ki3oSYXNgLb45GV1P6A0M+/s6nyCuNDqe5VpaY84BzXGwVbwFA=="],
6307
6240 "@inquirer/core/wrap-ansi/string-width/is-fullwidth-code-point": ["is-fullwidth-code-point@3.0.0", "", {}, "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg=="],6308 "@inquirer/core/wrap-ansi/string-width/is-fullwidth-code-point": ["is-fullwidth-code-point@3.0.0", "", {}, "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg=="],
6241 6309
6242 "@libsql/kysely-libsql/@libsql/client/@libsql/hrana-client/node-fetch": ["node-fetch@3.3.2", "", { "dependencies": { "data-uri-to-buffer": "^4.0.0", "fetch-blob": "^3.1.4", "formdata-polyfill": "^4.0.10" } }, "sha512-dRB78srN/l6gqWulah9SrxeYnxeddIG30+GOqK/9OlLVyLg3HPnr6SqOWTWOXKRwC2eGYCkZ59NNuSgvSrpgOA=="],6310 "@libsql/kysely-libsql/@libsql/client/@libsql/hrana-client/node-fetch": ["node-fetch@3.3.2", "", { "dependencies": { "data-uri-to-buffer": "^4.0.0", "fetch-blob": "^3.1.4", "formdata-polyfill": "^4.0.10" } }, "sha512-dRB78srN/l6gqWulah9SrxeYnxeddIG30+GOqK/9OlLVyLg3HPnr6SqOWTWOXKRwC2eGYCkZ59NNuSgvSrpgOA=="],
@@ -6257,6 +6325,14 @@ · 14 unmodified lines
6257 6325
6258 "@libsql/kysely-libsql/@libsql/client/libsql/detect-libc": ["detect-libc@2.0.2", "", {}, "sha512-UX6sGumvvqSaXgdKGUsgZWqcUyIXZ/vZTrlRT/iobiKhGL0zL4d3osHj3uqllWJK+i+sixDS/3COVEOFbupFyw=="],6326 "@libsql/kysely-libsql/@libsql/client/libsql/detect-libc": ["detect-libc@2.0.2", "", {}, "sha512-UX6sGumvvqSaXgdKGUsgZWqcUyIXZ/vZTrlRT/iobiKhGL0zL4d3osHj3uqllWJK+i+sixDS/3COVEOFbupFyw=="],
6259 6327
6328 "@react-grab/cli/ora/cli-cursor/restore-cursor": ["restore-cursor@5.1.0", "", { "dependencies": { "onetime": "^7.0.0", "signal-exit": "^4.1.0" } }, "sha512-oMA2dcrw6u0YfxJQXm342bFKX/E4sG9rbTzO9ptUcR/e8A33cHuvStiYOwH7fszkZlZ1z/ta9AAoPk2F4qIOHA=="],
6329
6330 "@react-grab/cli/ora/log-symbols/is-unicode-supported": ["is-unicode-supported@1.3.0", "", {}, "sha512-43r2mRvz+8JRIKnWJ+3j8JtjRKZ6GmjzfaE/qiBJnikNnYv/6bagRJ1kUhNk8R5EX/GkobD+r+sfxCPJsiKBLQ=="],
6331
6332 "@react-grab/cli/ora/string-width/emoji-regex": ["emoji-regex@10.6.0", "", {}, "sha512-toUI84YS5YmxW219erniWD0CIVOo46xGKColeNQRgOzDorgBi1v4D71/OFzgD9GO2UGKIv1C3Sp8DAn0+j5w7A=="],
6333
6334 "@react-grab/cli/ora/strip-ansi/ansi-regex": ["ansi-regex@6.2.2", "", {}, "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg=="],
6335
6260 "agents/yargs/cliui/strip-ansi": ["strip-ansi@7.2.0", "", { "dependencies": { "ansi-regex": "^6.2.2" } }, "sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w=="],6336 "agents/yargs/cliui/strip-ansi": ["strip-ansi@7.2.0", "", { "dependencies": { "ansi-regex": "^6.2.2" } }, "sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w=="],
6261 6337
6262 "agents/yargs/string-width/emoji-regex": ["emoji-regex@10.6.0", "", {}, "sha512-toUI84YS5YmxW219erniWD0CIVOo46xGKColeNQRgOzDorgBi1v4D71/OFzgD9GO2UGKIv1C3Sp8DAn0+j5w7A=="],6338 "agents/yargs/string-width/emoji-regex": ["emoji-regex@10.6.0", "", {}, "sha512-toUI84YS5YmxW219erniWD0CIVOo46xGKColeNQRgOzDorgBi1v4D71/OFzgD9GO2UGKIv1C3Sp8DAn0+j5w7A=="],
@@ -6293,6 +6369,8 @@ · 30 unmodified lines
6293 6369
6294 "rimraf/glob/minimatch/brace-expansion": ["brace-expansion@1.1.14", "", { "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" } }, "sha512-MWPGfDxnyzKU7rNOW9SP/c50vi3xrmrua/+6hfPbCS2ABNWfx24vPidzvC7krjU/RTo235sV776ymlsMtGKj8g=="],6370 "rimraf/glob/minimatch/brace-expansion": ["brace-expansion@1.1.14", "", { "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" } }, "sha512-MWPGfDxnyzKU7rNOW9SP/c50vi3xrmrua/+6hfPbCS2ABNWfx24vPidzvC7krjU/RTo235sV776ymlsMtGKj8g=="],
6295 6371
6372 "@react-grab/cli/ora/cli-cursor/restore-cursor/onetime": ["onetime@7.0.0", "", { "dependencies": { "mimic-function": "^5.0.0" } }, "sha512-VXJjc87FScF88uafS3JllDgvAm+c/Slfz06lorj2uAY34rlUu0Nt+v8wreiImcrgAjjIHp1rXpTDlLOGw29WwQ=="],
6373
6296 "agents/yargs/cliui/strip-ansi/ansi-regex": ["ansi-regex@6.2.2", "", {}, "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg=="],6374 "agents/yargs/cliui/strip-ansi/ansi-regex": ["ansi-regex@6.2.2", "", {}, "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg=="],
6297 6375
6298 "agents/yargs/string-width/strip-ansi/ansi-regex": ["ansi-regex@6.2.2", "", {}, "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg=="],6376 "agents/yargs/string-width/strip-ansi/ansi-regex": ["ansi-regex@6.2.2", "", {}, "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg=="],
e2e/cloud/org-limit.test.ts+7−3
@@ -41,10 +41,14 @@ scenario( · 40 unmodified lines
41 41
42 const openCreateOrgModal = async (currentOrg: string) => {42 const openCreateOrgModal = async (currentOrg: string) => {
43 await page.getByRole("button", { name: /Test User/ }).click();43 await page.getByRole("button", { name: /Test User/ }).click();
44 // The org entry + "Create organization" are radix menu items; role44 // The org entry is a radix submenu trigger; its content loads the
45 // locators keep us off the look-alike trigger button beneath.45 // org list from the API, so wait for the sub-content to mount and
46 // click "Create organization" scoped INSIDE it — clicking during
47 // the loading re-render dismisses the whole menu.
46 await page.getByRole("menuitem", { name: currentOrg }).click();48 await page.getByRole("menuitem", { name: currentOrg }).click();
47 await page.getByRole("menuitem", { name: "Create organization" }).click();49 const subContent = page.locator('[data-slot="dropdown-menu-sub-content"]');
50 await subContent.waitFor({ state: "visible" });
51 await subContent.getByText("Create organization", { exact: true }).click();
48 await page.getByText("Add another organization").waitFor();52 await page.getByText("Add another organization").waitFor();
49 };53 };
50 54
e2e/cloud/org-switcher.test.ts+3−1
@@ -45,7 +45,9 @@ scenario( · 44 unmodified lines
45 await step('Open the org switcher and choose "Create organization"', async () => {45 await step('Open the org switcher and choose "Create organization"', async () => {
46 await page.getByRole("button", { name: /Test User/ }).click();46 await page.getByRole("button", { name: /Test User/ }).click();
47 await page.getByRole("menuitem", { name: ORG_1 }).click();47 await page.getByRole("menuitem", { name: ORG_1 }).click();
48 await page.getByRole("menuitem", { name: "Create organization" }).click();48 const subContent = page.locator('[data-slot="dropdown-menu-sub-content"]');
49 await subContent.waitFor({ state: "visible" });
50 await subContent.getByText("Create organization", { exact: true }).click();
49 await page.getByText("Add another organization").waitFor();51 await page.getByText("Add another organization").waitFor();
50 });52 });
51 53
e2e/package.json+2−0
@@ -13,6 +13,8 @@ · 12 unmodified lines
13 },13 },
14 "dependencies": {14 "dependencies": {
15 "@executor-js/api": "workspace:*",15 "@executor-js/api": "workspace:*",
16 "@executor-js/emulate": "^0.6.0",
17 "@executor-js/mcporter": "^0.11.4",
16 "@executor-js/plugin-graphql": "workspace:*",18 "@executor-js/plugin-graphql": "workspace:*",
17 "@executor-js/plugin-mcp": "workspace:*",19 "@executor-js/plugin-mcp": "workspace:*",
18 "@executor-js/plugin-openapi": "workspace:*",20 "@executor-js/plugin-openapi": "workspace:*",
e2e/setup/boot.ts+8−3
@@ -72,13 +72,18 @@ export const bootProcesses = ( · 71 unmodified lines
72 };72 };
73};73};
74 74
75export const waitForHttp = async (url: string, timeoutMs = 90_000): Promise<void> => {75export const waitForHttp = async (
76 const deadline = Date.now() + timeoutMs;76 url: string,
77 options: { readonly timeoutMs?: number; readonly expectRedirect?: boolean } = {},
78): Promise<void> => {
79 const deadline = Date.now() + (options.timeoutMs ?? 90_000);
77 let lastError: unknown;80 let lastError: unknown;
78 while (Date.now() < deadline) {81 while (Date.now() < deadline) {
79 try {82 try {
80 const response = await fetch(url, { redirect: "manual" });83 const response = await fetch(url, { redirect: "manual" });
81 if (response.status < 500) return;84 // During a cold vite compile /api/* falls back to the SPA's 200 HTML —
85 // expectRedirect waits for the real handler (302) instead.
86 if (options.expectRedirect ? response.status === 302 : response.status < 500) return;
82 lastError = new Error(`status ${response.status}`);87 lastError = new Error(`status ${response.status}`);
83 } catch (error) {88 } catch (error) {
84 lastError = error;89 lastError = error;
e2e/setup/cloud.globalsetup.ts+45−21
@@ -1,14 +1,25 @@
1// Boot the cloud target: the app's OWN dev stack (PGlite dev-db + vite dev)1// Boot the cloud target: WorkOS + Autumn EMULATORS (in this process, from the
2// with EXECUTOR_E2E_STUB=1 — the one flag that makes `vite dev` a logged-in,2// vendored emulate fork) plus the app's own dev stack (PGlite dev-db + vite
3// fully-stubbed instance (multi-user WorkOS stub, free-plan Autumn, no3// dev) pointed at them via WORKOS_API_URL / AUTUMN_API_URL. The app runs its
4// network). Set E2E_CLOUD_URL to attach to an already-running instance4// REAL auth/billing code — real SDKs, real sealed-session crypto, real JWKS —
5// instead (e.g. while iterating on a scenario).5// against emulated services. Set E2E_CLOUD_URL to attach to a running stack.
6import { rmSync } from "node:fs";6import { rmSync } from "node:fs";
7import { resolve } from "node:path";7import { resolve } from "node:path";
8import { fileURLToPath } from "node:url";8import { fileURLToPath } from "node:url";
9 9
10// Vendored fork import (same pattern as mcporter).
11import { createEmulator } from "@executor-js/emulate";
12
10import { bootProcesses, waitForHttp } from "./boot";13import { bootProcesses, waitForHttp } from "./boot";
11import { CLOUD_BASE_URL, CLOUD_DB_PORT, CLOUD_PORT } from "../targets/cloud";14import {
15 CLOUD_BASE_URL,
16 CLOUD_DB_PORT,
17 CLOUD_PORT,
18 WORKOS_EMULATOR_PORT,
19 AUTUMN_EMULATOR_PORT,
20 E2E_WORKOS_CLIENT_ID,
21 E2E_COOKIE_PASSWORD,
22} from "../targets/cloud";
12 23
13const cloudDir = fileURLToPath(new URL("../../apps/cloud/", import.meta.url));24const cloudDir = fileURLToPath(new URL("../../apps/cloud/", import.meta.url));
14 25
@@ -18,31 +29,36 @@ export default async function setup(): Promise<(() => Promise<void>) | void> { · 3 unmodified lines
18 return;29 return;
19 }30 }
20 31
21 // Fresh stub DB per suite run — hermetic, like the selfhost data dir. The32 // Fresh dev DB per suite run — hermetic, like the selfhost data dir. The
22 // WorkOS stub mints org ids from a per-process counter, so a persisted DB33 // WorkOS emulator mints org ids from a per-process counter, so a persisted
23 // from a previous invocation collides with the new boot's ids (identities34 // DB from a previous invocation collides with the new boot's ids (identities
24 // land in polluted orgs / org creation 500s).35 // land in polluted orgs / org creation 500s).
25 const dbPath = resolve(cloudDir, ".e2e-stub-db");36 const dbPath = resolve(cloudDir, ".e2e-stub-db");
26 rmSync(dbPath, { recursive: true, force: true });37 rmSync(dbPath, { recursive: true, force: true });
27 38
39 // MCP access tokens minted by the emulator's OAuth server must carry the
40 // app's client id as audience (what the resource server verifies).
41 process.env.EMULATE_WORKOS_AUDIENCE = E2E_WORKOS_CLIENT_ID;
42 const workos = await createEmulator({ service: "workos", port: WORKOS_EMULATOR_PORT });
43 const autumn = await createEmulator({ service: "autumn", port: AUTUMN_EMULATOR_PORT });
44
28 const env = {45 const env = {
29 EXECUTOR_E2E_STUB: "1",46 // Real client, emulated service.
30 // The harness boots loopback MCP/OAuth test servers and points the47 WORKOS_API_URL: workos.url,
31 // instance at them; the hosted SSRF guard would otherwise block outbound48 AUTUMN_API_URL: autumn.url,
32 // probes/dials to localhost. Hermetic stub instance only.49 WORKOS_API_KEY: "sk_test_emulate",
33 ALLOW_LOCAL_NETWORK: "true",50 WORKOS_CLIENT_ID: E2E_WORKOS_CLIENT_ID,
34 // Stub creds — never contacted; the stub layers replace the clients.51 WORKOS_COOKIE_PASSWORD: E2E_COOKIE_PASSWORD,
35 WORKOS_API_KEY: "sk_e2e_stub",52 AUTUMN_SECRET_KEY: "am_test_emulate",
36 WORKOS_CLIENT_ID: "client_e2e_stub",
37 WORKOS_COOKIE_PASSWORD: "e2e_cookie_password_0123456789abcdef0123456789abcdef",
38 AUTUMN_SECRET_KEY: "am_e2e_stub",
39 ENCRYPTION_KEY: "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef",53 ENCRYPTION_KEY: "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef",
40 DATABASE_URL: `postgresql://postgres:postgres@127.0.0.1:${CLOUD_DB_PORT}/postgres`,54 DATABASE_URL: `postgresql://postgres:postgres@127.0.0.1:${CLOUD_DB_PORT}/postgres`,
41 EXECUTOR_DIRECT_DATABASE_URL: "true",55 EXECUTOR_DIRECT_DATABASE_URL: "true",
42 CLOUDFLARE_INCLUDE_PROCESS_ENV: "true",56 CLOUDFLARE_INCLUDE_PROCESS_ENV: "true",
43 VITE_PUBLIC_SITE_URL: CLOUD_BASE_URL,57 VITE_PUBLIC_SITE_URL: CLOUD_BASE_URL,
44 MCP_AUTHKIT_DOMAIN: "https://example.com",58 // The AuthKit domain (MCP OAuth metadata + JWKS) is the emulator too.
59 MCP_AUTHKIT_DOMAIN: workos.url,
45 MCP_RESOURCE_ORIGIN: CLOUD_BASE_URL,60 MCP_RESOURCE_ORIGIN: CLOUD_BASE_URL,
61 ALLOW_LOCAL_NETWORK: "true",
46 // Throwaway PGlite on its own port + dir so it never fights `bun dev`.62 // Throwaway PGlite on its own port + dir so it never fights `bun dev`.
47 DEV_DB_PORT: String(CLOUD_DB_PORT),63 DEV_DB_PORT: String(CLOUD_DB_PORT),
48 DEV_DB_PATH: dbPath,64 DEV_DB_PATH: dbPath,
@@ -63,9 +79,17 @@ export default async function setup(): Promise<(() => Promise<void>) | void> { · 14 unmodified lines
63 79
64 try {80 try {
65 await waitForHttp(CLOUD_BASE_URL);81 await waitForHttp(CLOUD_BASE_URL);
82 // The API plane is ready when login actually redirects to AuthKit.
83 await waitForHttp(`${CLOUD_BASE_URL}/api/auth/login`, { expectRedirect: true });
66 } catch (error) {84 } catch (error) {
67 await procs.teardown();85 await procs.teardown();
86 await workos.close();
87 await autumn.close();
68 throw error;88 throw error;
69 }89 }
70 return procs.teardown;90 return async () => {
91 await procs.teardown();
92 await workos.close();
93 await autumn.close();
94 };
71}95}
e2e/src/surfaces/mcp.ts+1−1
@@ -8,7 +8,7 @@ import { join } from "node:path"; · 7 unmodified lines
8 8
9import { Effect } from "effect";9import { Effect } from "effect";
10 10
11import { createRuntime, type Runtime } from "../../../vendor/mcporter/dist/index.js";11import { createRuntime, type Runtime } from "@executor-js/mcporter";
12 12
13import type { Identity, Target } from "../target";13import type { Identity, Target } from "../target";
14 14
e2e/targets/cloud.ts+70−25
@@ -1,8 +1,9 @@
1// The cloud app as a target: the stubbed dev server (`vite dev` +1// The cloud app as a target: its real dev server with the real WorkOS and
2// EXECUTOR_E2E_STUB=1) — real SSR, real routes, real PGlite-backed DB, WorkOS2// Autumn SDKs pointed at emulators (WORKOS_API_URL / AUTUMN_API_URL — see
3// and Autumn stubbed in-memory. Isolation: the stub resolves the user FROM the3// setup/cloud.globalsetup.ts). Identities are minted through the REAL login
4// wos-session cookie value, so every identity is a fresh user (and org) on the4// flow: /api/auth/login → emulator hosted AuthKit (headless via login_hint) →
5// one shared instance — no resets. Boot lives in setup/cloud.globalsetup.ts.5// /api/auth/callback → genuine sealed-session cookie. Isolation: every
6// identity is a fresh user (and org) — no resets.
6import { randomUUID } from "node:crypto";7import { randomUUID } from "node:crypto";
7 8
8import { Effect } from "effect";9import { Effect } from "effect";
@@ -12,46 +13,90 @@ import type { Identity, Target } from "../src/target"; · 3 unmodified lines
12export const CLOUD_PORT = Number(process.env.E2E_CLOUD_PORT ?? 4798);13export const CLOUD_PORT = Number(process.env.E2E_CLOUD_PORT ?? 4798);
13export const CLOUD_DB_PORT = Number(process.env.E2E_CLOUD_DB_PORT ?? 5436);14export const CLOUD_DB_PORT = Number(process.env.E2E_CLOUD_DB_PORT ?? 5436);
14export const CLOUD_BASE_URL = process.env.E2E_CLOUD_URL ?? `http://127.0.0.1:${CLOUD_PORT}`;15export const CLOUD_BASE_URL = process.env.E2E_CLOUD_URL ?? `http://127.0.0.1:${CLOUD_PORT}`;
16export const WORKOS_EMULATOR_PORT = Number(process.env.E2E_WORKOS_EMULATOR_PORT ?? 4914);
17export const AUTUMN_EMULATOR_PORT = Number(process.env.E2E_AUTUMN_EMULATOR_PORT ?? 4915);
18export const E2E_WORKOS_CLIENT_ID = "client_e2e_emulate";
19export const E2E_COOKIE_PASSWORD = "e2e_cookie_password_0123456789abcdef0123456789abcdef";
15 20
16const freshId = () => randomUUID().slice(0, 8);21const cookiePair = (response: Response, name: string): string | undefined => {
22 for (const header of response.headers.getSetCookie?.() ?? []) {
23 if (header.startsWith(`${name}=`)) return header.split(";")[0];
24 }
25 return undefined;
26};
27
28/** The real product login, headless: login → hosted AuthKit → callback. */
29const signIn = async (email: string): Promise<string> => {
30 const login = await fetch(new URL("/api/auth/login", CLOUD_BASE_URL), { redirect: "manual" });
31 const stateCookie = cookiePair(login, "wos-login-state");
32 const authorizeUrl = new URL(login.headers.get("location") ?? "");
33 if (!stateCookie || !authorizeUrl.searchParams.get("state")) {
34 throw new Error(`cloud signIn: login did not redirect to AuthKit (${login.status})`);
35 }
36 // The emulator's hosted login signs in headlessly via login_hint (creating
37 // the user if new) and redirects back with a code.
38 authorizeUrl.searchParams.set("login_hint", email);
39 const consent = await fetch(authorizeUrl, { redirect: "manual" });
40 const callbackUrl = consent.headers.get("location");
41 if (consent.status !== 302 || !callbackUrl) {
42 throw new Error(`cloud signIn: AuthKit emulator did not redirect (${consent.status})`);
43 }
44 const callback = await fetch(callbackUrl, {
45 redirect: "manual",
46 headers: { cookie: stateCookie },
47 });
48 const session = cookiePair(callback, "wos-session");
49 if (!session) throw new Error(`cloud signIn: callback set no session (${callback.status})`);
50 return session; // "wos-session=<sealed>"
51};
17 52
18export const cloudTarget = (): Target => ({53export const cloudTarget = (): Target => ({
19 name: "cloud",54 name: "cloud",
20 baseUrl: CLOUD_BASE_URL,55 baseUrl: CLOUD_BASE_URL,
21 mcpUrl: `${CLOUD_BASE_URL}/mcp`,56 mcpUrl: `${CLOUD_BASE_URL}/mcp`,
22 capabilities: new Set(["api", "browser", "billing"]),57 capabilities: new Set(["api", "browser", "billing", "mcp-oauth"]),
23 newIdentity: ({ org = true } = {}) =>58 newIdentity: ({ org = true } = {}) =>
24 Effect.promise(async (): Promise<Identity> => {59 Effect.promise(async (): Promise<Identity> => {
25 const user = `user_${freshId()}`;60 const label = `user-${randomUUID().slice(0, 8)}`;
26 // The stub WorkOS resolves the user FROM the cookie value; a bare value61 const email = `${label}@e2e.test`;
27 // is a fresh signed-in user with no organization yet.62 let session = await signIn(email);
28 let value = user;
29 if (org) {63 if (org) {
30 // Go through the REAL product flow: create the org via the session64 // The real create-organization flow; the refreshed sealed session in
31 // route and adopt the refreshed `<user>|org:<org>` cookie it sets —65 // the response carries the new org.
32 // membership and session state come from the same path real users take.
33 const response = await fetch(new URL("/api/auth/create-organization", CLOUD_BASE_URL), {66 const response = await fetch(new URL("/api/auth/create-organization", CLOUD_BASE_URL), {
34 method: "POST",67 method: "POST",
35 headers: {68 headers: {
36 "content-type": "application/json",69 "content-type": "application/json",
37 origin: new URL(CLOUD_BASE_URL).origin,70 origin: new URL(CLOUD_BASE_URL).origin,
38 cookie: `wos-session=${value}`,71 cookie: session,
39 },72 },
40 body: JSON.stringify({ name: `Org ${user}` }),73 body: JSON.stringify({ name: `Org ${label}` }),
41 });74 });
42 if (!response.ok) {75 if (!response.ok) {
43 throw new Error(`cloud newIdentity: create-organization → ${response.status}`);76 throw new Error(`cloud newIdentity: create-organization failed (${response.status})`);
44 }77 }
45 const refreshed = (response.headers.getSetCookie?.() ?? [])78 session = cookiePair(response, "wos-session") ?? session;
46 .map((cookie) => /^wos-session=([^;]+)/.exec(cookie)?.[1])
47 .find(Boolean);
48 if (!refreshed) throw new Error("cloud newIdentity: no refreshed session cookie");
49 value = decodeURIComponent(refreshed);
50 }79 }
80 const [name, value] = session.split(/=(.*)/s);
51 return {81 return {
52 label: user,82 label: email,
53 headers: { cookie: `wos-session=${value}` },83 headers: { cookie: session },
54 cookies: [{ name: "wos-session", value }],84 cookies: [{ name: name!, value: value! }],
85 credentials: { email, password: "emulated" },
55 };86 };
56 }),87 }),
88 // MCP OAuth against the emulator's authorization server: complete the
89 // hosted flow headlessly as this identity.
90 mcpConsent: (identity) => async (request) => {
91 const authorizeUrl = new URL(request.authorizationUrl);
92 authorizeUrl.searchParams.set("login_hint", identity.credentials?.email ?? "mcp@e2e.test");
93 const response = await fetch(authorizeUrl, { redirect: "manual" });
94 const location = response.headers.get("location");
95 if (response.status !== 302 || !location) {
96 throw new Error(`cloud mcpConsent: authorize did not redirect (${response.status})`);
97 }
98 const code = new URL(location).searchParams.get("code");
99 if (!code) throw new Error("cloud mcpConsent: no code in redirect");
100 return { code };
101 },
57});102});
e2e/targets/selfhost.ts+1−1
@@ -4,7 +4,7 @@ · 3 unmodified lines
4// in setup/selfhost.globalsetup.ts.4// in setup/selfhost.globalsetup.ts.
5import { Effect } from "effect";5import { Effect } from "effect";
6 6
7import { cookieConsentStrategy } from "../../vendor/mcporter/dist/index.js";7import { cookieConsentStrategy } from "@executor-js/mcporter";
8 8
9import type { Identity, Target } from "../src/target";9import type { Identity, Target } from "../src/target";
10 10
packages/plugins/workos-vault/src/sdk/client.ts+15−0
@@ -96,8 +96,22 @@ export interface WorkOSVaultPromiseApi { · 95 unmodified lines
96export interface WorkOSVaultCredentials {96export interface WorkOSVaultCredentials {
97 readonly apiKey: string;97 readonly apiKey: string;
98 readonly clientId: string;98 readonly clientId: string;
99 /** Optional WorkOS API base-URL override (e.g. a WorkOS emulator in tests). */
100 readonly apiUrl?: string;
99}101}
100 102
103const apiUrlOptions = (
104 url: string | undefined,
105): { apiHostname?: string; port?: number; https?: boolean } => {
106 if (!url) return {};
107 const parsed = new URL(url);
108 return {
109 apiHostname: parsed.hostname,
110 ...(parsed.port ? { port: Number(parsed.port) } : {}),
111 https: parsed.protocol === "https:",
112 };
113};
114
101interface WorkOSVaultUseOptions {115interface WorkOSVaultUseOptions {
102 readonly expectedErrorStatuses?: readonly number[];116 readonly expectedErrorStatuses?: readonly number[];
103 readonly expectedErrorOutcome?: string;117 readonly expectedErrorOutcome?: string;
@@ -205,6 +219,7 @@ export const makeConfiguredWorkOSVaultClient = ( · 101 unmodified lines
205 new WorkOSClient({219 new WorkOSClient({
206 apiKey: credentials.apiKey,220 apiKey: credentials.apiKey,
207 clientId: credentials.clientId,221 clientId: credentials.clientId,
222 ...apiUrlOptions(credentials.apiUrl),
208 }),223 }),
209 ),224 ),
210 catch: (cause) => new WorkOSVaultClientInstantiationError({ cause }),225 catch: (cause) => new WorkOSVaultClientInstantiationError({ cause }),
vendor/emulate+1−1
@@ -1 +1 @@
1Subproject commit a42fe29889c8a11aa752fc848af5ed71a76fa26e1Subproject commit 41f09e973c174d4d64c60b9f4ec271cf7ef9c2eb