diff --git a/meshchatx/meshchat.py b/meshchatx/meshchat.py index 179fab81..3a8c98b5 100644 --- a/meshchatx/meshchat.py +++ b/meshchatx/meshchat.py @@ -20440,9 +20440,9 @@ def main(): if args.self_check: results = reticulum_meshchat.run_self_test() - print("\n================================") - print(" System Self-Check Results") - print("================================") + print("\n================================", flush=True) + print(" System Self-Check Results", flush=True) + print("================================", flush=True) all_passed = True from meshchatx.src.backend.self_check import SELF_CHECK_LABELS @@ -20450,20 +20450,20 @@ def main(): for key, name in SELF_CHECK_LABELS.items(): check = results.get(key, {"status": "failed", "reason": "No result"}) if check["status"] == "ok": - print(f"[OK] {name}") + print(f"[OK] {name}", flush=True) else: all_passed = False reason = check.get("reason") or "Unknown error" - print(f"[FAILED] {name} - Reason: {reason}") + print(f"[FAILED] {name} - Reason: {reason}", flush=True) - print("================================") + print("================================", flush=True) if all_passed: - print("Status: SUCCESS (All checks passed)") - print("================================\n") + print("Status: SUCCESS (All checks passed)", flush=True) + print("================================\n", flush=True) sys.exit(0) else: - print("Status: FAILED") - print("================================\n") + print("Status: FAILED", flush=True) + print("================================\n", flush=True) sys.exit(1) if args.reset_password: diff --git a/meshchatx/src/backend/bot_handler.py b/meshchatx/src/backend/bot_handler.py index c54f11e3..29cf07ad 100644 --- a/meshchatx/src/backend/bot_handler.py +++ b/meshchatx/src/backend/bot_handler.py @@ -398,10 +398,13 @@ class BotHandler: if sys.platform.startswith("win"): # Use absolute path if possible to avoid S607 taskkill = shutil.which("taskkill") or "taskkill" + # Process may already have exited; suppress "not found" noise. subprocess.run( [taskkill, "/PID", str(pid), "/T", "/F"], check=False, timeout=5, + stdout=subprocess.DEVNULL, + stderr=subprocess.DEVNULL, ) else: try: diff --git a/meshchatx/src/backend/self_check.py b/meshchatx/src/backend/self_check.py index 0163a2ed..4c980219 100644 --- a/meshchatx/src/backend/self_check.py +++ b/meshchatx/src/backend/self_check.py @@ -181,25 +181,48 @@ def check_temp_filesystem() -> dict[str, str]: os.unlink(path) +def _is_frozen_executable() -> bool: + return bool(getattr(sys, "frozen", False)) + + +def _frontend_source_available() -> bool: + """True when running from a source tree with Vite frontend sources. + + Built ``meshchatx/public/`` is gitignored and often absent in CI / E2E + (Vite serves the UI). Frozen desktop builds still require bundled public. + """ + try: + package_dir = os.path.dirname(os.path.dirname(os.path.dirname(__file__))) + frontend = os.path.join(package_dir, "src", "frontend") + return os.path.isdir(frontend) and os.path.isfile( + os.path.join(frontend, "main.js") + ) + except Exception: + return False + + def check_public_assets(public_path_fn: Callable[[str], str]) -> dict[str, str]: try: root = public_path_fn("") + index_path = public_path_fn("index.html") if root else "" + if root and os.path.isdir(root): + if os.path.isfile(index_path): + return _status(True) + names = [n for n in os.listdir(root) if not n.startswith(".")] + if names: + return _status(True) + + # Source / CI / E2E: no built public dir, but frontend sources exist. + if not _is_frozen_executable() and _frontend_source_available(): + return _status(True) + if not root or not os.path.isdir(root): return _status(False, f"Public assets directory missing: {root!r}") - index_path = public_path_fn("index.html") - if not os.path.isfile(index_path): - names = os.listdir(root) - if not names: - return _status(False, "Public assets directory is empty") - return _status(True) + return _status(False, "Public assets directory is empty") except Exception as exc: return _status(False, f"Public assets check failed: {exc}") -def _is_frozen_executable() -> bool: - return bool(getattr(sys, "frozen", False)) - - def check_meshchatx_run_module() -> dict[str, str]: """Verify ``--meshchatx-run-module`` re-entry used by bots/rnsh on frozen builds.""" marker_dir = tempfile.mkdtemp(prefix="meshchatx_run_module_check_") diff --git a/tests/backend/test_self_check.py b/tests/backend/test_self_check.py index d34790f7..1c013de4 100644 --- a/tests/backend/test_self_check.py +++ b/tests/backend/test_self_check.py @@ -64,12 +64,25 @@ def test_check_public_assets_ok(tmp_path): assert self_check.check_public_assets(public_path)["status"] == "ok" -def test_check_public_assets_missing(tmp_path): +def test_check_public_assets_allows_source_tree_without_build(tmp_path, monkeypatch): missing = tmp_path / "nope" def public_path(name=""): return str(missing / name) if name else str(missing) + monkeypatch.setattr(self_check, "_is_frozen_executable", lambda: False) + monkeypatch.setattr(self_check, "_frontend_source_available", lambda: True) + assert self_check.check_public_assets(public_path)["status"] == "ok" + + +def test_check_public_assets_missing_when_frozen(tmp_path, monkeypatch): + missing = tmp_path / "nope" + + def public_path(name=""): + return str(missing / name) if name else str(missing) + + monkeypatch.setattr(self_check, "_is_frozen_executable", lambda: True) + monkeypatch.setattr(self_check, "_frontend_source_available", lambda: False) result = self_check.check_public_assets(public_path) assert result["status"] == "failed"