"""Desktop (Electron) app: build/stamp, stage-and-swap pack, exe integrity gate, macOS signing/TCC, Linux sandbox, launch (hermes gui/desktop). Split out of ``hermes_cli/main.py``. Names that still live in main (``PROJECT_ROOT``, ...) are imported lazily inside the functions that use them (avoids an import cycle). """ import logging import contextlib import argparse import os import re import shlex import shutil import stat import subprocess import sys import tempfile import time as _time_mod from pathlib import Path from typing import Optional from hermes_cli.main_tui_launch import _npm_lifecycle_env from hermes_cli.main_web_build import ( _hash_source_tree, _nixos_build_env, _stamp_is_current, _write_build_stamp) # Log-record parity with the origin module. logger = logging.getLogger("hermes_cli.main") _PREVIOUS_APP_KEPT = " ↩ The previous desktop app was left untouched and still works." def _desktop_dist_exists(desktop_dir: Path) -> bool: """Return True when a local desktop renderer build is present.""" return (desktop_dir / "dist" / "index.html").exists() def _compute_desktop_content_hash(project_root: Path) -> str: """SHA-256 of ``apps/desktop/`` (minus .gitignore matches) plus root workspace config.""" return _hash_source_tree(project_root, project_root / "apps" / "desktop") def _desktop_stamp_path() -> Path: """Path of the desktop build stamp under $HERMES_HOME.""" from hermes_constants import get_hermes_home return get_hermes_home() / "desktop-build-stamp.json" def _renderer_bundle_dir(desktop_dir: Path, *, source_mode: bool) -> Optional[Path]: """The renderer ``dist`` a launch loads: ``apps/desktop/dist`` in source mode, else the ``app.asar.unpacked/dist`` copy (the only real directory, and the one an interrupted replace tears).""" if source_mode: return desktop_dir / "dist" executable = _desktop_packaged_executable(desktop_dir) if executable is None: return None # macOS: …/Hermes.app/Contents/MacOS/Hermes → …/Contents/Resources resources = ( executable.parent.parent / "Resources" if sys.platform == "darwin" else executable.parent / "resources" ) return resources / "app.asar.unpacked" / "dist" # The module files the renderer fetches before any app code runs: Vite emits # them as `