"""PyInstaller 单文件发布:解压 Chrome 扩展与配置到本地,引擎 exe 从内置 _MEIPASS 运行。""" from __future__ import annotations import os import shutil import sys import zipfile from pathlib import Path _RUNTIME_APP_NAME = "LikeAI" _VERSION_FILE = "pack_version.txt" _EXTENSION_ARCHIVE = "extension.zip" def _local_runtime_root() -> Path: base = os.environ.get("LOCALAPPDATA") or os.environ.get("APPDATA") or str(Path.home()) return Path(base) / _RUNTIME_APP_NAME def _bundle_root() -> Path: return Path(getattr(sys, "_MEIPASS", Path(__file__).resolve().parent.parent)) def _read_embedded_version() -> str: path = _bundle_root() / _VERSION_FILE if path.is_file(): return path.read_text(encoding="utf-8").strip() return "0" def bundled_engine_dir() -> Path: """单文件内置的 bridge / skills_cli(字节码,无 .py)。""" return _bundle_root() / "engine" def is_frozen_app() -> bool: return bool(getattr(sys, "frozen", False)) and hasattr(sys, "_MEIPASS") def bootstrap() -> Path: """首次运行解压扩展与配置;返回持久化数据目录(配置、.xhs_bridge)。""" if not is_frozen_app(): return Path(__file__).resolve().parent.parent root = _local_runtime_root() root.mkdir(parents=True, exist_ok=True) version = _read_embedded_version() stamp = root / ".pack_version" installed = stamp.read_text(encoding="utf-8").strip() if stamp.is_file() else "" if installed != version: _extract_extension(root) _ensure_config_template(root) stamp.write_text(version, encoding="utf-8") return root def _extract_extension(root: Path) -> None: archive = _bundle_root() / _EXTENSION_ARCHIVE if not archive.is_file(): return ext_dir = root / "extension" if ext_dir.exists(): shutil.rmtree(ext_dir, ignore_errors=True) ext_dir.mkdir(parents=True, exist_ok=True) with zipfile.ZipFile(archive, "r") as zf: zf.extractall(ext_dir) def _ensure_config_template(root: Path) -> None: target = root / "config.yaml" if target.is_file(): return for name in ("config.example.yaml", "config.yaml"): src = _bundle_root() / name if src.is_file(): shutil.copy2(src, target) return def get_runtime_root() -> Path: if is_frozen_app(): return bootstrap() return Path(__file__).resolve().parent.parent