diff --git a/config/unified.config.ts b/config/unified.config.ts index 48c389c..9ea25d5 100644 --- a/config/unified.config.ts +++ b/config/unified.config.ts @@ -809,24 +809,33 @@ export function createUnifiedConfigForEnvironment( } /** - * Singleton configuration instance + * Lazy-loaded singleton configuration instance */ -export const unifiedConfig = createUnifiedConfig(); +let _unifiedConfig: UnifiedConfig | null = null; + +export const unifiedConfig: UnifiedConfig = new Proxy({} as UnifiedConfig, { + get(target, prop) { + if (!_unifiedConfig) { + _unifiedConfig = createUnifiedConfig(); + } + return _unifiedConfig[prop as keyof UnifiedConfig]; + }, +}); /** * Export specific configuration sections for convenience */ -export const appConfig = unifiedConfig.app; -export const databaseConfig = unifiedConfig.database; -export const containerConfig = unifiedConfig.container; -export const kubernetesConfig = unifiedConfig.kubernetes; -export const authConfig = unifiedConfig.auth; -export const emailConfig = unifiedConfig.email; -export const oauthConfig = unifiedConfig.oauth; -export const featureFlags = unifiedConfig.features; -export const performanceConfig = unifiedConfig.performance; -export const loggingConfig = unifiedConfig.logging; -export const securityConfig = unifiedConfig.security; +export const getAppConfig = () => unifiedConfig.app; +export const getDatabaseConfig = () => unifiedConfig.database; +export const getContainerConfig = () => unifiedConfig.container; +export const getKubernetesConfig = () => unifiedConfig.kubernetes; +export const getAuthConfig = () => unifiedConfig.auth; +export const getEmailConfig = () => unifiedConfig.email; +export const getOAuthConfig = () => unifiedConfig.oauth; +export const getFeatureFlags = () => unifiedConfig.features; +export const getPerformanceConfig = () => unifiedConfig.performance; +export const getLoggingConfig = () => unifiedConfig.logging; +export const getSecurityConfig = () => unifiedConfig.security; /** * Utility functions diff --git a/show-config.js b/show-config.js new file mode 100644 index 0000000..67bf397 --- /dev/null +++ b/show-config.js @@ -0,0 +1,187 @@ +#!/usr/bin/env bun + +/** + * Configuration Helper Script + * + * This script shows the current unified configuration and provides + * examples of environment variables that can be set to override defaults. + * + * Usage: + * bun show-config.js # Show current config + * bun show-config.js --env # Show as environment variables + * bun show-config.js --help # Show help + */ + +/* eslint-disable no-console */ + +import { unifiedConfig, exportAsEnvVars } from './config/unified.config'; + +const args = process.argv.slice(2); +const showEnv = args.includes('--env'); +const showHelp = args.includes('--help'); + +if (showHelp) { + console.log(` +Configuration Helper - Single Source of Truth + +USAGE: + bun show-config.js Show current unified config + bun show-config.js --env Show as environment variables + bun show-config.js --help Show this help + +DESCRIPTION: + This app uses a unified configuration system as the single source of truth. + All settings come from config/unified.config.ts with environment overrides. + +COMMON ENVIRONMENT OVERRIDES: + # Application + NODE_ENV=production + APP_NAME="My Custom App Name" + APP_BASE_URL=https://myapp.com + + # Database (required for production) + VITE_COUCHDB_URL=http://localhost:5984 + VITE_COUCHDB_USER=admin + VITE_COUCHDB_PASSWORD=secure-password + + # Authentication (required for production) + JWT_SECRET=your-secure-jwt-secret + SESSION_SECRET=your-secure-session-secret + + # Email (optional) + VITE_MAILGUN_API_KEY=key-abc123 + VITE_MAILGUN_DOMAIN=mg.example.com + + # OAuth (optional) + VITE_GOOGLE_CLIENT_ID=your-google-client-id + VITE_GITHUB_CLIENT_ID=your-github-client-id + +HOW IT WORKS: + 1. Unified config provides smart defaults for all environments + 2. Environment variables override specific settings when needed + 3. No more scattered .env files or hardcoded values + 4. Type-safe configuration throughout the app + +EXAMPLES: + # Development with custom database + VITE_COUCHDB_URL=http://dev-db:5984 bun run dev + + # Production build with custom settings + NODE_ENV=production APP_NAME="Prod App" make docker-build + + # Check current config + bun show-config.js +`); + process.exit(0); +} + +console.log('🔧 Unified Configuration - Single Source of Truth\n'); + +if (showEnv) { + console.log('📋 Current Configuration as Environment Variables:\n'); + const envVars = exportAsEnvVars(); + + // Group by category for better readability + const categories = { + Application: [ + 'APP_NAME', + 'APP_VERSION', + 'APP_BASE_URL', + 'NODE_ENV', + 'PORT', + ], + Database: [ + 'VITE_COUCHDB_URL', + 'VITE_COUCHDB_USER', + 'VITE_COUCHDB_PASSWORD', + 'COUCHDB_DATABASE_NAME', + ], + Authentication: ['JWT_SECRET', 'JWT_EXPIRES_IN', 'SESSION_SECRET'], + Email: [ + 'EMAIL_PROVIDER', + 'VITE_MAILGUN_API_KEY', + 'VITE_MAILGUN_DOMAIN', + 'MAILGUN_FROM_NAME', + 'MAILGUN_FROM_EMAIL', + ], + OAuth: [ + 'VITE_GOOGLE_CLIENT_ID', + 'GOOGLE_CLIENT_SECRET', + 'VITE_GITHUB_CLIENT_ID', + 'GITHUB_CLIENT_SECRET', + ], + Features: ['ENABLE_EMAIL_VERIFICATION', 'ENABLE_OAUTH', 'DEBUG_MODE'], + Logging: ['LOG_LEVEL', 'LOG_FORMAT'], + }; + + Object.entries(categories).forEach(([category, keys]) => { + console.log(`${category}:`); + keys.forEach(key => { + if (envVars[key] !== undefined) { + const value = envVars[key]; + // Mask sensitive values + const maskedValue = + key.includes('SECRET') || + key.includes('PASSWORD') || + key.includes('API_KEY') + ? '***masked***' + : value; + console.log(` ${key}=${maskedValue}`); + } + }); + console.log(''); + }); +} else { + console.log('📊 Current Unified Configuration:\n'); + + // Show key configuration sections + console.log('Application:'); + console.log(` Name: ${unifiedConfig.app.name}`); + console.log(` Environment: ${unifiedConfig.app.environment}`); + console.log(` Base URL: ${unifiedConfig.app.baseUrl}`); + console.log(` Port: ${unifiedConfig.app.port}`); + console.log(''); + + console.log('Database:'); + console.log(` URL: ${unifiedConfig.database.url}`); + console.log(` Username: ${unifiedConfig.database.username}`); + console.log( + ` Password: ${unifiedConfig.database.password ? '***set***' : '***not set***'}` + ); + console.log(` Mock Mode: ${unifiedConfig.database.useMock}`); + console.log(''); + + console.log('Authentication:'); + console.log( + ` JWT Secret: ${unifiedConfig.auth.jwtSecret ? '***set***' : '***not set***'}` + ); + console.log(` JWT Expires: ${unifiedConfig.auth.jwtExpiresIn}`); + console.log(''); + + console.log('Features:'); + console.log( + ` Email Verification: ${unifiedConfig.features.enableEmailVerification}` + ); + console.log(` OAuth: ${unifiedConfig.features.enableOAuth}`); + console.log(` Debug Mode: ${unifiedConfig.features.debugMode}`); + console.log(''); + + console.log('Email:'); + console.log(` Provider: ${unifiedConfig.email.provider}`); + console.log( + ` From: ${unifiedConfig.email.fromName} <${unifiedConfig.email.fromEmail}>` + ); + console.log(''); + + console.log('Logging:'); + console.log(` Level: ${unifiedConfig.logging.level}`); + console.log(` Format: ${unifiedConfig.logging.format}`); + console.log(''); +} + +console.log('💡 Tips:'); +console.log(' • Use --env to see current config as environment variables'); +console.log(' • Use --help to see configuration documentation'); +console.log(' • Set environment variables to override defaults'); +console.log(' • All configuration comes from config/unified.config.ts'); +console.log(''); diff --git a/utils/env.ts b/utils/env.ts index 4fe93df..8e30daf 100644 --- a/utils/env.ts +++ b/utils/env.ts @@ -1,59 +1,29 @@ -// Environment utility to safely access environment variables -// Compatible with both Vite (browser) and Node.js (Jest/server) +/** + * Environment Utilities - Single Source of Truth + * + * This module provides environment utilities that use the unified config + * as the single source of truth for all environment variables. + * + * Use this instead of directly accessing process.env or import.meta.env + */ -export interface EnvConfig { - VITE_COUCHDB_URL?: string; - VITE_COUCHDB_USERNAME?: string; - VITE_COUCHDB_PASSWORD?: string; - VITE_MAILGUN_API_KEY?: string; - VITE_MAILGUN_DOMAIN?: string; - VITE_MAILGUN_BASE_URL?: string; - VITE_MAILGUN_FROM_NAME?: string; - VITE_MAILGUN_FROM_EMAIL?: string; - NODE_ENV?: string; - COUCHDB_URL?: string; - [key: string]: string | undefined; -} +import { + getAppConfig, + getDatabaseConfig, + getAuthConfig, + getEmailConfig, + getOAuthConfig, + getFeatureFlags, + getLoggingConfig, + getSecurityConfig, + unifiedConfig, +} from '../config/unified.config'; /** - * Safely get environment variables from various sources - * Works in both browser (Vite) and Node.js (Jest/server) environments + * Get the current environment */ -export function getEnv(): EnvConfig { - let env: EnvConfig = {}; - - // Try to get from import.meta.env (Vite/browser) - try { - if (typeof globalThis !== 'undefined' && 'import' in globalThis) { - const importMeta = ( - globalThis as { import?: { meta?: { env?: Record } } } - ).import?.meta; - if (importMeta?.env) { - env = { ...env, ...importMeta.env }; - } - } - } catch (_e) { - // Ignore errors accessing import.meta - } - - // Try to get from process.env (Node.js) - try { - if (typeof process !== 'undefined' && process.env) { - env = { ...env, ...process.env }; - } - } catch (_e) { - // Ignore errors accessing process.env - } - - return env; -} - -/** - * Get a specific environment variable with optional fallback - */ -export function getEnvVar(key: string, fallback?: string): string | undefined { - const env = getEnv(); - return env[key] || fallback; +export function getEnvironment(): string { + return getAppConfig().environment; } /** @@ -74,35 +44,110 @@ export function isNode(): boolean { * Check if we're running in a test environment */ export function isTest(): boolean { - const env = getEnv(); + return getAppConfig().environment === 'test'; +} - // Check for Jest environment - if ( - typeof global !== 'undefined' && - 'expect' in global && - 'describe' in global - ) { - return true; - } - - // Check for Node.js test environment variables - if (typeof process !== 'undefined' && process.env) { - if (process.env.NODE_ENV === 'test' || process.env.JEST_WORKER_ID) { - return true; - } - } - - // Check environment variables - return env.NODE_ENV === 'test'; +/** + * Check if we're running in development + */ +export function isDevelopment(): boolean { + return getAppConfig().environment === 'development'; } /** * Check if we're running in production */ export function isProduction(): boolean { - const env = getEnv(); - return ( - env.NODE_ENV === 'production' || - (typeof process !== 'undefined' && process.env?.NODE_ENV === 'production') - ); + return getAppConfig().environment === 'production'; +} + +/** + * Check if we're running in staging + */ +export function isStaging(): boolean { + return getAppConfig().environment === 'staging'; +} + +/** + * Get application configuration + */ +export { getAppConfig }; + +/** + * Get database configuration + */ +export { getDatabaseConfig }; + +/** + * Get authentication configuration + */ +export { getAuthConfig }; + +/** + * Get email configuration + */ +export { getEmailConfig }; + +/** + * Get OAuth configuration + */ +export { getOAuthConfig }; + +/** + * Get feature flags + */ +export { getFeatureFlags }; + +/** + * Get logging configuration + */ +export { getLoggingConfig }; + +/** + * Get security configuration + */ +export { getSecurityConfig }; + +/** + * Get the full unified configuration + * Use this sparingly - prefer the specific getters above + */ +export function getUnifiedConfig() { + return unifiedConfig; +} + +/** + * Legacy compatibility - get a specific environment variable + * @deprecated Use the unified config getters instead + */ +export function getEnvVar(key: string, fallback?: string): string | undefined { + console.warn( + `getEnvVar('${key}') is deprecated. Use unified config instead.` + ); + + // Try to map common environment variables to unified config + switch (key) { + case 'NODE_ENV': + return getAppConfig().environment; + case 'APP_NAME': + case 'VITE_APP_NAME': + return getAppConfig().name; + case 'APP_BASE_URL': + return getAppConfig().baseUrl; + case 'VITE_COUCHDB_URL': + case 'COUCHDB_URL': + return getDatabaseConfig().url; + case 'VITE_COUCHDB_USER': + case 'COUCHDB_USER': + return getDatabaseConfig().username; + case 'VITE_COUCHDB_PASSWORD': + case 'COUCHDB_PASSWORD': + return getDatabaseConfig().password; + case 'JWT_SECRET': + return getAuthConfig().jwtSecret; + case 'LOG_LEVEL': + return getLoggingConfig().level; + default: + return fallback; + } }