2026-07-24 22:36:44 -04:00
|
|
|
"""Validate the exact embedded graph-browser HTML, CSS, and JavaScript."""
|
|
|
|
|
|
|
|
|
|
from __future__ import annotations
|
|
|
|
|
|
|
|
|
|
import os
|
|
|
|
|
import shutil
|
|
|
|
|
import subprocess
|
|
|
|
|
import sys
|
|
|
|
|
import tempfile
|
|
|
|
|
from pathlib import Path
|
|
|
|
|
from typing import cast
|
|
|
|
|
|
|
|
|
|
from docforge.index import ProjectIndex
|
|
|
|
|
from docforge.project import Project
|
|
|
|
|
from docforge.rendering import RenderService
|
|
|
|
|
|
|
|
|
|
ROOT = Path(__file__).resolve().parents[1]
|
2026-07-25 16:46:00 -04:00
|
|
|
ASSET_ROOT = ROOT / "src" / "docforge" / "assets"
|
2026-07-24 22:36:44 -04:00
|
|
|
|
|
|
|
|
|
|
|
|
|
def _graph_browser_html() -> str:
|
2026-07-25 16:46:00 -04:00
|
|
|
return (ASSET_ROOT / "graph.html").read_text(encoding="utf-8")
|
2026-07-24 22:36:44 -04:00
|
|
|
|
|
|
|
|
|
|
|
|
|
def _rendered_manual_html() -> str:
|
|
|
|
|
with tempfile.TemporaryDirectory() as directory:
|
|
|
|
|
fixture = ROOT / "tests" / "fixtures" / "alpha"
|
|
|
|
|
project_root = Path(directory) / "alpha"
|
|
|
|
|
shutil.copytree(fixture, project_root)
|
|
|
|
|
project = Project.open(project_root)
|
|
|
|
|
ProjectIndex(project).build()
|
|
|
|
|
result = RenderService(project).render("manual")
|
|
|
|
|
output = result.get("output")
|
|
|
|
|
if not isinstance(output, dict):
|
|
|
|
|
raise RuntimeError("Manual render did not return output identity")
|
|
|
|
|
relative_path = cast(dict[str, object], output).get("path")
|
|
|
|
|
if not isinstance(relative_path, str):
|
|
|
|
|
raise RuntimeError("Manual render did not return an output path")
|
|
|
|
|
return (project_root / relative_path).read_text(encoding="utf-8")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def _tool(name: str) -> str:
|
|
|
|
|
suffix = ".cmd" if os.name == "nt" else ""
|
|
|
|
|
path = ROOT / "node_modules" / ".bin" / f"{name}{suffix}"
|
|
|
|
|
if not path.is_file():
|
|
|
|
|
raise RuntimeError(f"Missing web validator {name}; run npm ci")
|
|
|
|
|
return str(path)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def _run(label: str, command: list[str], content: str) -> bool:
|
|
|
|
|
print(f"Checking {label}...")
|
|
|
|
|
result = subprocess.run(
|
|
|
|
|
command,
|
|
|
|
|
cwd=ROOT,
|
|
|
|
|
input=content,
|
|
|
|
|
text=True,
|
|
|
|
|
check=False,
|
|
|
|
|
)
|
|
|
|
|
return result.returncode == 0
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def main() -> int:
|
|
|
|
|
html = _graph_browser_html()
|
|
|
|
|
checks = (
|
|
|
|
|
(
|
|
|
|
|
"HTML",
|
|
|
|
|
[
|
|
|
|
|
_tool("html-validate"),
|
|
|
|
|
"--stdin",
|
|
|
|
|
"--stdin-filename=graph-browser.html",
|
|
|
|
|
"--max-warnings=0",
|
|
|
|
|
],
|
|
|
|
|
html,
|
|
|
|
|
),
|
|
|
|
|
(
|
|
|
|
|
"rendered manual HTML",
|
|
|
|
|
[
|
|
|
|
|
_tool("html-validate"),
|
|
|
|
|
"--stdin",
|
|
|
|
|
"--stdin-filename=rendered-manual.html",
|
|
|
|
|
"--max-warnings=0",
|
|
|
|
|
],
|
|
|
|
|
_rendered_manual_html(),
|
|
|
|
|
),
|
|
|
|
|
(
|
|
|
|
|
"CSS",
|
|
|
|
|
[
|
|
|
|
|
_tool("stylelint"),
|
|
|
|
|
"--stdin",
|
|
|
|
|
"--stdin-filename=graph-browser.css",
|
|
|
|
|
"--max-warnings=0",
|
|
|
|
|
],
|
2026-07-25 16:46:00 -04:00
|
|
|
(ASSET_ROOT / "graph.css").read_text(encoding="utf-8"),
|
2026-07-24 22:36:44 -04:00
|
|
|
),
|
|
|
|
|
(
|
|
|
|
|
"JavaScript",
|
|
|
|
|
[
|
|
|
|
|
_tool("eslint"),
|
|
|
|
|
"--stdin",
|
|
|
|
|
"--stdin-filename=graph-browser.js",
|
|
|
|
|
"--max-warnings=0",
|
|
|
|
|
],
|
2026-07-25 16:46:00 -04:00
|
|
|
(ASSET_ROOT / "graph.js").read_text(encoding="utf-8"),
|
2026-07-24 22:36:44 -04:00
|
|
|
),
|
|
|
|
|
)
|
|
|
|
|
results = [_run(label, command, content) for label, command, content in checks]
|
|
|
|
|
return 0 if all(results) else 1
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if __name__ == "__main__":
|
|
|
|
|
sys.exit(main())
|