84 lines
3.5 KiB
TypeScript
84 lines
3.5 KiB
TypeScript
import { z } from "zod"
|
|
|
|
const urlField = () =>
|
|
z.preprocess(
|
|
(value) => (typeof value === "string" ? value.trim() || undefined : value),
|
|
z.string().url().optional()
|
|
)
|
|
|
|
const stringField = () =>
|
|
z.preprocess(
|
|
(value) => (typeof value === "string" ? value.trim() || undefined : value),
|
|
z.string().min(1).optional()
|
|
)
|
|
|
|
const envSchema = z.object({
|
|
BETTER_AUTH_SECRET: stringField().or(z.literal("")).optional(),
|
|
BETTER_AUTH_URL: urlField().or(z.literal("")).optional(),
|
|
NEXT_PUBLIC_CONVEX_URL: urlField().or(z.literal("")).optional(),
|
|
CONVEX_INTERNAL_URL: urlField().or(z.literal("")).optional(),
|
|
DATABASE_URL: stringField().or(z.literal("")).optional(),
|
|
NEXT_PUBLIC_APP_URL: urlField().or(z.literal("")).optional(),
|
|
MACHINE_PROVISIONING_SECRET: z.string().optional(),
|
|
MACHINE_TOKEN_TTL_MS: z.coerce.number().optional(),
|
|
FLEET_SYNC_SECRET: z.string().optional(),
|
|
SMTP_ADDRESS: z.string().optional(),
|
|
SMTP_PORT: z.coerce.number().optional(),
|
|
SMTP_DOMAIN: z.string().optional(),
|
|
SMTP_USERNAME: z.string().optional(),
|
|
SMTP_PASSWORD: z.string().optional(),
|
|
SMTP_AUTHENTICATION: z.string().optional(),
|
|
SMTP_ENABLE_STARTTLS_AUTO: z.string().optional(),
|
|
SMTP_TLS: z.string().optional(),
|
|
MAILER_SENDER_EMAIL: z.string().optional(),
|
|
REPORTS_CRON_SECRET: z.string().optional(),
|
|
REPORTS_CRON_BASE_URL: urlField().or(z.literal("")).optional(),
|
|
})
|
|
|
|
const parsed = envSchema.safeParse(process.env)
|
|
|
|
if (!parsed.success) {
|
|
console.error("Failed to parse environment variables", parsed.error.flatten().fieldErrors)
|
|
throw new Error("Invalid environment configuration")
|
|
}
|
|
|
|
const isBuildPhase = process.env.NEXT_PHASE === "phase-production-build"
|
|
|
|
function resolveSecret(value: string | undefined, key: string) {
|
|
if (value && value.length > 0) return value
|
|
const fallback = isBuildPhase ? `build-placeholder-${key.toLowerCase()}` : `dev-${key.toLowerCase()}`
|
|
if (process.env.NODE_ENV === "production" && !isBuildPhase) {
|
|
throw new Error(`${key} must be set in production runtime environment`)
|
|
}
|
|
if (!isBuildPhase) {
|
|
console.warn(`ENV ${key} not set; using fallback value only for development.`)
|
|
}
|
|
return fallback
|
|
}
|
|
|
|
export const env = {
|
|
BETTER_AUTH_SECRET: resolveSecret(parsed.data.BETTER_AUTH_SECRET, "BETTER_AUTH_SECRET"),
|
|
BETTER_AUTH_URL: parsed.data.BETTER_AUTH_URL ?? parsed.data.NEXT_PUBLIC_APP_URL ?? "http://localhost:3000",
|
|
NEXT_PUBLIC_CONVEX_URL: parsed.data.NEXT_PUBLIC_CONVEX_URL,
|
|
CONVEX_INTERNAL_URL: parsed.data.CONVEX_INTERNAL_URL,
|
|
DATABASE_URL: parsed.data.DATABASE_URL,
|
|
NEXT_PUBLIC_APP_URL: parsed.data.NEXT_PUBLIC_APP_URL,
|
|
MACHINE_PROVISIONING_SECRET: parsed.data.MACHINE_PROVISIONING_SECRET,
|
|
MACHINE_TOKEN_TTL_MS: parsed.data.MACHINE_TOKEN_TTL_MS,
|
|
FLEET_SYNC_SECRET: parsed.data.FLEET_SYNC_SECRET,
|
|
REPORTS_CRON_SECRET: parsed.data.REPORTS_CRON_SECRET,
|
|
REPORTS_CRON_BASE_URL: parsed.data.REPORTS_CRON_BASE_URL,
|
|
SMTP: parsed.data.SMTP_ADDRESS && parsed.data.SMTP_USERNAME && parsed.data.SMTP_PASSWORD
|
|
? {
|
|
host: parsed.data.SMTP_ADDRESS,
|
|
port: parsed.data.SMTP_PORT ?? 465,
|
|
domain: parsed.data.SMTP_DOMAIN,
|
|
username: parsed.data.SMTP_USERNAME,
|
|
password: parsed.data.SMTP_PASSWORD,
|
|
tls: (parsed.data.SMTP_TLS ?? "true").toLowerCase() === "true",
|
|
starttls: (parsed.data.SMTP_ENABLE_STARTTLS_AUTO ?? "false").toLowerCase() === "true",
|
|
auth: parsed.data.SMTP_AUTHENTICATION ?? "login",
|
|
from: parsed.data.MAILER_SENDER_EMAIL ?? "no-reply@example.com",
|
|
}
|
|
: null,
|
|
}
|