Unified · Split
| @@ -8,6 +8,7 @@ · 7 unmodified lines | |||
| 8 | "@effect-atom/atom-react": "^0.5.0", | 8 | "@effect-atom/atom-react": "^0.5.0", |
| 9 | "@effect/platform": "catalog:", | 9 | "@effect/platform": "catalog:", |
| 10 | "@executor/api": "workspace:*", | 10 | "@executor/api": "workspace:*", |
| 11 | "@executor/env": "workspace:*", | ||
| 11 | "@executor/execution": "workspace:*", | 12 | "@executor/execution": "workspace:*", |
| 12 | "@executor/plugin-google-discovery": "workspace:*", | 13 | "@executor/plugin-google-discovery": "workspace:*", |
| 13 | "@executor/plugin-graphql": "workspace:*", | 14 | "@executor/plugin-graphql": "workspace:*", |
| @@ -31,6 +31,7 @@ import { CloudAuthHandlers, CloudAuthPublicHandlers } from "./auth/handlers"; · 30 unmodified lines | |||
| 31 | import { WorkOSAuth } from "./auth/workos"; | 31 | import { WorkOSAuth } from "./auth/workos"; |
| 32 | import { DbService } from "./services/db"; | 32 | import { DbService } from "./services/db"; |
| 33 | import { createTeamExecutor } from "./services/executor"; | 33 | import { createTeamExecutor } from "./services/executor"; |
| 34 | import { server } from "./env"; | ||
| 34 | 35 | ||
| 35 | const ProtectedCloudApi = addGroup(OpenApiGroup) | 36 | const ProtectedCloudApi = addGroup(OpenApiGroup) |
| 36 | .add(McpGroup) | 37 | .add(McpGroup) |
| @@ -104,7 +105,7 @@ const COOKIE_OPTIONS = { · 67 unmodified lines | |||
| 104 | httpOnly: true, | 105 | httpOnly: true, |
| 105 | sameSite: "lax" as const, | 106 | sameSite: "lax" as const, |
| 106 | maxAge: 60 * 60 * 24 * 7, | 107 | maxAge: 60 * 60 * 24 * 7, |
| 107 | secure: process.env.NODE_ENV === "production", | 108 | secure: server.NODE_ENV === "production", |
| 108 | }; | 109 | }; |
| 109 | 110 | ||
| 110 | const resolveAuth = (request: Request) => | 111 | const resolveAuth = (request: Request) => |
| @@ -151,7 +152,7 @@ const resolveExecutor = (teamId: string) => · 40 unmodified lines | |||
| 151 | const users = yield* UserStoreService; | 152 | const users = yield* UserStoreService; |
| 152 | const team = yield* users.use((store) => store.getTeam(teamId)); | 153 | const team = yield* users.use((store) => store.getTeam(teamId)); |
| 153 | const teamName = team?.name ?? "Unknown Team"; | 154 | const teamName = team?.name ?? "Unknown Team"; |
| 154 | const encryptionKey = process.env.ENCRYPTION_KEY ?? "local-dev-encryption-key"; | 155 | const encryptionKey = server.ENCRYPTION_KEY; |
| 155 | return yield* createTeamExecutor(teamId, teamName, encryptionKey); | 156 | return yield* createTeamExecutor(teamId, teamName, encryptionKey); |
| 156 | }); | 157 | }); |
| 157 | 158 | ||
| @@ -6,13 +6,14 @@ import { addGroup } from "@executor/api"; · 5 unmodified lines | |||
| 6 | import { AUTH_PATHS, CloudAuthApi, CloudAuthPublicApi } from "./api"; | 6 | import { AUTH_PATHS, CloudAuthApi, CloudAuthPublicApi } from "./api"; |
| 7 | import { AuthContext, UserStoreService } from "./context"; | 7 | import { AuthContext, UserStoreService } from "./context"; |
| 8 | import { WorkOSAuth } from "./workos"; | 8 | import { WorkOSAuth } from "./workos"; |
| 9 | import { server } from "../env"; | ||
| 9 | 10 | ||
| 10 | const COOKIE_OPTIONS = { | 11 | const COOKIE_OPTIONS = { |
| 11 | path: "/", | 12 | path: "/", |
| 12 | httpOnly: true, | 13 | httpOnly: true, |
| 13 | sameSite: "lax" as const, | 14 | sameSite: "lax" as const, |
| 14 | maxAge: 60 * 60 * 24 * 7, | 15 | maxAge: 60 * 60 * 24 * 7, |
| 15 | secure: process.env.NODE_ENV === "production", | 16 | secure: server.NODE_ENV === "production", |
| 16 | }; | 17 | }; |
| 17 | 18 | ||
| 18 | // --------------------------------------------------------------------------- | 19 | // --------------------------------------------------------------------------- |
| @@ -5,6 +5,7 @@ · 4 unmodified lines | |||
| 5 | import { Context, Effect, Layer } from "effect"; | 5 | import { Context, Effect, Layer } from "effect"; |
| 6 | import { WorkOS } from "@workos-inc/node"; | 6 | import { WorkOS } from "@workos-inc/node"; |
| 7 | import { WorkOSError } from "./errors"; | 7 | import { WorkOSError } from "./errors"; |
| 8 | import { server } from "../env"; | ||
| 8 | 9 | ||
| 9 | const COOKIE_NAME = "wos-session"; | 10 | const COOKIE_NAME = "wos-session"; |
| 10 | 11 | ||
| @@ -14,9 +15,9 @@ const COOKIE_NAME = "wos-session"; · 3 unmodified lines | |||
| 14 | 15 | ||
| 15 | 16 | ||
| 16 | const make = Effect.gen(function* () { | 17 | const make = Effect.gen(function* () { |
| 17 | const apiKey = process.env.WORKOS_API_KEY!; | 18 | const apiKey = server.WORKOS_API_KEY; |
| 18 | const clientId = process.env.WORKOS_CLIENT_ID!; | 19 | const clientId = server.WORKOS_CLIENT_ID; |
| 19 | const cookiePassword = process.env.WORKOS_COOKIE_PASSWORD!; | 20 | const cookiePassword = server.WORKOS_COOKIE_PASSWORD; |
| 20 | 21 | ||
| 21 | if (!cookiePassword || cookiePassword.length < 32) { | 22 | if (!cookiePassword || cookiePassword.length < 32) { |
| 22 | return yield* Effect.die(new Error("WORKOS_COOKIE_PASSWORD must be at least 32 characters")); | 23 | return yield* Effect.die(new Error("WORKOS_COOKIE_PASSWORD must be at least 32 characters")); |
| @@ -0,0 +1,55 @@ | |||
| 1 | import { createEnv, Env } from "@executor/env"; | ||
| 2 | |||
| 3 | const sharedShape = { | ||
| 4 | NODE_ENV: Env.literalOr( | ||
| 5 | "NODE_ENV", | ||
| 6 | "development", | ||
| 7 | "development", | ||
| 8 | "test", | ||
| 9 | "production", | ||
| 10 | ), | ||
| 11 | }; | ||
| 12 | |||
| 13 | const serverShape = { | ||
| 14 | DATABASE_URL: Env.stringOr("DATABASE_URL", ""), | ||
| 15 | PGLITE_DATA_DIR: Env.stringOr("PGLITE_DATA_DIR", ".pglite"), | ||
| 16 | ENCRYPTION_KEY: Env.stringOr( | ||
| 17 | "ENCRYPTION_KEY", | ||
| 18 | "local-dev-encryption-key", | ||
| 19 | ), | ||
| 20 | WORKOS_API_KEY: Env.string("WORKOS_API_KEY"), | ||
| 21 | WORKOS_CLIENT_ID: Env.string("WORKOS_CLIENT_ID"), | ||
| 22 | WORKOS_COOKIE_PASSWORD: Env.string("WORKOS_COOKIE_PASSWORD"), | ||
| 23 | }; | ||
| 24 | |||
| 25 | type SharedEnv = Readonly<{ | ||
| 26 | NODE_ENV: "development" | "test" | "production"; | ||
| 27 | }>; | ||
| 28 | |||
| 29 | type ServerEnv = SharedEnv & Readonly<{ | ||
| 30 | DATABASE_URL: string; | ||
| 31 | PGLITE_DATA_DIR: string; | ||
| 32 | ENCRYPTION_KEY: string; | ||
| 33 | WORKOS_API_KEY: string; | ||
| 34 | WORKOS_CLIENT_ID: string; | ||
| 35 | WORKOS_COOKIE_PASSWORD: string; | ||
| 36 | }>; | ||
| 37 | |||
| 38 | type WebEnv = Readonly<Record<string, never>>; | ||
| 39 | |||
| 40 | export const shared = createEnv(sharedShape, { | ||
| 41 | runtimeEnv: process.env, | ||
| 42 | emptyStringAsUndefined: true, | ||
| 43 | }) as SharedEnv; | ||
| 44 | |||
| 45 | export const web = createEnv({}, { | ||
| 46 | prefix: "PUBLIC_", | ||
| 47 | runtimeEnv: process.env, | ||
| 48 | emptyStringAsUndefined: true, | ||
| 49 | }) as WebEnv; | ||
| 50 | |||
| 51 | export const server = createEnv(serverShape, { | ||
| 52 | extends: [shared], | ||
| 53 | runtimeEnv: process.env, | ||
| 54 | emptyStringAsUndefined: true, | ||
| 55 | }) as ServerEnv; | ||
| @@ -7,6 +7,7 @@ import { resolve } from "node:path"; · 6 unmodified lines | |||
| 7 | import * as sharedSchema from "@executor/storage-postgres/schema"; | 7 | import * as sharedSchema from "@executor/storage-postgres/schema"; |
| 8 | import * as cloudSchema from "./schema"; | 8 | import * as cloudSchema from "./schema"; |
| 9 | import type { DrizzleDb } from "@executor/storage-postgres"; | 9 | import type { DrizzleDb } from "@executor/storage-postgres"; |
| 10 | import { server } from "../env"; | ||
| 10 | 11 | ||
| 11 | const schema = { ...sharedSchema, ...cloudSchema }; | 12 | const schema = { ...sharedSchema, ...cloudSchema }; |
| 12 | 13 | ||
| @@ -23,11 +24,11 @@ type DbResource = { · 10 unmodified lines | |||
| 23 | }; | 24 | }; |
| 24 | 25 | ||
| 25 | const createDbResource = async (): Promise<DbResource> => { | 26 | const createDbResource = async (): Promise<DbResource> => { |
| 26 | if (process.env.DATABASE_URL) { | 27 | if (server.DATABASE_URL) { |
| 27 | const { drizzle } = await import("drizzle-orm/node-postgres"); | 28 | const { drizzle } = await import("drizzle-orm/node-postgres"); |
| 28 | const { migrate } = await import("drizzle-orm/node-postgres/migrator"); | 29 | const { migrate } = await import("drizzle-orm/node-postgres/migrator"); |
| 29 | const { Pool } = await import("pg"); | 30 | const { Pool } = await import("pg"); |
| 30 | const pool = new Pool({ connectionString: process.env.DATABASE_URL }); | 31 | const pool = new Pool({ connectionString: server.DATABASE_URL }); |
| 31 | const db = drizzle(pool, { schema }) as DrizzleDb; | 32 | const db = drizzle(pool, { schema }) as DrizzleDb; |
| 32 | await migrate(db as any, { migrationsFolder: MIGRATIONS_DIR }); | 33 | await migrate(db as any, { migrationsFolder: MIGRATIONS_DIR }); |
| 33 | return { | 34 | return { |
| @@ -39,7 +40,7 @@ const createDbResource = async (): Promise<DbResource> => { · 5 unmodified lines | |||
| 39 | const { PGlite } = await import("@electric-sql/pglite"); | 40 | const { PGlite } = await import("@electric-sql/pglite"); |
| 40 | const { drizzle } = await import("drizzle-orm/pglite"); | 41 | const { drizzle } = await import("drizzle-orm/pglite"); |
| 41 | const { migrate } = await import("drizzle-orm/pglite/migrator"); | 42 | const { migrate } = await import("drizzle-orm/pglite/migrator"); |
| 42 | const dataDir = process.env.PGLITE_DATA_DIR ?? ".pglite"; | 43 | const dataDir = server.PGLITE_DATA_DIR; |
| 43 | const client = new PGlite(dataDir); | 44 | const client = new PGlite(dataDir); |
| 44 | const db = drizzle(client, { schema }) as DrizzleDb; | 45 | const db = drizzle(client, { schema }) as DrizzleDb; |
| 45 | await migrate(db, { migrationsFolder: MIGRATIONS_DIR }); | 46 | await migrate(db, { migrationsFolder: MIGRATIONS_DIR }); |
| @@ -23,9 +23,10 @@ type AuthTeam = { · 22 unmodified lines | |||
| 23 | // Auth atom — typed query against CloudAuthApi | 23 | // Auth atom — typed query against CloudAuthApi |
| 24 | // --------------------------------------------------------------------------- | 24 | // --------------------------------------------------------------------------- |
| 25 | 25 | ||
| 26 | export const authAtom = CloudApiClient.query("cloudAuth", "me", { | 26 | export const authAtom = |
| 27 | timeToLive: "5 minutes", | 27 | CloudApiClient.query("cloudAuth", "me", { |
| 28 | }); | 28 | timeToLive: "5 minutes", |
| 29 | }); | ||
| 29 | 30 | ||
| 30 | // --------------------------------------------------------------------------- | 31 | // --------------------------------------------------------------------------- |
| 31 | // Provider + hook | 32 | // Provider + hook |
| @@ -8,8 +8,8 @@ · 7 unmodified lines | |||
| 8 | "skipLibCheck": true, | 8 | "skipLibCheck": true, |
| 9 | "outDir": "dist", | 9 | "outDir": "dist", |
| 10 | "rootDir": ".", | 10 | "rootDir": ".", |
| 11 | "declaration": true, | 11 | "declaration": false, |
| 12 | "declarationMap": true, | 12 | "declarationMap": false, |
| 13 | "sourceMap": true, | 13 | "sourceMap": true, |
| 14 | "jsx": "react-jsx", | 14 | "jsx": "react-jsx", |
| 15 | "plugins": [ | 15 | "plugins": [ |
| @@ -2,10 +2,24 @@ import { defineConfig } from "vite"; · 1 unmodified line | |||
| 2 | import { tanstackStart } from "@tanstack/react-start/plugin/vite"; | 2 | import { tanstackStart } from "@tanstack/react-start/plugin/vite"; |
| 3 | import react from "@vitejs/plugin-react"; | 3 | import react from "@vitejs/plugin-react"; |
| 4 | import tailwindcss from "@tailwindcss/vite"; | 4 | import tailwindcss from "@tailwindcss/vite"; |
| 5 | import { createEnv, Env } from "@executor/env"; | ||
| 6 | |||
| 7 | const server = { | ||
| 8 | PORT: Env.numberOr("PORT", 5173), | ||
| 9 | }; | ||
| 10 | |||
| 11 | type ViteEnv = Readonly<{ | ||
| 12 | PORT: number; | ||
| 13 | }>; | ||
| 14 | |||
| 15 | const viteEnv = createEnv(server, { | ||
| 16 | runtimeEnv: process.env, | ||
| 17 | emptyStringAsUndefined: true, | ||
| 18 | }) as ViteEnv; | ||
| 5 | 19 | ||
| 6 | export default defineConfig({ | 20 | export default defineConfig({ |
| 7 | server: { | 21 | server: { |
| 8 | port: parseInt(process.env.PORT ?? "5173", 10), | 22 | port: viteEnv.PORT, |
| 9 | host: "127.0.0.1", | 23 | host: "127.0.0.1", |
| 10 | }, | 24 | }, |
| 11 | resolve: { tsconfigPaths: true }, | 25 | resolve: { tsconfigPaths: true }, |
| @@ -47,6 +47,7 @@ · 46 unmodified lines | |||
| 47 | "@effect-atom/atom-react": "^0.5.0", | 47 | "@effect-atom/atom-react": "^0.5.0", |
| 48 | "@effect/platform": "catalog:", | 48 | "@effect/platform": "catalog:", |
| 49 | "@executor/api": "workspace:*", | 49 | "@executor/api": "workspace:*", |
| 50 | "@executor/env": "workspace:*", | ||
| 50 | "@executor/execution": "workspace:*", | 51 | "@executor/execution": "workspace:*", |
| 51 | "@executor/plugin-google-discovery": "workspace:*", | 52 | "@executor/plugin-google-discovery": "workspace:*", |
| 52 | "@executor/plugin-graphql": "workspace:*", | 53 | "@executor/plugin-graphql": "workspace:*", |