History

feat(cloud): use @executor/env for env access (#100)

Rhys Sullivan committed 7 months ago

Commit b5f705e2 parents 004ac0d 1b950e410 files · +91 −15Browse files

Unified · Split

10 files
  1. apps/cloud/package.json +1 −0
  2. apps/cloud/src/api.ts +3 −2
  3. apps/cloud/src/auth/handlers.ts +2 −1
  4. apps/cloud/src/auth/workos.ts +4 −3
  5. apps/cloud/src/env.ts +55 −0
  6. apps/cloud/src/services/db.ts +4 −3
  7. apps/cloud/src/web/auth.tsx +4 −3
  8. apps/cloud/tsconfig.json +2 −2
  9. apps/cloud/vite.config.ts +15 −1
  10. bun.lock +1 −0
apps/cloud/package.json+1−0
@@ -8,6 +8,7 @@ · 7 unmodified lines
88 "@effect-atom/atom-react": "^0.5.0",
99 "@effect/platform": "catalog:",
1010 "@executor/api": "workspace:*",
11+ "@executor/env": "workspace:*",
1112 "@executor/execution": "workspace:*",
1213 "@executor/plugin-google-discovery": "workspace:*",
1314 "@executor/plugin-graphql": "workspace:*",
apps/cloud/src/api.ts+3−2
@@ -31,6 +31,7 @@ import { CloudAuthHandlers, CloudAuthPublicHandlers } from "./auth/handlers"; · 30 unmodified lines
3131 import { WorkOSAuth } from "./auth/workos";
3232 import { DbService } from "./services/db";
3333 import { createTeamExecutor } from "./services/executor";
34+import { server } from "./env";
3435
3536 const ProtectedCloudApi = addGroup(OpenApiGroup)
3637 .add(McpGroup)
@@ -104,7 +105,7 @@ const COOKIE_OPTIONS = { · 67 unmodified lines
104105 httpOnly: true,
105106 sameSite: "lax" as const,
106107 maxAge: 60 * 60 * 24 * 7,
107− secure: process.env.NODE_ENV === "production",
108+ secure: server.NODE_ENV === "production",
108109 };
109110
110111 const resolveAuth = (request: Request) =>
@@ -151,7 +152,7 @@ const resolveExecutor = (teamId: string) => · 40 unmodified lines
151152 const users = yield* UserStoreService;
152153 const team = yield* users.use((store) => store.getTeam(teamId));
153154 const teamName = team?.name ?? "Unknown Team";
154− const encryptionKey = process.env.ENCRYPTION_KEY ?? "local-dev-encryption-key";
155+ const encryptionKey = server.ENCRYPTION_KEY;
155156 return yield* createTeamExecutor(teamId, teamName, encryptionKey);
156157 });
157158
apps/cloud/src/auth/handlers.ts+2−1
@@ -6,13 +6,14 @@ import { addGroup } from "@executor/api"; · 5 unmodified lines
66 import { AUTH_PATHS, CloudAuthApi, CloudAuthPublicApi } from "./api";
77 import { AuthContext, UserStoreService } from "./context";
88 import { WorkOSAuth } from "./workos";
9+import { server } from "../env";
910
1011 const COOKIE_OPTIONS = {
1112 path: "/",
1213 httpOnly: true,
1314 sameSite: "lax" as const,
1415 maxAge: 60 * 60 * 24 * 7,
15− secure: process.env.NODE_ENV === "production",
16+ secure: server.NODE_ENV === "production",
1617 };
1718
1819 // ---------------------------------------------------------------------------
apps/cloud/src/auth/workos.ts+4−3
@@ -5,6 +5,7 @@ · 4 unmodified lines
55 import { Context, Effect, Layer } from "effect";
66 import { WorkOS } from "@workos-inc/node";
77 import { WorkOSError } from "./errors";
8+import { server } from "../env";
89
910 const COOKIE_NAME = "wos-session";
1011
@@ -14,9 +15,9 @@ const COOKIE_NAME = "wos-session"; · 3 unmodified lines
1415
1516
1617 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;
2021
2122 if (!cookiePassword || cookiePassword.length < 32) {
2223 return yield* Effect.die(new Error("WORKOS_COOKIE_PASSWORD must be at least 32 characters"));
apps/cloud/src/env.ts+55−0
@@ -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;
apps/cloud/src/services/db.ts+4−3
@@ -7,6 +7,7 @@ import { resolve } from "node:path"; · 6 unmodified lines
77 import * as sharedSchema from "@executor/storage-postgres/schema";
88 import * as cloudSchema from "./schema";
99 import type { DrizzleDb } from "@executor/storage-postgres";
10+import { server } from "../env";
1011
1112 const schema = { ...sharedSchema, ...cloudSchema };
1213
@@ -23,11 +24,11 @@ type DbResource = { · 10 unmodified lines
2324 };
2425
2526 const createDbResource = async (): Promise<DbResource> => {
26− if (process.env.DATABASE_URL) {
27+ if (server.DATABASE_URL) {
2728 const { drizzle } = await import("drizzle-orm/node-postgres");
2829 const { migrate } = await import("drizzle-orm/node-postgres/migrator");
2930 const { Pool } = await import("pg");
30− const pool = new Pool({ connectionString: process.env.DATABASE_URL });
31+ const pool = new Pool({ connectionString: server.DATABASE_URL });
3132 const db = drizzle(pool, { schema }) as DrizzleDb;
3233 await migrate(db as any, { migrationsFolder: MIGRATIONS_DIR });
3334 return {
@@ -39,7 +40,7 @@ const createDbResource = async (): Promise<DbResource> => { · 5 unmodified lines
3940 const { PGlite } = await import("@electric-sql/pglite");
4041 const { drizzle } = await import("drizzle-orm/pglite");
4142 const { migrate } = await import("drizzle-orm/pglite/migrator");
42− const dataDir = process.env.PGLITE_DATA_DIR ?? ".pglite";
43+ const dataDir = server.PGLITE_DATA_DIR;
4344 const client = new PGlite(dataDir);
4445 const db = drizzle(client, { schema }) as DrizzleDb;
4546 await migrate(db, { migrationsFolder: MIGRATIONS_DIR });
apps/cloud/src/web/auth.tsx+4−3
@@ -23,9 +23,10 @@ type AuthTeam = { · 22 unmodified lines
2323 // Auth atom — typed query against CloudAuthApi
2424 // ---------------------------------------------------------------------------
2525
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+ });
2930
3031 // ---------------------------------------------------------------------------
3132 // Provider + hook
apps/cloud/tsconfig.json+2−2
@@ -8,8 +8,8 @@ · 7 unmodified lines
88 "skipLibCheck": true,
99 "outDir": "dist",
1010 "rootDir": ".",
11− "declaration": true,
12− "declarationMap": true,
11+ "declaration": false,
12+ "declarationMap": false,
1313 "sourceMap": true,
1414 "jsx": "react-jsx",
1515 "plugins": [
apps/cloud/vite.config.ts+15−1
@@ -2,10 +2,24 @@ import { defineConfig } from "vite"; · 1 unmodified line
22 import { tanstackStart } from "@tanstack/react-start/plugin/vite";
33 import react from "@vitejs/plugin-react";
44 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;
519
620 export default defineConfig({
721 server: {
8− port: parseInt(process.env.PORT ?? "5173", 10),
22+ port: viteEnv.PORT,
923 host: "127.0.0.1",
1024 },
1125 resolve: { tsconfigPaths: true },
bun.lock+1−0
@@ -47,6 +47,7 @@ · 46 unmodified lines
4747 "@effect-atom/atom-react": "^0.5.0",
4848 "@effect/platform": "catalog:",
4949 "@executor/api": "workspace:*",
50+ "@executor/env": "workspace:*",
5051 "@executor/execution": "workspace:*",
5152 "@executor/plugin-google-discovery": "workspace:*",
5253 "@executor/plugin-graphql": "workspace:*",