""" MkDocs Hook for Environment Variable Injection Injects environment variables into the site as JavaScript configuration. This allows client-side code to access server-side config values. """ import os import logging from typing import Dict, Any # Configure logging logging.basicConfig(level=logging.INFO) logger = logging.getLogger(__name__) def on_config(config: Dict[str, Any]) -> Dict[str, Any]: """ Hook that runs when MkDocs loads the configuration. Injects environment variables as extra JavaScript. """ import re # Read environment variables (with fallbacks) media_api_url = os.environ.get('MEDIA_API_PUBLIC_URL', 'http://localhost:4100') media_api_port = os.environ.get('MEDIA_API_PORT', '4100') api_url = os.environ.get('API_URL', '') api_port = os.environ.get('API_PORT', '4000') admin_port = os.environ.get('ADMIN_PORT', '3000') admin_url = os.environ.get('ADMIN_URL', '') base_domain = os.environ.get('BASE_DOMAIN', '') gancio_url = os.environ.get('GANCIO_URL', 'http://localhost:8092') gancio_port = os.environ.get('GANCIO_PORT', '8092') if base_domain and not base_domain.startswith('http'): base_domain = f'https://{base_domain}' # Helper: detect Docker container hostnames (not browser-accessible) def is_docker_hostname(url: str) -> bool: """Check if URL uses a Docker container hostname instead of localhost/domain.""" host = re.sub(r'^https?://', '', url).split(':')[0].split('/')[0] # Docker hostnames typically contain hyphens and no dots (not localhost, not a domain) return host != 'localhost' and '.' not in host # Resolve media API URL — must be browser-accessible (not Docker hostname) if is_docker_hostname(media_api_url): media_api_url = f'http://localhost:{media_api_port}' # Resolve Gancio URL — must be browser-accessible if is_docker_hostname(gancio_url): gancio_url = f'http://localhost:{gancio_port}' # Resolve payment API URL — must be browser-accessible if api_url and not is_docker_hostname(api_url): payment_api_url = api_url else: payment_api_url = f'http://localhost:{api_port}' # Resolve public URL (admin app) if admin_url and not is_docker_hostname(admin_url) and 'localhost' not in admin_url: # Production domain — use as-is public_url = admin_url else: # Dev: use ADMIN_PORT (most reliable source of truth) public_url = f'http://localhost:{admin_port}' # App URL (for payment fallback links, same as public_url) app_url = public_url # Create inline JavaScript with config config_script = f""" // Auto-generated configuration from environment variables // Generated by mkdocs/docs/hooks/env_config_hook.py (function() {{ window.MEDIA_API_URL = '{media_api_url}'; window.PUBLIC_URL = '{public_url}'; window.PAYMENT_API_URL = '{payment_api_url}'; window.APP_URL = '{app_url}'; window.GANCIO_URL = '{gancio_url}'; window.VIDEO_PLAYER_DEBUG = {str(os.environ.get('VIDEO_PLAYER_DEBUG', 'false')).lower()}; console.log('[EnvConfig] Loaded from environment:', {{ mediaApiUrl: window.MEDIA_API_URL, publicUrl: window.PUBLIC_URL, paymentApiUrl: window.PAYMENT_API_URL, appUrl: window.APP_URL, gancioUrl: window.GANCIO_URL, debug: window.VIDEO_PLAYER_DEBUG }}); }})(); """.strip() # Inject as extra_javascript (inline script) # MkDocs will include this before other scripts if 'extra_javascript' not in config: config['extra_javascript'] = [] # Prepend inline config script (load before video-player.js) # Note: We'll need to create a file for this env_config_path = 'assets/js/env-config.js' # Write the generated config to a file (only if content changed to avoid # triggering MkDocs file watcher rebuild loop in serve mode) import pathlib docs_dir = pathlib.Path(config['docs_dir']) env_config_file = docs_dir / env_config_path env_config_file.parent.mkdir(parents=True, exist_ok=True) existing_content = '' if env_config_file.exists(): existing_content = env_config_file.read_text() if existing_content != config_script: with open(env_config_file, 'w') as f: f.write(config_script) logger.info(f"✓ Generated env config: {env_config_file}") logger.info(f" MEDIA_API_URL: {media_api_url}") logger.info(f" PUBLIC_URL: {public_url}") logger.info(f" PAYMENT_API_URL: {payment_api_url}") logger.info(f" APP_URL: {app_url}") logger.info(f" GANCIO_URL: {gancio_url}") else: logger.info(f"✓ Env config unchanged, skipping write") # Insert at the beginning of extra_javascript list if env_config_path not in config['extra_javascript']: config['extra_javascript'].insert(0, env_config_path) # Inject public_url and admin_port into config.extra so Jinja2 templates # can use {{ config.extra.public_url }} and {{ config.extra.admin_port }} # for cross-site navigation links with client-side URL resolution if 'extra' not in config: config['extra'] = {} config['extra']['public_url'] = public_url config['extra']['admin_port'] = int(admin_port) config['extra']['payment_api_url'] = payment_api_url config['extra']['app_url'] = app_url # API URL for docs analytics tracking (browser-accessible) config['extra']['api_url'] = payment_api_url # Same resolved URL logger.info(f" Injected config.extra.public_url: {public_url}") logger.info(f" Injected config.extra.admin_port: {admin_port}") logger.info(f" Injected config.extra.payment_api_url: {payment_api_url}") logger.info(f" Injected config.extra.app_url: {app_url}") logger.info(f" Injected config.extra.api_url: {payment_api_url}") return config def on_pre_build(config: Dict[str, Any]) -> None: """ Hook that runs before build starts. Ensures config is regenerated on each build. """ logger.info("Environment configuration will be generated from environment variables")