49 lines
1.8 KiB
TypeScript
49 lines
1.8 KiB
TypeScript
import { z } from "zod";
|
|
|
|
const EnvSchema = z.object({
|
|
DATABASE_URL: z.string().min(1),
|
|
UPLOAD_DIR: z.string().default("./data/uploads"),
|
|
APP_URL: z.string().url().default("http://localhost:3000"),
|
|
APP_SECRET: z.string().min(32, "APP_SECRET must be at least 32 chars"),
|
|
ADMIN_SESSION_HOURS: z.coerce.number().int().positive().default(8),
|
|
OPERATOR_SESSION_HOURS: z.coerce.number().int().positive().default(12),
|
|
BOOTSTRAP_ADMIN_EMAIL: z.string().email().optional(),
|
|
BOOTSTRAP_ADMIN_PASSWORD: z.string().min(1).optional(),
|
|
BOOTSTRAP_ADMIN_NAME: z.string().default("Administrator"),
|
|
PIN_MAX_ATTEMPTS: z.coerce.number().int().positive().default(5),
|
|
PIN_LOCKOUT_MINUTES: z.coerce.number().int().positive().default(15),
|
|
NODE_ENV: z.enum(["development", "production", "test"]).default("development"),
|
|
});
|
|
|
|
export type Env = z.infer<typeof EnvSchema>;
|
|
|
|
function load(): Env {
|
|
// During `next build` page-data collection the route modules are evaluated
|
|
// without real secrets — fall back to safe placeholders so the build can
|
|
// emit the module graph. Real runtime still re-validates at request time.
|
|
const isBuildPhase =
|
|
process.env.NEXT_PHASE === "phase-production-build" ||
|
|
process.env.NEXT_BUILD === "true";
|
|
|
|
const source = isBuildPhase
|
|
? {
|
|
...process.env,
|
|
DATABASE_URL: process.env.DATABASE_URL ?? "file:./data/build-placeholder.db",
|
|
APP_SECRET:
|
|
process.env.APP_SECRET ??
|
|
"build-time-placeholder-secret-please-override-at-runtime",
|
|
}
|
|
: process.env;
|
|
|
|
const parsed = EnvSchema.safeParse(source);
|
|
if (!parsed.success) {
|
|
const issues = parsed.error.issues
|
|
.map((i) => ` - ${i.path.join(".")}: ${i.message}`)
|
|
.join("\n");
|
|
throw new Error(`Invalid environment configuration:\n${issues}`);
|
|
}
|
|
return parsed.data;
|
|
}
|
|
|
|
export const env = load();
|