| |
| from __future__ import annotations |
| import re |
| import sys |
| from pathlib import Path |
|
|
| ROOT = Path(__file__).resolve().parents[1] |
| errors: list[str] = [] |
| warnings: list[str] = [] |
|
|
|
|
| def fail(msg: str) -> None: |
| errors.append(msg) |
|
|
|
|
| def warn(msg: str) -> None: |
| warnings.append(msg) |
|
|
| version_file = ROOT / "VERSION" |
| version = version_file.read_text(encoding="utf-8").strip() if version_file.exists() else "" |
| if not re.fullmatch(r"\d+\.\d+\.\d+", version): |
| fail("VERSION missing or not semantic x.y.z") |
|
|
| readme = (ROOT / "README.md").read_text(encoding="utf-8") if (ROOT / "README.md").exists() else "" |
| if not readme.startswith("---\n"): |
| fail("README.md must start with Hugging Face YAML front matter") |
| else: |
| end = readme.find("\n---", 4) |
| if end < 0: |
| fail("README.md YAML front matter is not closed") |
| else: |
| meta = readme[4:end] |
| for required in ("title:", "sdk: docker", "app_port: 7860"): |
| if required not in meta: |
| fail(f"README.md metadata missing: {required}") |
|
|
| app_html = ROOT / "app.html" |
| if not app_html.exists(): |
| fail("root app.html is missing") |
| else: |
| html = app_html.read_text(encoding="utf-8") |
| if '__APP_VERSION__' not in html: |
| fail("app.html must use __APP_VERSION__ placeholder") |
| if 'data-view="projectPackage"' not in html: |
| fail("project data package sidebar entry is missing") |
|
|
| ui = ROOT / "ui_server.py" |
| if not ui.exists(): |
| fail("ui_server.py is missing") |
| else: |
| text = ui.read_text(encoding="utf-8") |
| if 'with_name("VERSION")' not in text: |
| fail("ui_server.py must load VERSION as the single version source") |
| if 'with_name("app.html")' not in text: |
| fail("ui_server.py must serve root app.html") |
| if 'templates/app.html' in text: |
| fail("ui_server.py still references templates/app.html") |
|
|
| if (ROOT / "templates" / "app.html").exists(): |
| fail("duplicate templates/app.html must not be shipped") |
|
|
| for forbidden in (".pytest_cache", "__pycache__"): |
| matches = [p for p in ROOT.rglob(forbidden) if p.is_dir()] |
| if matches: |
| fail(f"forbidden cache directory present: {matches[0].relative_to(ROOT)}") |
| if list(ROOT.rglob("*.pyc")): |
| fail("compiled .pyc files must not be shipped") |
|
|
|
|
| for required in ( |
| ROOT / "static" / "css" / "app.css", |
| ROOT / "static" / "js" / "app.js", |
| ROOT / "views" / "admin.py", |
| ROOT / "services" / "project_planner.py", |
| ROOT / "routes" / "data_health.py", |
| ): |
| if not required.exists(): |
| fail(f"P0 architecture file missing: {required.relative_to(ROOT)}") |
|
|
| for name in ("Dockerfile", "start.sh", "requirements.txt"): |
| if not (ROOT / name).exists(): |
| fail(f"deployment file missing: {name}") |
|
|
| if version and f"Current UI release: **v{version}**" not in readme: |
| warn("README current release line is not synchronized with VERSION") |
|
|
| for msg in warnings: |
| print(f"[preflight] WARNING: {msg}") |
| if errors: |
| for msg in errors: |
| print(f"[preflight] ERROR: {msg}", file=sys.stderr) |
| sys.exit(1) |
| import shutil as _shutil, subprocess as _subprocess |
| node_bin=_shutil.which("node") |
| frontend_smoke=ROOT/"scripts"/"frontend_runtime_smoke.js" |
| if node_bin and frontend_smoke.exists(): |
| proc=_subprocess.run([node_bin,str(frontend_smoke)],cwd=ROOT,capture_output=True,text=True) |
| if proc.returncode!=0: |
| error("frontend runtime smoke failed: "+(proc.stderr.strip() or proc.stdout.strip())[:500]) |
|
|
| print(f"[preflight] OK: v{version}; single template; clean deployment tree") |
|
|