Files
RustProxy/tools/checks/source_module_contracts.py
T
2026-10-10 08:53:55 +02:00

66 lines
3.6 KiB
Python
Executable File

#!/usr/bin/env python3
"""Compiler-independent source-size and test-isolation contracts for non-Rust modules."""
from __future__ import annotations
from pathlib import Path
import os
import re
import shutil
import subprocess
import tempfile
ROOT = Path(__file__).resolve().parents[2]
MAX_LINES = 500
def lines(path: Path) -> int:
return len(path.read_text(encoding="utf-8").splitlines())
# Browser implementation modules are bounded. QA harnesses under tools/checks are checked separately.
for path in sorted((ROOT / "src/admin/web").rglob("*.js")):
assert lines(path) <= MAX_LINES, f"{path.relative_to(ROOT)} has {lines(path)} lines (max {MAX_LINES})"
# Docker Agent implementation and Go tests are separate files and all remain bounded.
for path in sorted((ROOT / "docker-agent").glob("*.go")):
assert lines(path) <= MAX_LINES, f"{path.relative_to(ROOT)} has {lines(path)} lines (max {MAX_LINES})"
if not path.name.endswith("_test.go"):
text = path.read_text(encoding="utf-8")
assert not re.search(r"(?m)^func\s+Test[A-Z]", text), f"Go test remains in implementation file: {path.relative_to(ROOT)}"
# Application-coupled Python entry points are split from external QA/dev tooling.
for directory in ("scripts/runtime", "scripts/build", "scripts/deploy", "scripts/migration"):
for path in sorted((ROOT / directory).glob("*.py")):
assert lines(path) <= MAX_LINES, f"{path.relative_to(ROOT)} has {lines(path)} lines (max {MAX_LINES})"
for old in (ROOT / "src/admin/web/app.js", ROOT / "src/admin/web/vhosts.js"):
assert not old.exists(), f"oversized browser monolith returned: {old.relative_to(ROOT)}"
for required in (
"src/admin/web/app/core.js", "src/admin/web/app/preferences.js", "src/admin/web/app/config-history.js", "src/admin/web/app/runtime.js", "src/admin/web/app/settings.js",
"src/admin/web/vhosts/core.js", "src/admin/web/vhosts/health.js", "src/admin/web/vhosts/editor.js", "src/admin/web/vhosts/render.js",
"docker-agent/model.go", "docker-agent/docker.go", "docker-agent/network.go",
"docker-agent/certificate.go", "docker-agent/server.go",
"scripts/deploy/deploy_core.py", "scripts/deploy/deploy_runtime.py",
"scripts/migration/migrate_npm_core.py", "scripts/migration/migrate_npm_api.py",
):
assert (ROOT / required).is_file(), f"missing split source module: {required}"
# When Node is available, parse every standalone browser module and both assembled public bundles.
node = shutil.which("node")
if node:
env = dict(os.environ, TERM=os.environ.get("TERM", "xterm"))
standalone = list((ROOT / "src/admin/web").rglob("*.js"))
for path in sorted(standalone):
subprocess.run([node, "--check", str(path)], check=True, env=env, stdout=subprocess.DEVNULL)
from web_assets import read_app_js, read_vhosts_js
with tempfile.TemporaryDirectory(prefix="rustproxy-js-contract-") as directory:
temporary = Path(directory)
for name, content in (("app.js", read_app_js(ROOT)), ("vhosts.js", read_vhosts_js(ROOT))):
target = temporary / name
target.write_text(content, encoding="utf-8")
subprocess.run([node, "--check", str(target)], check=True, env=env, stdout=subprocess.DEVNULL)
# The public vhosts asset wraps three individually valid source modules in one private IIFE.
# Every source part is parsed above; this additional check keeps the assembled browser asset valid.
print(f"PASS: browser, Docker Agent and application-coupled Python modules are <= {MAX_LINES} lines, tooling is separated, tests are isolated, and browser source boundaries parse cleanly.")