1
0
Fork 0
Code Issues Pull requests Projects Releases 2 Packages Wiki Activity Actions Pages

Add browser asset quality gate

This commit is contained in:
Andraxion 2026-07-24 22:36:44 -04:00
parent 2841042b9c
commit 5ccb336e84
14 changed files with 2898 additions and 16 deletions

131
tools/check_web_assets.py Normal file
View file

@ -0,0 +1,131 @@
"""Validate the exact embedded graph-browser HTML, CSS, and JavaScript."""
from __future__ import annotations
import ast
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]
SOURCE = ROOT / "src" / "docforge" / "visualization.py"
def _graph_browser_html() -> str:
tree = ast.parse(SOURCE.read_text(encoding="utf-8"), filename=str(SOURCE))
for statement in tree.body:
if not isinstance(statement, ast.Assign):
continue
if any(
isinstance(target, ast.Name) and target.id == "_GRAPH_BROWSER_HTML"
for target in statement.targets
):
value = ast.literal_eval(statement.value)
if isinstance(value, str):
return value
raise RuntimeError("Could not find the literal graph-browser HTML asset")
def _embedded(html: str, tag: str) -> str:
opening = f"<{tag}>"
closing = f"</{tag}>"
if html.count(opening) != 1 or html.count(closing) != 1:
raise RuntimeError(f"Expected exactly one embedded {tag} asset")
return html.split(opening, 1)[1].split(closing, 1)[0]
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",
],
_embedded(html, "style"),
),
(
"JavaScript",
[
_tool("eslint"),
"--stdin",
"--stdin-filename=graph-browser.js",
"--max-warnings=0",
],
_embedded(html, "script"),
),
)
results = [_run(label, command, content) for label, command, content in checks]
return 0 if all(results) else 1
if __name__ == "__main__":
sys.exit(main())