19 changed files with 218 additions and 57 deletions
Binary file not shown.
@ -1,11 +1,11 @@ |
|||
import './env'; |
|||
import { defineConfig } from 'drizzle-kit'; |
|||
import { env } from '../env'; |
|||
|
|||
export default defineConfig({ |
|||
out: './migrations', |
|||
schema: './lib/schema/*', |
|||
dialect: 'sqlite', |
|||
dbCredentials: { |
|||
url: process.env.DATABASE_URL! |
|||
url: env.DATABASE_URL, |
|||
}, |
|||
}); |
|||
|
|||
@ -1,4 +1,4 @@ |
|||
|
|||
import { config } from 'dotenv'; |
|||
|
|||
config({ path: '../../.env' }); |
|||
// Re-export env from the centralized env package.
|
|||
// This file is kept for backward-compatible side-effect imports.
|
|||
export { env } from '../env'; |
|||
|
|||
@ -0,0 +1,36 @@ |
|||
import { config } from "dotenv"; |
|||
import path from "node:path"; |
|||
import { fileURLToPath } from "node:url"; |
|||
import { envSchema, type Env } from "./schema"; |
|||
|
|||
// ── Load .env from project root ──
|
|||
// Resolve relative to this file: packages/env/ → ../../.env = project root
|
|||
const __dirname = path.dirname(fileURLToPath(import.meta.url)); |
|||
const projectRoot = path.resolve(__dirname, "..", ".."); |
|||
config({ path: path.resolve(projectRoot, ".env") }); |
|||
|
|||
// ── Parse & validate ──
|
|||
function parseEnv(): Env { |
|||
const raw: Record<string, string | undefined> = {}; |
|||
|
|||
// Collect only the keys we care about from process.env
|
|||
for (const key of Object.keys(envSchema.shape)) { |
|||
raw[key] = process.env[key]; |
|||
} |
|||
|
|||
const result = envSchema.safeParse(raw); |
|||
|
|||
if (!result.success) { |
|||
const errors = result.error.issues |
|||
.map((issue) => ` - ${issue.path.join(".")}: ${issue.message}`) |
|||
.join("\n"); |
|||
throw new Error(`Environment variable validation failed:\n${errors}`); |
|||
} |
|||
|
|||
return result.data; |
|||
} |
|||
|
|||
export const env: Readonly<Env> = parseEnv(); |
|||
|
|||
// Re-export the type for consumers
|
|||
export type { Env } from "./schema"; |
|||
@ -0,0 +1,12 @@ |
|||
{ |
|||
"name": "env", |
|||
"version": "0.1.0", |
|||
"type": "module", |
|||
"exports": { |
|||
".": "./index.ts" |
|||
}, |
|||
"dependencies": { |
|||
"dotenv": "17.4.1", |
|||
"zod": "4.3.6" |
|||
} |
|||
} |
|||
@ -0,0 +1,54 @@ |
|||
import { z } from "zod"; |
|||
|
|||
/** |
|||
* Zod schema for all environment variables used across the project. |
|||
* |
|||
* Every `process.env.X` access should be replaced by importing `env` from |
|||
* this package and using the typed, validated value. |
|||
* |
|||
* To add a new env var: |
|||
* 1. Add it to this schema with the correct type, default, and validation. |
|||
* 2. The typed `env` object is auto-generated from the schema's output. |
|||
*/ |
|||
|
|||
export const envSchema = z.object({ |
|||
// ── Database ──
|
|||
DATABASE_URL: z.string().min(1, "DATABASE_URL is required"), |
|||
|
|||
// ── Node / Runtime ──
|
|||
NODE_ENV: z |
|||
.enum(["development", "production", "test"]) |
|||
.default("development"), |
|||
|
|||
// ── Logging (logger package) ──
|
|||
LOG_MAX_SIZE: z.coerce.number().int().positive().default(10 * 1024 * 1024), // 10 MB
|
|||
LOG_MAX_BACKUPS: z.coerce.number().int().nonnegative().default(5), |
|||
|
|||
// ── Static file serving ──
|
|||
STATIC_DIR: z.string().default("static"), |
|||
UPLOAD_SUBDIR: z.string().default("upload"), |
|||
|
|||
// ── Scheduler ──
|
|||
SCHEDULER_MAX_CONCURRENCY: z.coerce.number().int().positive().default(5), |
|||
SCHEDULER_LOG_RETENTION_DAYS: z.coerce.number().int().positive().default(30), |
|||
|
|||
// ── App ──
|
|||
APP_URL: z.string().min(1, "APP_URL is required"), |
|||
NITRO_PORT: z.coerce.number().int().positive(), |
|||
|
|||
// ── OAuth: GitHub ──
|
|||
GITHUB_CLIENT_ID: z.string().default(""), |
|||
GITHUB_CLIENT_SECRET: z.string().default(""), |
|||
|
|||
// ── OAuth: Gitea ──
|
|||
GITEA_CLIENT_ID: z.string().default(""), |
|||
GITEA_CLIENT_SECRET: z.string().default(""), |
|||
GITEA_URL: z.string().default(""), |
|||
|
|||
// ── Bootstrap (seed) ──
|
|||
BOOTSTRAP_ADMIN_USERNAME: z.string().optional(), |
|||
BOOTSTRAP_ADMIN_PASSWORD: z.string().optional(), |
|||
}); |
|||
|
|||
/** Inferred type of the parsed env object. */ |
|||
export type Env = z.infer<typeof envSchema>; |
|||
Loading…
Reference in new issue