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