#!/usr/bin/env python3 """ Frontend Configuration Builder Generates frontend/config.js from backend .env files to maintain single source of truth. This eliminates duplication and ensures frontend/backend configuration consistency. Usage: python scripts/build-frontend-config.py python scripts/build-frontend-config.py --env production python scripts/build-frontend-config.py --verbose """ import os import sys import json import argparse from pathlib import Path from datetime import datetime from dotenv import load_dotenv class FrontendConfigBuilder: def __init__(self, verbose=False): self.verbose = verbose self.project_root = Path(__file__).parent.parent self.environments = ["development", "production", "staging"] def log(self, message): """Print message if verbose mode is enabled""" if self.verbose: print(f"[{datetime.now().strftime('%H:%M:%S')}] {message}") def load_env_config(self, environment): """Load configuration from environment file""" self.log(f"Loading configuration for {environment} environment...") # Determine which .env file to use if environment == "development": env_file = self.project_root / ".env" else: env_file = self.project_root / f".env.{environment}" if not env_file.exists(): self.log(f"Warning: {env_file} not found, skipping {environment}") return None # Load environment variables load_dotenv(env_file, override=True) # Extract frontend-relevant configuration config = { "API_URL": os.getenv("BACKEND_API_URL", "http://127.0.0.1:8000"), "DEBUG": os.getenv("API_DEBUG", "false").lower() == "true", "SHOW_DEBUG_INFO": os.getenv("ENABLE_DEBUG_INFO", "false").lower() == "true", "ENVIRONMENT": os.getenv("ENVIRONMENT", environment), "CONFIDENCE_THRESHOLD": float(os.getenv("CONFIDENCE_THRESHOLD", "0.5")), "DEFAULT_LANGUAGE": os.getenv("DEFAULT_LANGUAGE", "en"), "REQUEST_TIMEOUT_MS": int(os.getenv("REQUEST_TIMEOUT_MS") or 30000), "API_KEY": os.getenv("DEMO_API_KEY", ""), } self.log(f"Loaded config for {environment}: API_URL={config['API_URL']}") return config def generate_config_file(self, configs, target_env=None): """Generate JavaScript configuration file""" self.log("Generating frontend configuration file...") # Filter configs if specific environment requested if target_env: if target_env in configs: configs = {target_env: configs[target_env]} else: raise ValueError( f"Environment '{target_env}' not found in loaded configurations" ) # Generate JavaScript content js_content = self._generate_js_content(configs) # Write to frontend directory output_file = self.project_root / "frontend" / "config.js" output_file.parent.mkdir(exist_ok=True) with open(output_file, "w", encoding="utf-8") as f: f.write(js_content) self.log(f"Configuration written to {output_file}") return output_file def _generate_js_content(self, configs): """Generate the JavaScript configuration content""" timestamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S") js_content = f"""/** * Frontend Configuration * * Auto-generated from backend .env files on {timestamp} * DO NOT EDIT THIS FILE MANUALLY - your changes will be overwritten! * * To update configuration: * 1. Edit the appropriate .env file (.env for dev, .env.production, .env.staging) * 2. Run: python scripts/build-frontend-config.py * 3. Redeploy your frontend */ // Configuration for different environments const config = {json.dumps(configs, indent=2)}; /** * Environment Detection * Automatically detects current environment based on hostname */ function detectEnvironment() {{ const hostname = window.location.hostname; // Local development if (hostname === 'localhost' || hostname === '127.0.0.1' || hostname.endsWith('.local')) {{ return 'development'; }} // Staging (if there are staging URLs) if (hostname.includes('staging')) {{ return 'staging'; }} // Default to production for live domains return 'production'; }} // Get current environment configuration const currentEnvironment = detectEnvironment(); const currentConfig = config[currentEnvironment]; if (!currentConfig) {{ console.error(`No configuration found for environment: ${{currentEnvironment}}`); console.error('Available environments:', Object.keys(config)); // Fallback to development config window.ChatbotConfig = config.development || {{}}; }} else {{ window.ChatbotConfig = currentConfig; }} // Add environment info for debugging window.ChatbotConfig.DETECTED_ENVIRONMENT = currentEnvironment; // Debug logging (only in development) if (currentConfig && currentConfig.DEBUG) {{ console.log('🤖 Chatbot Configuration Loaded'); console.log('Environment:', currentEnvironment); console.log('API URL:', currentConfig.API_URL); console.log('Debug Mode:', currentConfig.DEBUG); console.log('Full Config:', currentConfig); }} // Export for module systems (if needed) if (typeof module !== 'undefined' && module.exports) {{ module.exports = {{ config, currentConfig: window.ChatbotConfig }}; }} """ return js_content def validate_configuration(self, configs): """Validate generated configuration for common issues""" self.log("Validating configuration...") issues = [] for env, config in configs.items(): # Check for missing API URLs (empty string is valid = same-origin) if config.get("API_URL") == "http://127.0.0.1:8000": if env == "production": issues.append("Production API_URL still points to localhost!") # Check for development settings in production if env == "production": if config.get("DEBUG", False): issues.append("Production has DEBUG=true (should be false)") if config.get("SHOW_DEBUG_INFO", False): issues.append( "Production has SHOW_DEBUG_INFO=true (should be false)" ) if issues: print("⚠️ Configuration Issues Found:") for issue in issues: print(f" - {issue}") print() else: self.log("✅ Configuration validation passed") return len(issues) == 0 def build(self, target_env=None): """Main build process""" print("🔧 Building Frontend Configuration...") print(f"Project root: {self.project_root}") print() # Load configurations from all environment files configs = {} for env in self.environments: config = self.load_env_config(env) if config: configs[env] = config if not configs: print("❌ No valid environment configurations found!") print("Make sure you have .env files in your project root.") return False # Validate configuration self.validate_configuration(configs) # Generate configuration file output_file = self.generate_config_file(configs, target_env) print("✅ Frontend configuration built successfully!") print(f"📁 Generated: {output_file}") print(f"🌍 Environments: {', '.join(configs.keys())}") if target_env: print(f"🎯 Target environment: {target_env}") print() print("Next steps:") print("1. Review the generated frontend/config.js file") print("2. Test frontend locally") print("3. Deploy frontend to Netlify or any other used frontend tools") print("4. The frontend will automatically use the correct environment!") return True def main(): parser = argparse.ArgumentParser( description="Generate frontend configuration from backend .env files", formatter_class=argparse.RawDescriptionHelpFormatter, epilog=""" Examples: python scripts/build-frontend-config.py # Build all environments python scripts/build-frontend-config.py --env production # Build only production python scripts/build-frontend-config.py --verbose # Verbose output """, ) parser.add_argument( "--env", choices=["development", "production", "staging"], help="Build configuration for specific environment only", ) parser.add_argument( "--verbose", "-v", action="store_true", help="Enable verbose output" ) args = parser.parse_args() # Create builder and run builder = FrontendConfigBuilder(verbose=args.verbose) success = builder.build(target_env=args.env) sys.exit(0 if success else 1) if __name__ == "__main__": main()