148 lines
7.0 KiB
Python
148 lines
7.0 KiB
Python
#!/usr/bin/env python3
|
|
"""Check whether the Jyotish web app is ready for PWA/Pake/Tauri packaging."""
|
|
|
|
from __future__ import annotations
|
|
|
|
import json
|
|
import re
|
|
import shutil
|
|
import subprocess
|
|
from pathlib import Path
|
|
|
|
|
|
ROOT = Path(__file__).resolve().parents[1]
|
|
APP = ROOT / "jyotish-app"
|
|
|
|
|
|
def read(path: Path) -> str:
|
|
return path.read_text(encoding="utf-8")
|
|
|
|
|
|
def require(condition: bool, label: str, failures: list[str]) -> None:
|
|
if not condition:
|
|
failures.append(label)
|
|
|
|
|
|
def probe_command(binary: str, args: list[str] | None = None) -> dict:
|
|
path = shutil.which(binary)
|
|
result = {
|
|
"binary": binary,
|
|
"available": bool(path),
|
|
"path": path,
|
|
"version": None,
|
|
}
|
|
if not path:
|
|
return result
|
|
try:
|
|
completed = subprocess.run(
|
|
[path, *(args or ["--version"])],
|
|
capture_output=True,
|
|
text=True,
|
|
timeout=4,
|
|
check=False,
|
|
)
|
|
result["version"] = (completed.stdout or completed.stderr).strip().splitlines()[0] if (completed.stdout or completed.stderr).strip() else ""
|
|
except Exception as exc:
|
|
result["version"] = f"probe_failed: {exc}"
|
|
return result
|
|
|
|
|
|
def toolchain_probe() -> dict:
|
|
probes = {
|
|
"node": probe_command("node"),
|
|
"npm": probe_command("npm"),
|
|
"rustc": probe_command("rustc"),
|
|
"cargo": probe_command("cargo"),
|
|
"xcodebuild": probe_command("xcodebuild", ["-version"]),
|
|
"pake": probe_command("pake"),
|
|
"tauri": probe_command("tauri"),
|
|
}
|
|
xcode_version = probes["xcodebuild"].get("version") or ""
|
|
xcode_ready = bool(probes["xcodebuild"]["available"] and "requires Xcode" not in xcode_version)
|
|
return {
|
|
"non_destructive": True,
|
|
"note": "Probe only checks local CLI presence/version; it does not run npm build, pake, tauri build, signing, notarization, or generate packages.",
|
|
"license_gate": {
|
|
"pake": "Pake upstream is GPL-3.0; review distribution compatibility before shipping a bundled desktop app.",
|
|
"tauri": "Tauri app distribution still needs platform permissions, sidecar lifecycle design, signing_notarization, and Apple Developer decisions on macOS.",
|
|
},
|
|
"commands": probes,
|
|
"readiness": {
|
|
"pwa": True,
|
|
"pake": bool(probes["node"]["available"] and probes["npm"]["available"] and probes["rustc"]["available"] and probes["cargo"]["available"] and probes["pake"]["available"]),
|
|
"tauri": bool(probes["node"]["available"] and probes["npm"]["available"] and probes["rustc"]["available"] and probes["cargo"]["available"] and probes["tauri"]["available"]),
|
|
"macos_signing_notarization": xcode_ready,
|
|
},
|
|
"warnings": [
|
|
"xcodebuild exists but full Xcode is not selected; macOS signing_notarization is not ready."
|
|
] if probes["xcodebuild"]["available"] and not xcode_ready else [],
|
|
}
|
|
|
|
|
|
def main() -> int:
|
|
failures: list[str] = []
|
|
package = json.loads(read(APP / "package.json"))
|
|
manifest = json.loads(read(APP / "public" / "manifest.webmanifest"))
|
|
sw = read(APP / "public" / "sw.js")
|
|
html = read(APP / "index.html")
|
|
api_server = read(ROOT / "scripts" / "jyotish_api_server.py")
|
|
main_js = read(APP / "main.js")
|
|
click_smoke = read(ROOT / "tests" / "run_frontend_click_smoke.py")
|
|
|
|
scripts = package.get("scripts", {})
|
|
require("build" in scripts, "jyotish-app/package.json missing build script", failures)
|
|
require("preview" in scripts, "jyotish-app/package.json missing preview script", failures)
|
|
require(manifest.get("name") == "Jyotish Vedic Astrology", "manifest name mismatch", failures)
|
|
require(manifest.get("display") == "standalone", "manifest display must be standalone", failures)
|
|
require(manifest.get("scope") == "/", "manifest scope must be /", failures)
|
|
require(manifest.get("start_url") == "/", "manifest start_url must be /", failures)
|
|
require(bool(manifest.get("theme_color")), "manifest theme_color missing", failures)
|
|
require(any(icon.get("src") == "/pwa-icon.svg" for icon in manifest.get("icons", [])), "manifest icon missing", failures)
|
|
require("CACHE_NAME = 'jyotish-shell-v1'" in sw, "service worker cache name missing", failures)
|
|
require("url.pathname.startsWith('/api/')" in sw, "service worker must bypass API requests", failures)
|
|
require("caches.match('/index.html')" in sw, "service worker fallback missing", failures)
|
|
require('rel="manifest"' in html and "/manifest.webmanifest" in html, "index missing manifest link", failures)
|
|
require("/pwa-icon.svg" in html, "index missing app icon", failures)
|
|
require("JYOTISH_API_HOST', '127.0.0.1'" in api_server, "API host default must stay loopback", failures)
|
|
require("Trust Center" in main_js and "Local-first" in main_js, "Trust Center status missing", failures)
|
|
require("pwa-install" in main_js and "promptPWAInstall" in main_js, "PWA install action missing", failures)
|
|
require(bool(re.search(r"127\.0\.0\.1:5200", main_js)), "Trust Center must show loopback API boundary", failures)
|
|
require("tests/run_frontend_click_smoke.py" in read(ROOT / "scripts" / "run_quality_gate.py"), "quality gate must run browser click smoke", failures)
|
|
require("--mode" in click_smoke and "all" in click_smoke, "click smoke must support --mode all", failures)
|
|
require("offline_recovery_guidance_visible" in click_smoke, "click smoke must verify offline recovery guidance", failures)
|
|
require("manifest.webmanifest" in click_smoke and "serviceWorker" in click_smoke, "click smoke must verify PWA installed shell", failures)
|
|
|
|
if failures:
|
|
print(json.dumps({"valid": False, "failures": failures}, ensure_ascii=False, indent=2))
|
|
return 1
|
|
first_launch_checks = [
|
|
{
|
|
"path": "PWA installed shell",
|
|
"command": "python3 tests/run_frontend_click_smoke.py --mode all",
|
|
"expected": "manifest.webmanifest, serviceWorker, mobile shell, online workflow, and offline recovery guidance",
|
|
},
|
|
{
|
|
"path": "Pake first launch",
|
|
"command": "cd jyotish-app && npm run build && npm run preview -- --host 127.0.0.1 --port 4173",
|
|
"expected": "URL shell can open the built app; local API still needs python3 scripts/jyotish_api_server.py on 127.0.0.1:5200",
|
|
},
|
|
{
|
|
"path": "Tauri sidecar readiness",
|
|
"command": "python3 scripts/desktop_packaging_preflight.py",
|
|
"expected": "loopback API boundary, sidecar route, manifest, service worker, and Trust Center remain visible before scaffolding",
|
|
},
|
|
]
|
|
print(json.dumps({
|
|
"valid": True,
|
|
"packaging_paths": ["pwa", "pake-url-shell", "tauri-sidecar-spike"],
|
|
"app_dir": str(APP),
|
|
"api_default": "127.0.0.1:5200",
|
|
"first_launch_checks": first_launch_checks,
|
|
"toolchain_probe": toolchain_probe(),
|
|
}, ensure_ascii=False, indent=2))
|
|
return 0
|
|
|
|
|
|
if __name__ == "__main__":
|
|
raise SystemExit(main())
|