smart-chatbot-api / scripts /build-frontend-config.py
GitHub Actions
Deploy from GitHub Actions (2026-03-16 04:38 UTC)
4413b85
#!/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()