import { resolve } from "node:path"; import { z } from "zod"; import type { AdbConfig } from "./adb.js"; const booleanFromString = z .enum(["true", "false"]) .default("false") .transform((value) => value === "true"); const schema = z.object({ NODE_ENV: z.enum(["development", "test", "production"]).default("development"), HOST: z.string().default("127.0.0.1"), PORT: z.coerce.number().int().min(1).max(65_535).default(8787), DATABASE_PATH: z.string().default(resolve("data/companion.db")), SESSION_TTL_HOURS: z.coerce.number().positive().max(24 * 30).default(24), COOKIE_SECURE: booleanFromString, ALLOWED_ORIGINS: z.string().default("http://mg4pi.local:8787"), UPDATE_STATUS_PATH: z.string().default("/var/lib/pi-car-companion/update-status.json"), VERSION_FILE: z.string().default(resolve("REVISION")), NETWORK_MANAGER_ENABLED: booleanFromString, NETWORK_STATUS_PATH: z.string().default("/var/lib/pi-car-companion/network-status.json"), NETWORK_COMMAND_PATH: z.string().default("/var/lib/pi-car-companion/network-command.json"), ADB_ENABLED: booleanFromString, ADB_PATH: z.string().min(1).default("adb"), ADB_SERIAL: z.string().max(128).regex(/^[a-zA-Z0-9._:-]*$/).default(""), ADB_TIMEOUT_MS: z.coerce.number().int().min(500).max(15_000).default(3_000) }); export type AppConfig = { nodeEnv: "development" | "test" | "production"; host: string; port: number; databasePath: string; sessionTtlMs: number; cookieSecure: boolean; allowedOrigins: ReadonlySet; updateStatusPath: string; versionFile: string; network: { enabled: boolean; statusPath: string; commandPath: string; }; adb: AdbConfig; }; export function loadConfig(environment: NodeJS.ProcessEnv = process.env): AppConfig { const parsed = schema.parse(environment); const allowedOrigins = new Set( parsed.ALLOWED_ORIGINS.split(",") .map((origin) => origin.trim()) .filter(Boolean) ); if (parsed.NODE_ENV === "development") { allowedOrigins.add("http://localhost:5173"); allowedOrigins.add("http://127.0.0.1:5173"); } const adbSerial = parsed.ADB_SERIAL.trim() || null; return { nodeEnv: parsed.NODE_ENV, host: parsed.HOST, port: parsed.PORT, databasePath: parsed.DATABASE_PATH, sessionTtlMs: parsed.SESSION_TTL_HOURS * 60 * 60 * 1000, cookieSecure: parsed.COOKIE_SECURE, allowedOrigins, updateStatusPath: parsed.UPDATE_STATUS_PATH, versionFile: parsed.VERSION_FILE, network: { enabled: parsed.NETWORK_MANAGER_ENABLED, statusPath: parsed.NETWORK_STATUS_PATH, commandPath: parsed.NETWORK_COMMAND_PATH }, adb: { enabled: parsed.ADB_ENABLED, executable: parsed.ADB_PATH, serial: adbSerial, timeoutMs: parsed.ADB_TIMEOUT_MS } }; }