SPB Git

Repository Public

env.ts 2.3 KB 49 lines typescript

1/** Server-side configuration. Never import this from a client component. */2function req(name: string): string {3  const v = process.env[name];4  if (!v) throw new Error(`Missing environment variable ${name}`);5  return v;6}7const opt = (name: string) => process.env[name] || undefined;89export type MailTransport = "resend" | "smtp" | "test" | "none";1011export const env = {12  get databaseUrl() { return req("DATABASE_URL"); },13  get appUrl() { return (opt("APP_URL") ?? "http://localhost:8660").replace(/\/$/, ""); },14  get appSecret() {15    const s = req("APP_SECRET");16    if (s.length < 32) throw new Error("APP_SECRET must be at least 32 characters");17    return s;18  },19  get isProd() { return process.env.NODE_ENV === "production"; },20  get mailTransport(): MailTransport {21    const t = opt("MAIL_TRANSPORT") ?? "none";22    if (t !== "resend" && t !== "smtp" && t !== "test" && t !== "none") throw new Error(`Unknown MAIL_TRANSPORT ${t}`);23    if (t === "test" && process.env.NODE_ENV === "production") throw new Error("MAIL_TRANSPORT=test is refused in production");24    return t;25  },26  get resendApiKey() { return opt("RESEND_API_KEY"); },27  get smtpUrl() { return opt("SMTP_URL"); },28  get mailFrom() { return opt("MAIL_FROM"); },29  get ownerEmail() { return opt("OWNER_EMAIL"); },30  get ownerName() { return opt("OWNER_NAME") ?? "SP Boucher"; },31  /** Test-only clock override (ISO instant). Refused in production. */32  get fakeNow() { return process.env.NODE_ENV === "production" ? undefined : opt("SPCAL_FAKE_NOW"); },33};3435/** The only clock the application reads, so tests can pin it. */36export function nowMs(): number {37  const f = env.fakeNow;38  return f ? Temporal.Instant.from(f).epochMilliseconds : Date.now();39}4041export function mailConfigured(): { ok: boolean; transport: MailTransport; reason?: string } {42  const transport = env.mailTransport;43  if (transport === "none") return { ok: false, transport, reason: "MAIL_TRANSPORT is not set" };44  if (!env.mailFrom && transport !== "test") return { ok: false, transport, reason: "MAIL_FROM is not set" };45  if (transport === "resend" && !env.resendApiKey) return { ok: false, transport, reason: "RESEND_API_KEY is not set" };46  if (transport === "smtp" && !env.smtpUrl) return { ok: false, transport, reason: "SMTP_URL is not set" };47  return { ok: true, transport };48}49
Files main
Menu

Theme