120 lines
3.6 KiB
Python
120 lines
3.6 KiB
Python
|
|
#!/usr/bin/env python3
|
||
|
|
from __future__ import annotations
|
||
|
|
|
||
|
|
import argparse
|
||
|
|
import json
|
||
|
|
import subprocess
|
||
|
|
from pathlib import Path
|
||
|
|
|
||
|
|
LIVE_TEMPLATES_DIR = Path.home() / ".nanobot" / "cards" / "templates"
|
||
|
|
EXAMPLE_TEMPLATES_DIR = (
|
||
|
|
Path(__file__).resolve().parent.parent / "examples" / "cards" / "templates"
|
||
|
|
)
|
||
|
|
|
||
|
|
|
||
|
|
LEGACY_MARKERS = (
|
||
|
|
"__nanobot",
|
||
|
|
"document.currentScript",
|
||
|
|
"mountLegacyTemplate",
|
||
|
|
"legacy-template-module",
|
||
|
|
)
|
||
|
|
|
||
|
|
|
||
|
|
def iter_template_dirs(root: Path) -> dict[str, Path]:
|
||
|
|
templates: dict[str, Path] = {}
|
||
|
|
if not root.exists():
|
||
|
|
return templates
|
||
|
|
for child in sorted(root.iterdir()):
|
||
|
|
if not child.is_dir():
|
||
|
|
continue
|
||
|
|
if not (child / "manifest.json").exists():
|
||
|
|
continue
|
||
|
|
templates[child.name] = child
|
||
|
|
return templates
|
||
|
|
|
||
|
|
|
||
|
|
def validate_template_dir(template_dir: Path, failures: list[str]) -> None:
|
||
|
|
manifest_path = template_dir / "manifest.json"
|
||
|
|
template_path = template_dir / "template.html"
|
||
|
|
runtime_path = template_dir / "card.js"
|
||
|
|
|
||
|
|
for required in (manifest_path, template_path, runtime_path):
|
||
|
|
if not required.exists():
|
||
|
|
failures.append(f"{template_dir}: missing {required.name}")
|
||
|
|
return
|
||
|
|
|
||
|
|
try:
|
||
|
|
manifest = json.loads(manifest_path.read_text(encoding="utf-8"))
|
||
|
|
except Exception as exc:
|
||
|
|
failures.append(f"{manifest_path}: invalid JSON ({exc})")
|
||
|
|
return
|
||
|
|
if not isinstance(manifest, dict):
|
||
|
|
failures.append(f"{manifest_path}: manifest must be an object")
|
||
|
|
|
||
|
|
template_html = template_path.read_text(encoding="utf-8")
|
||
|
|
if "<script" in template_html.lower():
|
||
|
|
failures.append(f"{template_path}: inline script tags are not allowed")
|
||
|
|
|
||
|
|
runtime_js = runtime_path.read_text(encoding="utf-8")
|
||
|
|
if "export function mount" not in runtime_js:
|
||
|
|
failures.append(f"{runtime_path}: missing `export function mount`")
|
||
|
|
for marker in LEGACY_MARKERS:
|
||
|
|
if marker in runtime_js:
|
||
|
|
failures.append(f"{runtime_path}: legacy marker `{marker}` still present")
|
||
|
|
|
||
|
|
result = subprocess.run(
|
||
|
|
["node", "--check", str(runtime_path)],
|
||
|
|
capture_output=True,
|
||
|
|
text=True,
|
||
|
|
)
|
||
|
|
if result.returncode != 0:
|
||
|
|
detail = (result.stderr or result.stdout).strip()
|
||
|
|
failures.append(f"{runtime_path}: node --check failed: {detail}")
|
||
|
|
|
||
|
|
|
||
|
|
def main() -> int:
|
||
|
|
parser = argparse.ArgumentParser()
|
||
|
|
parser.add_argument("--live-root", type=Path, default=LIVE_TEMPLATES_DIR)
|
||
|
|
parser.add_argument("--example-root", type=Path, default=EXAMPLE_TEMPLATES_DIR)
|
||
|
|
args = parser.parse_args()
|
||
|
|
|
||
|
|
live_root = args.live_root.expanduser()
|
||
|
|
example_root = args.example_root.expanduser()
|
||
|
|
|
||
|
|
failures: list[str] = []
|
||
|
|
for template_dir in iter_template_dirs(live_root).values():
|
||
|
|
validate_template_dir(template_dir, failures)
|
||
|
|
for template_dir in iter_template_dirs(example_root).values():
|
||
|
|
validate_template_dir(template_dir, failures)
|
||
|
|
|
||
|
|
sync_check = subprocess.run(
|
||
|
|
[
|
||
|
|
"python3",
|
||
|
|
str(Path(__file__).resolve().parent / "sync_card_templates.py"),
|
||
|
|
"--check",
|
||
|
|
"--source",
|
||
|
|
str(live_root),
|
||
|
|
"--dest",
|
||
|
|
str(example_root),
|
||
|
|
],
|
||
|
|
capture_output=True,
|
||
|
|
text=True,
|
||
|
|
)
|
||
|
|
if sync_check.returncode != 0:
|
||
|
|
detail = (sync_check.stdout + sync_check.stderr).strip()
|
||
|
|
if detail:
|
||
|
|
print(detail)
|
||
|
|
failures.append("template mirror drift detected")
|
||
|
|
|
||
|
|
if failures:
|
||
|
|
for failure in failures:
|
||
|
|
print(failure)
|
||
|
|
return 1
|
||
|
|
|
||
|
|
print("card runtime ok")
|
||
|
|
return 0
|
||
|
|
|
||
|
|
|
||
|
|
if __name__ == "__main__":
|
||
|
|
raise SystemExit(main())
|