Files
rxminder/config/unified.config.ts
William Valentin b59160eb10 feat: implement unified configuration as single source of truth
- Make unified config lazy-loaded to avoid initialization issues
- Replace direct config exports with getter functions
- Rewrite utils/env.ts to use unified config instead of scattered access
- Add show-config.js helper script for configuration management
- Type-safe configuration access throughout the app
- Smart defaults for all environments with environment overrides
- Eliminates scattered process.env and import.meta.env access
2025-09-08 21:23:44 -07:00

961 lines
26 KiB
TypeScript

/**
* Unified Application Configuration System
*
* This file serves as the single source of truth for all application configuration.
* It supports multiple environments (development, staging, production) and provides
* type-safe configuration for both frontend and backend components.
*
* Configuration sources (in order of precedence):
* 1. Environment variables
* 2. Environment-specific config files
* 3. Default values
*/
export type Environment = 'development' | 'staging' | 'production' | 'test';
export interface UnifiedConfig {
// Application Identity
app: {
name: string;
version: string;
environment: Environment;
baseUrl: string;
port: number;
};
// Database Configuration
database: {
url: string;
username: string;
password: string;
name: string;
useMock: boolean;
connectionTimeout: number;
retryAttempts: number;
};
// Container & Registry Configuration
container: {
registry: string;
repository: string;
tag: string;
imageUrl: string;
};
// Kubernetes Configuration
kubernetes: {
namespace: string;
ingressHost: string;
ingressClass: string;
certIssuer: string;
storageClass: string;
storageSize: string;
replicas: {
frontend: number;
database: number;
};
resources: {
frontend: {
requests: { memory: string; cpu: string };
limits: { memory: string; cpu: string };
};
database: {
requests: { memory: string; cpu: string };
limits: { memory: string; cpu: string };
};
};
};
// Authentication Configuration
auth: {
jwtSecret: string;
jwtExpiresIn: string;
refreshTokenExpiresIn: string;
emailVerificationExpiresIn: string;
bcryptRounds: number;
};
// Email Configuration
email: {
provider: 'mailgun' | 'smtp' | 'console';
mailgun?: {
apiKey: string;
domain: string;
baseUrl: string;
};
smtp?: {
host: string;
port: number;
secure: boolean;
username: string;
password: string;
};
fromName: string;
fromEmail: string;
};
// OAuth Configuration
oauth: {
google?: {
clientId: string;
clientSecret?: string;
};
github?: {
clientId: string;
clientSecret?: string;
};
};
// Feature Flags
features: {
enableEmailVerification: boolean;
enableOAuth: boolean;
enableAdminInterface: boolean;
enableMonitoring: boolean;
enableMetrics: boolean;
enableTracing: boolean;
enableRateLimiting: boolean;
enableSecurityHeaders: boolean;
debugMode: boolean;
hotReload: boolean;
};
// Performance & Monitoring
performance: {
cacheTimeout: number;
requestTimeout: number;
maxConnections: number;
enableCors: boolean;
corsOrigin: string | string[];
};
// Logging Configuration
logging: {
level: 'debug' | 'info' | 'warn' | 'error';
format: 'json' | 'text';
enableTimestamp: boolean;
enableColors: boolean;
};
// Security Configuration
security: {
enableHttps: boolean;
enableHsts: boolean;
enableCsp: boolean;
sessionSecret: string;
rateLimitRequests: number;
rateLimitWindow: number;
};
}
/**
* Environment-specific configuration overrides
*/
const environmentConfigs: Record<Environment, Partial<UnifiedConfig>> = {
development: {
app: {
name: 'rxminder',
version: '1.0.0',
environment: 'development',
baseUrl: 'http://localhost:5173',
port: 5173,
},
database: {
url: 'http://localhost:5984',
username: 'admin',
password: 'password',
name: 'rxminder_dev',
useMock: false,
connectionTimeout: 5000,
retryAttempts: 3,
},
kubernetes: {
namespace: 'rxminder-dev',
ingressHost: 'rxminder-dev.local',
ingressClass: 'nginx',
certIssuer: 'selfsigned',
storageClass: 'standard',
storageSize: '1Gi',
replicas: {
frontend: 1,
database: 1,
},
resources: {
frontend: {
requests: { memory: '128Mi', cpu: '50m' },
limits: { memory: '256Mi', cpu: '200m' },
},
database: {
requests: { memory: '256Mi', cpu: '100m' },
limits: { memory: '512Mi', cpu: '500m' },
},
},
},
features: {
enableEmailVerification: false,
enableOAuth: false,
enableAdminInterface: true,
enableMonitoring: false,
enableMetrics: false,
enableTracing: false,
enableRateLimiting: false,
enableSecurityHeaders: false,
debugMode: true,
hotReload: true,
},
logging: {
level: 'debug',
format: 'text',
enableTimestamp: true,
enableColors: true,
},
performance: {
cacheTimeout: 300, // 5 minutes
requestTimeout: 30000,
maxConnections: 100,
enableCors: true,
corsOrigin: '*',
},
},
staging: {
app: {
name: 'rxminder',
version: '1.0.0',
environment: 'staging',
baseUrl: 'https://staging.rxminder.com',
port: 80,
},
kubernetes: {
namespace: 'rxminder-staging',
ingressHost: 'staging.rxminder.com',
ingressClass: 'nginx',
certIssuer: 'letsencrypt-staging',
storageClass: 'ssd',
storageSize: '5Gi',
replicas: {
frontend: 2,
database: 1,
},
resources: {
frontend: {
requests: { memory: '256Mi', cpu: '100m' },
limits: { memory: '512Mi', cpu: '500m' },
},
database: {
requests: { memory: '512Mi', cpu: '200m' },
limits: { memory: '1Gi', cpu: '1000m' },
},
},
},
features: {
enableEmailVerification: true,
enableOAuth: true,
enableAdminInterface: true,
enableMonitoring: true,
enableMetrics: true,
enableTracing: false,
enableRateLimiting: true,
enableSecurityHeaders: true,
debugMode: false,
hotReload: false,
},
logging: {
level: 'info',
format: 'json',
enableTimestamp: true,
enableColors: false,
},
performance: {
cacheTimeout: 1800, // 30 minutes
requestTimeout: 60000,
maxConnections: 150,
enableCors: true,
corsOrigin: 'https://staging.rxminder.com',
},
},
production: {
app: {
name: 'rxminder',
version: '1.0.0',
environment: 'production',
baseUrl: 'https://rxminder.com',
port: 80,
},
database: {
url: 'http://rxminder-couchdb-service:5984',
username: 'admin',
password: 'secure_password',
name: 'rxminder_prod',
useMock: false,
connectionTimeout: 10000,
retryAttempts: 5,
},
kubernetes: {
namespace: 'rxminder-prod',
ingressHost: 'rxminder.com',
ingressClass: 'nginx',
certIssuer: 'letsencrypt-prod',
storageClass: 'ssd',
storageSize: '10Gi',
replicas: {
frontend: 3,
database: 1,
},
resources: {
frontend: {
requests: { memory: '256Mi', cpu: '100m' },
limits: { memory: '512Mi', cpu: '500m' },
},
database: {
requests: { memory: '512Mi', cpu: '200m' },
limits: { memory: '1Gi', cpu: '1000m' },
},
},
},
features: {
enableEmailVerification: true,
enableOAuth: true,
enableAdminInterface: true,
enableMonitoring: true,
enableMetrics: true,
enableTracing: true,
enableRateLimiting: true,
enableSecurityHeaders: true,
debugMode: false,
hotReload: false,
},
logging: {
level: 'warn',
format: 'json',
enableTimestamp: true,
enableColors: false,
},
performance: {
cacheTimeout: 3600, // 1 hour
requestTimeout: 120000,
maxConnections: 200,
enableCors: true,
corsOrigin: 'https://rxminder.com',
},
security: {
enableHttps: true,
enableHsts: true,
enableCsp: true,
sessionSecret: 'secure-session-secret-key',
rateLimitRequests: 100,
rateLimitWindow: 900000, // 15 minutes
},
},
test: {
app: {
name: 'rxminder',
version: '1.0.0',
environment: 'test',
baseUrl: 'http://localhost:3000',
port: 3000,
},
database: {
url: 'http://localhost:5984',
username: 'test',
password: 'test',
name: 'rxminder_test',
useMock: true,
connectionTimeout: 3000,
retryAttempts: 1,
},
features: {
enableEmailVerification: false,
enableOAuth: false,
enableAdminInterface: false,
enableMonitoring: false,
enableMetrics: false,
enableTracing: false,
enableRateLimiting: false,
enableSecurityHeaders: false,
debugMode: true,
hotReload: false,
},
logging: {
level: 'error',
format: 'text',
enableTimestamp: false,
enableColors: false,
},
email: {
provider: 'console' as const,
fromName: 'Test RxMinder',
fromEmail: 'test@rxminder.local',
},
},
};
/**
* Default configuration values
*/
const defaultConfig: UnifiedConfig = {
app: {
name: 'RxMinder',
version: '1.0.0',
environment: 'development',
baseUrl: 'http://localhost:5173',
port: 5173,
},
database: {
url: 'http://localhost:5984',
username: 'admin',
password: 'changeme',
name: 'meds_app',
useMock: false,
connectionTimeout: 30000,
retryAttempts: 3,
},
container: {
registry: 'gitea-http.taildb3494.ts.net',
repository: 'will/meds',
tag: 'latest',
imageUrl: '', // Will be computed
},
kubernetes: {
namespace: 'rxminder',
ingressHost: 'rxminder.local',
ingressClass: 'nginx',
certIssuer: 'letsencrypt-prod',
storageClass: 'longhorn',
storageSize: '1Gi',
replicas: {
frontend: 1,
database: 1,
},
resources: {
frontend: {
requests: { memory: '128Mi', cpu: '50m' },
limits: { memory: '256Mi', cpu: '200m' },
},
database: {
requests: { memory: '256Mi', cpu: '100m' },
limits: { memory: '512Mi', cpu: '500m' },
},
},
},
auth: {
jwtSecret: 'your-super-secret-jwt-key-change-in-production',
jwtExpiresIn: '1h',
refreshTokenExpiresIn: '7d',
emailVerificationExpiresIn: '24h',
bcryptRounds: 12,
},
email: {
provider: 'console',
mailgun: {
apiKey: '',
domain: '',
baseUrl: 'https://api.mailgun.net/v3',
},
fromName: 'RxMinder',
fromEmail: 'noreply@rxminder.com',
},
oauth: {
google: {
clientId: '',
},
github: {
clientId: '',
},
},
features: {
enableEmailVerification: true,
enableOAuth: true,
enableAdminInterface: true,
enableMonitoring: false,
enableMetrics: false,
enableTracing: false,
enableRateLimiting: false,
enableSecurityHeaders: false,
debugMode: false,
hotReload: false,
},
performance: {
cacheTimeout: 1800,
requestTimeout: 30000,
maxConnections: 100,
enableCors: true,
corsOrigin: '*',
},
logging: {
level: 'info',
format: 'json',
enableTimestamp: true,
enableColors: false,
},
security: {
enableHttps: false,
enableHsts: false,
enableCsp: false,
sessionSecret: 'your-session-secret-change-in-production',
rateLimitRequests: 100,
rateLimitWindow: 900000, // 15 minutes
},
};
/**
* Get environment variable with fallback
* Reads from .env file via process.env or import.meta.env
*/
function getEnvVar(key: string, fallback: string = ''): string {
// First try process.env (Node.js, includes .env via dotenv or build process)
if (typeof process !== 'undefined' && process.env && process.env[key]) {
return process.env[key]!;
}
// Then try import.meta.env (Vite/browser, includes .env via Vite's loadEnv)
if (
typeof import.meta !== 'undefined' &&
import.meta.env &&
import.meta.env[key]
) {
return import.meta.env[key];
}
// Use fallback if not found
return fallback;
}
/**
* Get boolean environment variable
*/
function getBoolEnvVar(key: string, fallback: boolean = false): boolean {
const value = getEnvVar(key);
if (value === '') return fallback;
return value.toLowerCase() === 'true' || value === '1';
}
/**
* Get number environment variable
*/
function getNumberEnvVar(key: string, fallback: number = 0): number {
const value = getEnvVar(key);
if (value === '') return fallback;
const parsed = parseInt(value, 10);
return isNaN(parsed) ? fallback : parsed;
}
/**
* Load configuration from environment variables (.env file)
* This provides environment-specific overrides for the unified config
*/
function loadFromEnvironment(config: UnifiedConfig): UnifiedConfig {
return {
...config,
app: {
...config.app,
name: getEnvVar('APP_NAME', config.app.name),
version: getEnvVar('APP_VERSION', config.app.version),
baseUrl: getEnvVar('APP_BASE_URL', config.app.baseUrl),
port: getNumberEnvVar('PORT', config.app.port),
},
database: {
...config.database,
url:
getEnvVar('VITE_COUCHDB_URL') ||
getEnvVar('COUCHDB_URL', config.database.url),
username:
getEnvVar('VITE_COUCHDB_USER') ||
getEnvVar('COUCHDB_USER', config.database.username),
password:
getEnvVar('VITE_COUCHDB_PASSWORD') ||
getEnvVar('COUCHDB_PASSWORD', config.database.password),
name: getEnvVar('COUCHDB_DATABASE_NAME', config.database.name),
useMock: getBoolEnvVar('USE_MOCK_DB', config.database.useMock),
},
container: {
...config.container,
registry: getEnvVar('CONTAINER_REGISTRY', config.container.registry),
repository: getEnvVar(
'CONTAINER_REPOSITORY',
config.container.repository
),
tag: getEnvVar('CONTAINER_TAG', config.container.tag),
},
kubernetes: {
...config.kubernetes,
ingressHost: getEnvVar('INGRESS_HOST', config.kubernetes.ingressHost),
storageClass: getEnvVar('STORAGE_CLASS', config.kubernetes.storageClass),
storageSize: getEnvVar('STORAGE_SIZE', config.kubernetes.storageSize),
},
auth: {
...config.auth,
jwtSecret: getEnvVar('JWT_SECRET', config.auth.jwtSecret),
jwtExpiresIn: getEnvVar('JWT_EXPIRES_IN', config.auth.jwtExpiresIn),
},
security: {
...config.security,
sessionSecret: getEnvVar('SESSION_SECRET', config.security.sessionSecret),
},
email: {
...config.email,
provider:
(getEnvVar('EMAIL_PROVIDER', config.email.provider) as
| 'mailgun'
| 'smtp'
| 'console') || config.email.provider,
mailgun: {
...config.email.mailgun!,
apiKey:
getEnvVar('VITE_MAILGUN_API_KEY') ||
getEnvVar('MAILGUN_API_KEY', config.email.mailgun?.apiKey || ''),
domain:
getEnvVar('VITE_MAILGUN_DOMAIN') ||
getEnvVar('MAILGUN_DOMAIN', config.email.mailgun?.domain || ''),
},
fromName:
getEnvVar('VITE_MAILGUN_FROM_NAME') ||
getEnvVar('MAILGUN_FROM_NAME', config.email.fromName),
fromEmail:
getEnvVar('VITE_MAILGUN_FROM_EMAIL') ||
getEnvVar('MAILGUN_FROM_EMAIL', config.email.fromEmail),
},
oauth: {
google: {
clientId:
getEnvVar('VITE_GOOGLE_CLIENT_ID') ||
getEnvVar('GOOGLE_CLIENT_ID', config.oauth.google?.clientId || ''),
clientSecret: getEnvVar('GOOGLE_CLIENT_SECRET'),
},
github: {
clientId:
getEnvVar('VITE_GITHUB_CLIENT_ID') ||
getEnvVar('GITHUB_CLIENT_ID', config.oauth.github?.clientId || ''),
clientSecret: getEnvVar('GITHUB_CLIENT_SECRET'),
},
},
features: {
...config.features,
enableEmailVerification: getBoolEnvVar(
'ENABLE_EMAIL_VERIFICATION',
config.features.enableEmailVerification
),
enableOAuth: getBoolEnvVar('ENABLE_OAUTH', config.features.enableOAuth),
enableAdminInterface: getBoolEnvVar(
'ENABLE_ADMIN_INTERFACE',
config.features.enableAdminInterface
),
enableMonitoring: getBoolEnvVar(
'ENABLE_MONITORING',
config.features.enableMonitoring
),
enableMetrics: getBoolEnvVar(
'ENABLE_METRICS',
config.features.enableMetrics
),
debugMode: getBoolEnvVar('DEBUG_MODE', config.features.debugMode),
},
logging: {
...config.logging,
level:
(getEnvVar('LOG_LEVEL', config.logging.level) as
| 'debug'
| 'info'
| 'warn'
| 'error') || config.logging.level,
},
};
}
/**
* Deep merge two objects
*/
function deepMerge<T>(target: T, source: Partial<T>): T {
const result = { ...target };
for (const key in source) {
if (
source[key] &&
typeof source[key] === 'object' &&
!Array.isArray(source[key])
) {
result[key as keyof T] = deepMerge(
result[key as keyof T],
source[key] as Partial<T[keyof T]>
);
} else if (source[key] !== undefined) {
result[key as keyof T] = source[key] as T[keyof T];
}
}
return result;
}
/**
* Validate configuration
*/
function validateConfig(config: UnifiedConfig): void {
const errors: string[] = [];
const warnings: string[] = [];
// Production-specific validations (only when NODE_ENV is explicitly production)
const isActualProduction = getEnvVar('NODE_ENV') === 'production';
if (config.app.environment === 'production' && isActualProduction) {
if (config.auth.jwtSecret === defaultConfig.auth.jwtSecret) {
errors.push('JWT_SECRET must be changed in production');
}
if (
config.security.sessionSecret === defaultConfig.security.sessionSecret
) {
errors.push('SESSION_SECRET must be changed in production');
}
if (config.database.password === defaultConfig.database.password) {
warnings.push(
'Consider changing default database password in production'
);
}
}
// Email configuration validation
if (
config.features.enableEmailVerification &&
config.email.provider === 'mailgun'
) {
if (!config.email.mailgun?.apiKey) {
warnings.push(
'MAILGUN_API_KEY not configured - email will fall back to console'
);
}
if (!config.email.mailgun?.domain) {
warnings.push('MAILGUN_DOMAIN not configured - email may not work');
}
}
// OAuth configuration validation
if (config.features.enableOAuth) {
if (!config.oauth.google?.clientId && !config.oauth.github?.clientId) {
warnings.push('No OAuth client IDs configured - OAuth will be disabled');
}
}
// Log warnings and throw errors
if (warnings.length > 0) {
console.warn('⚠️ Configuration warnings:', warnings);
}
if (errors.length > 0) {
console.error('❌ Configuration errors:', errors);
throw new Error(`Configuration validation failed: ${errors.join(', ')}`);
}
}
/**
* Create unified configuration
* Uses .env file as primary source for environment-specific overrides
*/
export function createUnifiedConfig(): UnifiedConfig {
// Determine environment from .env file or NODE_ENV
const nodeEnv = getEnvVar('NODE_ENV', 'development') as Environment;
const environment = ['development', 'staging', 'production', 'test'].includes(
nodeEnv
)
? (nodeEnv as Environment)
: 'development';
return createUnifiedConfigForEnvironment(environment);
}
/**
* Create unified configuration for specific environment
* Priority: .env file overrides > environment-specific config > defaults
*/
export function createUnifiedConfigForEnvironment(
environment: Environment
): UnifiedConfig {
// Start with default config
let config = { ...defaultConfig };
config.app.environment = environment;
// Apply environment-specific overrides (lower priority)
if (environmentConfigs[environment]) {
config = deepMerge(config, environmentConfigs[environment]);
}
// Apply .env file overrides (highest priority)
config = loadFromEnvironment(config);
// Compute derived values after all overrides
config.container.imageUrl = `${config.container.registry}/${config.container.repository}:${config.container.tag}`;
// Validate configuration
if (environment !== 'test') {
validateConfig(config);
}
return config;
}
/**
* Lazy-loaded singleton configuration instance
*/
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 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
*/
export const isProduction = () =>
unifiedConfig.app.environment === 'production';
export const isDevelopment = () =>
unifiedConfig.app.environment === 'development';
export const isStaging = () => unifiedConfig.app.environment === 'staging';
export const isTest = () => unifiedConfig.app.environment === 'test';
/**
* Export configuration as environment variables (for scripts and Kubernetes)
*/
export function exportAsEnvVars(
config?: UnifiedConfig
): Record<string, string> {
const configToUse = config || unifiedConfig;
return {
// Application
APP_NAME: configToUse.app.name,
APP_VERSION: configToUse.app.version,
NODE_ENV: configToUse.app.environment,
APP_BASE_URL: configToUse.app.baseUrl,
PORT: configToUse.app.port.toString(),
// Database
COUCHDB_URL: configToUse.database.url,
COUCHDB_USER: configToUse.database.username,
COUCHDB_PASSWORD: configToUse.database.password,
COUCHDB_DATABASE_NAME: configToUse.database.name,
USE_MOCK_DB: configToUse.database.useMock.toString(),
// Container
CONTAINER_REGISTRY: configToUse.container.registry,
CONTAINER_REPOSITORY: configToUse.container.repository,
CONTAINER_TAG: configToUse.container.tag,
DOCKER_IMAGE: configToUse.container.imageUrl,
// Kubernetes
KUBERNETES_NAMESPACE: configToUse.kubernetes.namespace,
INGRESS_HOST: configToUse.kubernetes.ingressHost,
INGRESS_CLASS: configToUse.kubernetes.ingressClass,
CERT_MANAGER_ISSUER: configToUse.kubernetes.certIssuer,
STORAGE_CLASS: configToUse.kubernetes.storageClass,
STORAGE_SIZE: configToUse.kubernetes.storageSize,
// Features
ENABLE_EMAIL_VERIFICATION:
configToUse.features.enableEmailVerification.toString(),
ENABLE_OAUTH: configToUse.features.enableOAuth.toString(),
ENABLE_ADMIN_INTERFACE:
configToUse.features.enableAdminInterface.toString(),
ENABLE_MONITORING: configToUse.features.enableMonitoring.toString(),
ENABLE_METRICS: configToUse.features.enableMetrics.toString(),
DEBUG_MODE: configToUse.features.debugMode.toString(),
// Logging
LOG_LEVEL: configToUse.logging.level,
LOG_FORMAT: configToUse.logging.format,
// Performance
CACHE_TTL: configToUse.performance.cacheTimeout.toString(),
REQUEST_TIMEOUT: configToUse.performance.requestTimeout.toString(),
MAX_CONNECTIONS: configToUse.performance.maxConnections.toString(),
ENABLE_CORS: configToUse.performance.enableCors.toString(),
CORS_ORIGIN: Array.isArray(configToUse.performance.corsOrigin)
? configToUse.performance.corsOrigin.join(',')
: configToUse.performance.corsOrigin,
// Replicas (for Kubernetes)
FRONTEND_REPLICAS: configToUse.kubernetes.replicas.frontend.toString(),
DATABASE_REPLICAS: configToUse.kubernetes.replicas.database.toString(),
// Resources (for Kubernetes)
FRONTEND_MEMORY_REQUEST:
configToUse.kubernetes.resources.frontend.requests.memory,
FRONTEND_CPU_REQUEST:
configToUse.kubernetes.resources.frontend.requests.cpu,
FRONTEND_MEMORY_LIMIT:
configToUse.kubernetes.resources.frontend.limits.memory,
FRONTEND_CPU_LIMIT: configToUse.kubernetes.resources.frontend.limits.cpu,
DATABASE_MEMORY_REQUEST:
configToUse.kubernetes.resources.database.requests.memory,
DATABASE_CPU_REQUEST:
configToUse.kubernetes.resources.database.requests.cpu,
DATABASE_MEMORY_LIMIT:
configToUse.kubernetes.resources.database.limits.memory,
DATABASE_CPU_LIMIT: configToUse.kubernetes.resources.database.limits.cpu,
};
}
/**
* Debug helper to log current configuration
*/
export function logConfig(): void {
if (unifiedConfig.features.debugMode) {
console.warn('🔧 Unified Configuration (Single Source of Truth):', {
environment: unifiedConfig.app.environment,
app: unifiedConfig.app.name,
version: unifiedConfig.app.version,
baseUrl: unifiedConfig.app.baseUrl,
database: {
url: unifiedConfig.database.url,
useMock: unifiedConfig.database.useMock,
},
container: {
registry: unifiedConfig.container.registry,
repository: unifiedConfig.container.repository,
imageUrl: unifiedConfig.container.imageUrl,
},
features: unifiedConfig.features,
configSource: '.env file overrides applied',
});
}
}
// Auto-log in development
if (isDevelopment() && typeof window !== 'undefined') {
logConfig();
}