import "dotenv/config"; import { z } from "zod"; const envSchema = z.object({ PORT: z.coerce.number().int().min(1).max(65535).default(4000), TRUST_PROXY_HOPS: z.coerce.number().int().min(0).max(5).default(1), NODE_ENV: z.enum(["development", "test", "production"]).default("development"), FRONTEND_URL: z.string().url(), BACKEND_URL: z.string().url(), ANALYSIS_API_URL: z.string().url().default("http://127.0.0.1:8000"), AGENT_API_URL: z.string().url().optional(), AGENT_API_KEY: z.string().min(16).optional(), HF_TOKEN: z.string().optional(), ANALYSIS_PROXY_SECRET: z.string().min(32), COLAB_WORKER_SECRET: z.string().optional(), SUPABASE_URL: z.string().url(), SUPABASE_ANON_KEY: z.string().min(1), SUPABASE_SERVICE_ROLE_KEY: z.string().min(1), }); const parsed = envSchema.safeParse(process.env); if (!parsed.success) { console.error("Invalid environment configuration:", parsed.error.flatten().fieldErrors); process.exit(1); } export const env = parsed.data; if (env.COLAB_WORKER_SECRET && env.COLAB_WORKER_SECRET.length < 32) { console.error("COLAB_WORKER_SECRET must contain at least 32 characters when configured"); process.exit(1); } export const isProduction = env.NODE_ENV === "production"; // Production browsers reach auth through Cloudflare Pages, including OAuth. export const authPublicOrigin = new URL(isProduction ? env.FRONTEND_URL : env.BACKEND_URL).origin; if (isProduction) { for (const name of ["FRONTEND_URL", "BACKEND_URL", "SUPABASE_URL"] as const) { const url = new URL(env[name]); if (url.protocol !== "https:" || ["localhost", "127.0.0.1", "[::1]"].includes(url.hostname)) { console.error(`${name} must use a public HTTPS URL in production`); process.exit(1); } } } // Trusted identity headers must stay inside the combined backend. const analysisUrl = new URL(env.ANALYSIS_API_URL); if (analysisUrl.protocol !== "http:" || !["localhost", "127.0.0.1", "[::1]"].includes(analysisUrl.hostname)) { console.error("ANALYSIS_API_URL must use HTTP on loopback"); process.exit(1); }