Spaces:
Configuration error
Configuration error
| /** | |
| * 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() | |