open-prompt / src /lib /env.ts
anky2002's picture
feat: add environment variable validation with clear startup errors
e31f25c verified
/**
* Environment variable validation.
* Import this at the top of your app to get clear startup errors
* instead of cryptic runtime failures.
*
* Usage: import '@/lib/env' in layout.tsx or next.config.ts
*/
type EnvVar = {
name: string
required: boolean
description: string
}
const ENV_VARS: EnvVar[] = [
// Required
{ name: 'DATABASE_URL', required: true, description: 'PostgreSQL connection string (Neon)' },
{ name: 'NEXT_PUBLIC_STACK_PROJECT_ID', required: true, description: 'Stack Auth project ID' },
{ name: 'NEXT_PUBLIC_STACK_PUBLISHABLE_CLIENT_KEY', required: true, description: 'Stack Auth publishable key' },
{ name: 'STACK_SECRET_SERVER_KEY', required: true, description: 'Stack Auth server secret key' },
// AI Providers (at least one required)
{ name: 'OPENAI_API_KEY', required: false, description: 'OpenAI API key (for GPT-4o models)' },
{ name: 'ANTHROPIC_API_KEY', required: false, description: 'Anthropic API key (for Claude models)' },
{ name: 'GOOGLE_AI_API_KEY', required: false, description: 'Google AI API key (for Gemini models)' },
// Optional services
{ name: 'UPSTASH_REDIS_REST_URL', required: false, description: 'Upstash Redis URL (caching & rate limiting)' },
{ name: 'UPSTASH_REDIS_REST_TOKEN', required: false, description: 'Upstash Redis auth token' },
{ name: 'NEXT_PUBLIC_TURNSTILE_SITE_KEY', required: false, description: 'Cloudflare Turnstile site key (bot protection)' },
{ name: 'TURNSTILE_SECRET_KEY', required: false, description: 'Cloudflare Turnstile secret key' },
{ name: 'STRIPE_SECRET_KEY', required: false, description: 'Stripe secret key (payments)' },
{ name: 'STRIPE_WEBHOOK_SECRET', required: false, description: 'Stripe webhook signing secret' },
{ name: 'NEXT_PUBLIC_APP_URL', required: false, description: 'Public URL of the app (defaults to open-prompt.netlify.app)' },
]
export function validateEnv(): { valid: boolean; errors: string[]; warnings: string[] } {
const errors: string[] = []
const warnings: string[] = []
for (const envVar of ENV_VARS) {
const value = process.env[envVar.name]
if (envVar.required && !value) {
errors.push(`❌ Missing required: ${envVar.name}${envVar.description}`)
} else if (!envVar.required && !value) {
warnings.push(`⚠️ Optional missing: ${envVar.name}${envVar.description}`)
}
}
// Check that at least one AI provider is configured
const hasAiProvider = !!(
process.env.OPENAI_API_KEY ||
process.env.ANTHROPIC_API_KEY ||
process.env.GOOGLE_AI_API_KEY
)
if (!hasAiProvider) {
errors.push('❌ No AI provider configured. Set at least one of: OPENAI_API_KEY, ANTHROPIC_API_KEY, GOOGLE_AI_API_KEY')
}
// Check Redis pair (both or neither)
const hasRedisUrl = !!process.env.UPSTASH_REDIS_REST_URL
const hasRedisToken = !!process.env.UPSTASH_REDIS_REST_TOKEN
if (hasRedisUrl !== hasRedisToken) {
warnings.push('⚠️ Redis partially configured. Set both UPSTASH_REDIS_REST_URL and UPSTASH_REDIS_REST_TOKEN, or neither.')
}
// Check Turnstile pair
const hasTurnstileSite = !!process.env.NEXT_PUBLIC_TURNSTILE_SITE_KEY
const hasTurnstileSecret = !!process.env.TURNSTILE_SECRET_KEY
if (hasTurnstileSite !== hasTurnstileSecret) {
warnings.push('⚠️ Turnstile partially configured. Set both NEXT_PUBLIC_TURNSTILE_SITE_KEY and TURNSTILE_SECRET_KEY, or neither.')
}
return {
valid: errors.length === 0,
errors,
warnings,
}
}
/**
* Log environment validation results during startup.
* Call this in development to catch issues early.
*/
export function logEnvValidation(): void {
// Only validate in development or on first build
if (process.env.NODE_ENV === 'production' && process.env.NEXT_PHASE !== 'phase-production-build') {
return
}
const { valid, errors, warnings } = validateEnv()
if (errors.length > 0) {
console.error('\n🔴 Environment Validation Failed:\n')
errors.forEach(e => console.error(` ${e}`))
console.error('')
}
if (warnings.length > 0 && process.env.NODE_ENV === 'development') {
console.warn('\n🟡 Environment Warnings:\n')
warnings.forEach(w => console.warn(` ${w}`))
console.warn('')
}
if (valid && process.env.NODE_ENV === 'development') {
console.log('\n✅ Environment validation passed\n')
}
}
// Auto-run validation when this module is imported
logEnvValidation()