From 1fe349af853ce2e213292befc98f2cc026e6ba48 Mon Sep 17 00:00:00 2001 From: Nicole Rappe Date: Sun, 26 Oct 2025 03:37:05 -0600 Subject: [PATCH] Stage Engine web interface assets at runtime --- Data/Engine/bootstrapper.py | 63 ++++++++++++++++++++++++++++++++++++- Data/Engine/config.py | 3 +- 2 files changed, 64 insertions(+), 2 deletions(-) diff --git a/Data/Engine/bootstrapper.py b/Data/Engine/bootstrapper.py index 400a1df..a833fee 100644 --- a/Data/Engine/bootstrapper.py +++ b/Data/Engine/bootstrapper.py @@ -8,9 +8,11 @@ legacy server defaults by binding to ``0.0.0.0:5001`` and honouring the from __future__ import annotations +import logging import os +import shutil from pathlib import Path -from typing import Any, Dict +from typing import Any, Dict, Set from .server import EngineContext, create_app @@ -19,6 +21,10 @@ DEFAULT_HOST = "0.0.0.0" DEFAULT_PORT = 5001 +def _project_root() -> Path: + return Path(__file__).resolve().parents[2] + + def _build_runtime_config() -> Dict[str, Any]: return { "HOST": os.environ.get("BOREALIS_ENGINE_HOST", DEFAULT_HOST), @@ -29,6 +35,55 @@ def _build_runtime_config() -> Dict[str, Any]: } +def _stage_web_interface_assets(logger: logging.Logger) -> None: + project_root = _project_root() + engine_web_root = project_root / "Engine" / "web-interface" + legacy_source = project_root / "Data" / "Server" / "WebUI" + + package_json = engine_web_root / "package.json" + src_dir = engine_web_root / "src" + + if package_json.is_file() and src_dir.is_dir(): + return + + if not legacy_source.is_dir(): + logger.warning( + "Legacy WebUI source missing; unable to stage Engine web interface from %s", + legacy_source, + ) + return + + engine_web_root.mkdir(parents=True, exist_ok=True) + + preserved: Set[str] = {".gitignore", "README.md"} + for child in engine_web_root.iterdir(): + if child.name in preserved: + continue + if child.is_dir(): + shutil.rmtree(child, ignore_errors=True) + else: + try: + child.unlink() + except FileNotFoundError: + continue + + for item in legacy_source.iterdir(): + destination = engine_web_root / item.name + if item.is_dir(): + shutil.copytree(item, destination, dirs_exist_ok=True) + else: + shutil.copy2(item, destination) + + if not package_json.is_file() or not src_dir.is_dir(): + raise RuntimeError( + f"Failed to stage Engine web interface assets into {engine_web_root}" + ) + + logger.info( + "Engine web interface staged from %s to %s", legacy_source, engine_web_root + ) + + def _ensure_tls_material(context: EngineContext) -> None: """Ensure TLS certificate material exists, updating the context if created.""" @@ -96,6 +151,12 @@ def main() -> None: config = _build_runtime_config() app, socketio, context = create_app(config) + try: + _stage_web_interface_assets(context.logger) + except Exception as exc: + context.logger.error("Failed to stage Engine web interface: %s", exc) + raise + host = config.get("HOST", DEFAULT_HOST) port = int(config.get("PORT", DEFAULT_PORT)) diff --git a/Data/Engine/config.py b/Data/Engine/config.py index 2c83265..c743b3a 100644 --- a/Data/Engine/config.py +++ b/Data/Engine/config.py @@ -65,8 +65,9 @@ def _ensure_parent(path: Path) -> None: def _resolve_static_folder() -> str: candidate_roots = [ - ENGINE_DIR.parent / "Engine" / "web-interface", + PROJECT_ROOT / "Engine" / "web-interface", ENGINE_DIR / "web-interface", + PROJECT_ROOT / "Data" / "Server" / "web-interface", ] candidates = []