import { z } from "zod"; /** * Variables d'environnement serveur, validées au premier accès. * Aucune n'est exposée au navigateur (pas de préfixe NEXT_PUBLIC_ sauf * NEXT_PUBLIC_SITE_URL et NEXT_PUBLIC_CONTENT_PREVIEW, non sensibles). */ const schema = z.object({ NODE_ENV: z.enum(["development", "test", "production"]).default("development"), NEXT_PUBLIC_SITE_URL: z.url().optional(), NEXT_PUBLIC_CONTENT_PREVIEW: z.enum(["0", "1"]).default("0"), /** `smtp` = envoi réel ; `log` = journalisation locale uniquement (dev/CI). */ CONTACT_TRANSPORT: z.enum(["smtp", "log"]).default("log"), CONTACT_TO: z.email().optional(), CONTACT_FROM: z.email().optional(), SMTP_HOST: z.string().optional(), SMTP_PORT: z.coerce.number().int().positive().optional(), SMTP_SECURE: z .enum(["true", "false"]) .default("false") .transform((v) => v === "true"), SMTP_USER: z.string().optional(), SMTP_PASS: z.string().optional(), /** Limitation de débit du formulaire : N requêtes par fenêtre (secondes). */ CONTACT_RATE_LIMIT: z.coerce.number().int().positive().default(5), CONTACT_RATE_WINDOW_S: z.coerce.number().int().positive().default(600), }); export type Env = z.infer; let cached: Env | null = null; export function getEnv(): Env { if (cached) return cached; const parsed = schema.safeParse(process.env); if (!parsed.success) { const issues = parsed.error.issues.map((i) => `${i.path.join(".")}: ${i.message}`).join("; "); throw new Error(`Variables d'environnement invalides : ${issues}`); } const env = parsed.data; if (env.CONTACT_TRANSPORT === "smtp") { const missing = (["CONTACT_TO", "CONTACT_FROM", "SMTP_HOST", "SMTP_PORT"] as const).filter( (k) => !env[k], ); if (missing.length) { throw new Error(`CONTACT_TRANSPORT=smtp requiert : ${missing.join(", ")}`); } } cached = env; return env; } /** Pour les tests : vider le cache après modification de process.env. */ export function resetEnvCache() { cached = null; } export function siteUrl(fallback: string): string { return process.env.NEXT_PUBLIC_SITE_URL ?? fallback; }