changemaker.lite/mkdocs/docs/hooks/env_config_hook.py
2026-02-18 17:15:31 -07:00

153 lines
6.0 KiB
Python

"""
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")