fix: redeploy the site on any script it imports

This commit is contained in:
Abdessamad Derraz committed 2026-10-06 05:23:27 +02:00
1 parent d02672c17b
commit cd90f46937
2 files changed
+129 -5

No files matched your search

+89
View File
@@ -0,0 +1,89 @@
"""The site redeploys whenever a script it runs changes.
Deploy Site listed five scripts by hand while the build imports forty:
a change to siterender.py, which writes every page, published nothing
until some unrelated file moved.
"""
from __future__ import annotations
import ast
import fnmatch
import unittest
from pathlib import Path
import yaml
REPO_ROOT = Path(__file__).resolve().parent.parent
SCRIPTS = REPO_ROOT / "scripts"
def _module_file(name: str) -> Path | None:
name = name.removeprefix("scripts.")
for candidate in (
SCRIPTS / (name.replace(".", "/") + ".py"),
SCRIPTS / name.replace(".", "/") / "__init__.py",
):
if candidate.exists():
return candidate
return None
def _imports(path: Path) -> set[Path]:
found: set[Path] = set()
for node in ast.walk(ast.parse(path.read_text(encoding="utf-8"))):
if isinstance(node, ast.Import):
names = [alias.name for alias in node.names]
elif isinstance(node, ast.ImportFrom) and node.level == 0 and node.module:
names = [node.module, *(f"{node.module}.{a.name}" for a in node.names)]
elif isinstance(node, ast.ImportFrom):
base = path.parent / (node.module or "").replace(".", "/")
candidates = [base / f"{a.name}.py" for a in node.names]
candidates += [base.with_suffix(".py"), base / "__init__.py"]
found.update(c for c in candidates if c.is_file())
continue
else:
continue
found.update(f for f in map(_module_file, names) if f)
return found
def closure(entry_points: list[str]) -> set[str]:
seen: set[Path] = set()
stack = [SCRIPTS / name for name in entry_points]
while stack:
path = stack.pop()
if path not in seen:
seen.add(path)
stack.extend(_imports(path))
return {str(p.relative_to(REPO_ROOT)) for p in seen}
class DeploySiteTriggers(unittest.TestCase):
def test_every_script_the_build_imports_triggers_it(self):
workflow = yaml.safe_load(
(REPO_ROOT / ".github/workflows/deploy-site.yml").read_text(encoding="utf-8")
)
on = workflow.get("on", workflow.get(True))
patterns = on["push"]["paths"]
run = "\n".join(
step.get("run", "") for job in workflow["jobs"].values() for step in job["steps"]
)
entry_points = sorted(
{
word.removeprefix("scripts/")
for word in run.split()
if word.startswith("scripts/") and word.endswith(".py")
}
)
self.assertIn("generate_site.py", entry_points)
uncovered = sorted(
path
for path in closure(entry_points)
if not any(fnmatch.fnmatch(path, p.replace("**", "*")) for p in patterns)
)
self.assertEqual(uncovered, [])
if __name__ == "__main__":
unittest.main()