-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathenv.ts
More file actions
111 lines (89 loc) · 4.06 KB
/
Copy pathenv.ts
File metadata and controls
111 lines (89 loc) · 4.06 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
import { Value } from "@sinclair/typebox/value";
import { t, type Static } from "elysia";
const EnvSchema = t.Object({
DATABASE_URL: t.String(),
JWT_SECRET: t.String(),
JWT_EXPIRY: t.Optional(t.String({ default: "1d" })),
REFRESH_TOKEN_EXPIRY: t.Optional(t.String({ default: "30d" })),
PORT: t.Optional(t.String({ default: "4000" })),
NODE_ENV: t.Optional(
t.Union([t.Literal("development"), t.Literal("production"), t.Literal("staging")]),
),
CORS_ORIGINS: t.Optional(t.String({ default: "http://localhost:4001" })),
LOG_LEVEL: t.Optional(t.String({ default: "info" })),
UPLOAD_DIR: t.Optional(t.String({ default: "./uploads" })),
R2_ACCOUNT_ID: t.Optional(t.String()),
R2_BUCKET_NAME: t.Optional(t.String({ default: "ogstack-images" })),
R2_ACCESS_KEY_ID: t.Optional(t.String()),
R2_SECRET_ACCESS_KEY: t.Optional(t.String()),
R2_PUBLIC_URL: t.Optional(t.String()),
ADMIN_EMAIL: t.Optional(t.String()),
ADMIN_PASSWORD: t.Optional(t.String()),
RESEND_API_KEY: t.Optional(t.String()),
EMAIL_FROM_NAME: t.Optional(t.String({ default: "OGStack Team" })),
EMAIL_FROM_ADDRESS: t.Optional(t.String({ default: "noreply@ogstack.dev" })),
WEBSITE_URL: t.Optional(t.String({ default: "http://localhost:4001" })),
GITHUB_CLIENT_ID: t.Optional(t.String()),
GITHUB_CLIENT_SECRET: t.Optional(t.String()),
GITHUB_CALLBACK_URL: t.String({ default: "http://localhost:5000/api/auth/github/callback" }),
GOOGLE_CLIENT_ID: t.Optional(t.String()),
GOOGLE_CLIENT_SECRET: t.Optional(t.String()),
GOOGLE_CALLBACK_URL: t.String({ default: "http://localhost:5000/api/auth/google/callback" }),
STRIPE_SECRET_KEY: t.Optional(t.String()),
STRIPE_WEBHOOK_SECRET: t.Optional(t.String()),
FAL_API_KEY: t.Optional(t.String()),
PROMPT_PROVIDER: t.Optional(t.String()),
ANTHROPIC_API_KEY: t.Optional(t.String()),
ANTHROPIC_MODEL: t.Optional(t.String()),
OPENAI_API_KEY: t.Optional(t.String()),
OPENAI_MODEL: t.Optional(t.String()),
DEEPSEEK_API_KEY: t.Optional(t.String()),
DEEPSEEK_MODEL: t.Optional(t.String()),
OLLAMA_BASE_URL: t.Optional(t.String()),
OLLAMA_MODEL: t.Optional(t.String()),
LLAMACPP_BASE_URL: t.Optional(t.String()),
LLAMACPP_MODEL: t.Optional(t.String()),
CLAUDE_CODE_ENABLED: t.Optional(t.String()),
CLAUDE_CODE_MODEL: t.Optional(t.String()),
RENDER_PROVIDER: t.Optional(t.String()),
BROWSERLESS_URL: t.Optional(t.String()),
BROWSERLESS_TOKEN: t.Optional(t.String()),
SCRAPINGBEE_API_KEY: t.Optional(t.String()),
RECAPTCHA_PROJECT_ID: t.Optional(t.String()),
RECAPTCHA_SITE_KEY: t.Optional(t.String()),
RECAPTCHA_MIN_SCORE: t.Optional(t.String({ default: "0.5" })),
GOOGLE_APPLICATION_CREDENTIALS: t.Optional(t.String()),
});
export type Env = Static<typeof EnvSchema>;
declare global {
/**
* Augmenting the global `NodeJS.ProcessEnv` requires namespace + interface
* merging — there is no ES2015-module equivalent, so the lint rules below are
* disabled for this single declaration.
*/
// eslint-disable-next-line @typescript-eslint/no-namespace
namespace NodeJS {
// eslint-disable-next-line @typescript-eslint/no-empty-object-type
interface ProcessEnv extends Env {}
}
}
/**
* Validates environment variables against the defined schema and writes any
* schema-declared defaults back into `process.env`, so consumers can read
* `process.env.FOO!` directly without repeating `?? "default"` at every call site.
* Throws on validation failure to prevent startup with invalid configuration.
*/
export function validateEnv(): void {
const converted = Value.Convert(EnvSchema, { ...process.env }) as Record<string, unknown>;
const defaults = Value.Default(EnvSchema, converted) as Record<string, unknown>;
const errors = [...Value.Errors(EnvSchema, defaults)];
if (errors.length) {
const messages = errors.map((e) => ` ${e.path.slice(1)}: ${e.message}`).join("\n");
throw new Error(`Environment validation failed:\n${messages}`);
}
for (const [key, value] of Object.entries(defaults)) {
if (value !== undefined && !process.env[key]) {
process.env[key] = String(value);
}
}
}