import type { z } from "zod" import { loadEnvFile } from "./load.ts" import { cwdPathBuilder } from "./paths.ts" import { PrivateEnvSchema, PublicEnvSchema } from "./schema.ts" export { defaultMailwomanPaths } from "./paths.ts" // The optional `.env` is read once (it can't change mid-process); the real environment is layered on top // LIVE. `process.env` can change during a process — a test stubbing a var, a late setter — and `$public` / // `$private` must reflect it, exactly like `process.env` itself. So each key is a getter that re-parses the // current `{ ...dotEnv, ...process.env }` on access. The schemas enumerate the keys we know about; `z.object` // strips the rest of `process.env`, so only declared keys ever surface, typed. const dotEnv = loadEnvFile(cwdPathBuilder(".env")) function liveEnv(schema: z.ZodObject): z.infer> { const view = {} as z.infer> for (const key of Object.keys(schema.shape)) { Object.defineProperty(view, key, { enumerable: true, // oxlint-disable-next-line sister-software/no-process-globals -- this module is the typed process.env boundary get: () => schema.parse({ ...dotEnv, ...process.env })[key as keyof z.infer>], }) } return view } /** * Publicly accessible environment — non-secret operational config (DB paths, batch tuning, `NODE_ENV`). Safe to log. A * live, typed view over `process.env` layered on an optional `.env`; only keys in {@link PublicEnvSchema} appear. * * @see {@link $private} for secrets (tokens, upload credentials). */ export const $public = liveEnv(PublicEnvSchema) /** * Privately accessible environment — secrets and credentials (HF token, API keys, rclone S3 creds). Do NOT log. A live, * typed view over `process.env` layered on an optional `.env`; only keys in {@link PrivateEnvSchema} appear. * * @see {@link $public} for non-secret operational config. */ export const $private = liveEnv(PrivateEnvSchema)