from __future__ import annotations import argparse import shutil import sqlite3 import subprocess import sys import time import urllib.error import urllib.request from pathlib import Path ROOT = Path(__file__).resolve().parents[1] FRONTEND_ROOT = ROOT / "frontend" E2E_URL = "http://127.0.0.1:8876/index.html" def run(label: str, command: list[str]) -> None: print(f"\n[{label}] {' '.join(command)}", flush=True) subprocess.run(command, cwd=ROOT, check=True) def python_test_command(preservation_baseline: bool | None = None) -> list[str]: if preservation_baseline is None: preservation_baseline = (ROOT.parent / "static" / "app.js").is_file() if preservation_baseline: return [sys.executable, "-m", "unittest", "discover", "-s", "tests"] modules = [ f"tests.{path.stem}" for path in sorted((ROOT / "tests").glob("test_*.py")) if not path.stem.startswith("test_preservation_") ] if not modules: raise RuntimeError("no standalone candidate tests found") return [sys.executable, "-m", "unittest", *modules] def verify_git_diff() -> None: result = subprocess.run( ["git", "rev-parse", "--show-toplevel"], cwd=ROOT, capture_output=True, text=True, check=False, ) if result.returncode != 0: print("\n[patch] skipped: standalone export is not a Git checkout") return repository_root = Path(result.stdout.strip()).resolve() if ROOT != repository_root / "app": print("\n[patch] skipped: standalone export is outside the canonical app path") return run("patch", ["git", "diff", "--check"]) def verify_database() -> None: database = ROOT / "data" / "review.db" if not database.exists(): print("\n[database] skipped: data/review.db does not exist") return with sqlite3.connect(f"file:{database.as_posix()}?mode=ro", uri=True) as connection: result = connection.execute("PRAGMA integrity_check").fetchone() if not result or result[0] != "ok": raise RuntimeError(f"SQLite integrity check failed: {result}") print(f"\n[database] integrity_check=ok size={database.stat().st_size}") def url_reachable(url: str) -> bool: try: with urllib.request.urlopen(url, timeout=1) as response: return response.status == 200 except (OSError, urllib.error.URLError): return False def start_e2e_server() -> subprocess.Popen[bytes] | None: if url_reachable(E2E_URL): print(f"\n[e2e-server] reusing {E2E_URL}") return None creation_flags = subprocess.CREATE_NO_WINDOW if sys.platform == "win32" else 0 process = subprocess.Popen( [ sys.executable, "-m", "http.server", "8876", "--bind", "127.0.0.1", "--directory", "frontend", ], cwd=ROOT, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL, creationflags=creation_flags, ) deadline = time.monotonic() + 15 while time.monotonic() < deadline: if process.poll() is not None: raise RuntimeError("Playwright static server exited before becoming ready") if url_reachable(E2E_URL): print(f"\n[e2e-server] started {E2E_URL}") return process time.sleep(0.1) stop_e2e_server(process) raise RuntimeError("Playwright static server did not become ready within 15 seconds") def stop_e2e_server(process: subprocess.Popen[bytes] | None) -> None: if process is None: return process.terminate() try: process.wait(timeout=5) except subprocess.TimeoutExpired: process.kill() process.wait(timeout=5) print("\n[e2e-server] stopped") def main() -> int: parser = argparse.ArgumentParser( description="Verify the modular preservation candidate" ) parser.add_argument( "--e2e", action="store_true", help="also run the Playwright browser suite", ) args = parser.parse_args() run("python", python_test_command()) run( "api-registry", [sys.executable, "tools/build_api_registry.py", "--check"], ) run( "architecture-inventory", [sys.executable, "tools/build_architecture_inventory.py", "--check"], ) node = shutil.which("node") if not node: raise RuntimeError("node is required for JavaScript syntax checks") scripts = sorted(FRONTEND_ROOT.rglob("*.js")) if not scripts: raise RuntimeError(f"no JavaScript files found under {FRONTEND_ROOT}") for script in scripts: run( "javascript", [node, "--check", script.relative_to(ROOT).as_posix()], ) verify_git_diff() verify_database() if args.e2e: npx = shutil.which("npx.cmd" if sys.platform == "win32" else "npx") if not npx: raise RuntimeError("npx is required for the Playwright suite") server = start_e2e_server() try: run("playwright", [npx, "playwright", "test", "--reporter=dot"]) finally: stop_e2e_server(server) print("\nBaseline verification passed.") return 0 if __name__ == "__main__": raise SystemExit(main())