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
This commit is contained in:
@@ -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
|
||||
|
||||
187
show-config.js
Normal file
187
show-config.js
Normal file
@@ -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('');
|
||||
197
utils/env.ts
197
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<string, string> } } }
|
||||
).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;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user