129 lines
4.1 KiB
Python
129 lines
4.1 KiB
Python
|
|
#!/usr/bin/env python3
|
||
|
|
"""Run local release gates for the Nexus snapshot.
|
||
|
|
|
||
|
|
This is intentionally conservative and network-free after dependencies are installed:
|
||
|
|
- Python syntax check for touched backend/test/release scripts.
|
||
|
|
- UTF-8/control-character integrity check for release-facing text files.
|
||
|
|
- /app-v2 TypeScript/Vite build.
|
||
|
|
- /app-v2 build output sensitive information scan.
|
||
|
|
- Optional BtPanel pytest suite when a Python 3.12/3.13 test env is active.
|
||
|
|
"""
|
||
|
|
|
||
|
|
from __future__ import annotations
|
||
|
|
|
||
|
|
import argparse
|
||
|
|
import os
|
||
|
|
import shutil
|
||
|
|
import subprocess
|
||
|
|
import sys
|
||
|
|
from pathlib import Path
|
||
|
|
|
||
|
|
ROOT = Path(__file__).resolve().parents[1]
|
||
|
|
FRONTEND_V2 = ROOT / "frontend-v2"
|
||
|
|
|
||
|
|
PY_COMPILE_TARGETS = [
|
||
|
|
"server/main.py",
|
||
|
|
"server/application/services/btpanel_service.py",
|
||
|
|
"server/infrastructure/btpanel/bootstrap_lock.py",
|
||
|
|
"server/infrastructure/btpanel/login_url_lock.py",
|
||
|
|
"server/infrastructure/btpanel/ssh_bootstrap.py",
|
||
|
|
"tests/test_btpanel_bootstrap_lock.py",
|
||
|
|
"tests/test_btpanel_get_config.py",
|
||
|
|
"tests/test_btpanel_login_url.py",
|
||
|
|
"tests/test_btpanel_login_url_route.py",
|
||
|
|
"tests/test_btpanel_ssh_bootstrap.py",
|
||
|
|
"tests/test_publish_release_bundle.py",
|
||
|
|
"scripts/check_backend_test_env.py",
|
||
|
|
"scripts/check_deploy_excludes.py",
|
||
|
|
"scripts/check_text_integrity.py",
|
||
|
|
"scripts/generate_release_bundle.py",
|
||
|
|
"scripts/generate_release_diff.py",
|
||
|
|
"scripts/prepare_release_artifact.py",
|
||
|
|
"scripts/publish_release_bundle.py",
|
||
|
|
"scripts/run_btpanel_tests.py",
|
||
|
|
"scripts/run_local_release_gate.py",
|
||
|
|
"scripts/scan_release_bundle.py",
|
||
|
|
"scripts/scan_release_manifest.py",
|
||
|
|
"scripts/verify_online_release.py",
|
||
|
|
"scripts/write_release_metadata.py",
|
||
|
|
]
|
||
|
|
|
||
|
|
SHELL_CHECK_TARGETS = [
|
||
|
|
"deploy/apply-release-bundle.sh",
|
||
|
|
"deploy/deploy-production.sh",
|
||
|
|
"deploy/preflight-release-host.sh",
|
||
|
|
"deploy/rsync-local-to-server.sh",
|
||
|
|
"deploy/sync_webapp_to_container.sh",
|
||
|
|
]
|
||
|
|
|
||
|
|
|
||
|
|
def npm_cmd() -> str:
|
||
|
|
return "npm.cmd" if os.name == "nt" else "npm"
|
||
|
|
|
||
|
|
|
||
|
|
def bash_cmd() -> str | None:
|
||
|
|
found = shutil.which("bash")
|
||
|
|
if found:
|
||
|
|
return found
|
||
|
|
git_bash = Path("C:/Program Files/Git/bin/bash.exe")
|
||
|
|
if git_bash.exists():
|
||
|
|
return str(git_bash)
|
||
|
|
return None
|
||
|
|
|
||
|
|
|
||
|
|
def run(cmd: list[str], *, cwd: Path = ROOT, check: bool = True) -> int:
|
||
|
|
print("\n$ " + " ".join(cmd), flush=True)
|
||
|
|
proc = subprocess.run(cmd, cwd=cwd)
|
||
|
|
if check and proc.returncode != 0:
|
||
|
|
raise SystemExit(proc.returncode)
|
||
|
|
return proc.returncode
|
||
|
|
|
||
|
|
|
||
|
|
def existing(paths: list[str]) -> list[str]:
|
||
|
|
return [p for p in paths if (ROOT / p).exists()]
|
||
|
|
|
||
|
|
|
||
|
|
def main() -> int:
|
||
|
|
parser = argparse.ArgumentParser(description=__doc__)
|
||
|
|
parser.add_argument("--with-pytest", action="store_true", help="Also run the BtPanel pytest suite after env check.")
|
||
|
|
parser.add_argument("--skip-frontend", action="store_true", help="Skip /app-v2 build:safe.")
|
||
|
|
args = parser.parse_args()
|
||
|
|
|
||
|
|
targets = existing(PY_COMPILE_TARGETS)
|
||
|
|
if not targets:
|
||
|
|
print("ERROR: no py_compile targets found", file=sys.stderr)
|
||
|
|
return 2
|
||
|
|
|
||
|
|
run([sys.executable, "-m", "py_compile", *targets])
|
||
|
|
|
||
|
|
shell_targets = existing(SHELL_CHECK_TARGETS)
|
||
|
|
if shell_targets:
|
||
|
|
bash = bash_cmd()
|
||
|
|
if not bash:
|
||
|
|
print("ERROR: bash not found; cannot syntax-check deploy shell scripts.", file=sys.stderr)
|
||
|
|
return 2
|
||
|
|
for target in shell_targets:
|
||
|
|
run([bash, "-n", target])
|
||
|
|
|
||
|
|
run([sys.executable, "scripts/check_deploy_excludes.py"])
|
||
|
|
run([sys.executable, "scripts/check_text_integrity.py"])
|
||
|
|
|
||
|
|
if not args.skip_frontend:
|
||
|
|
if not FRONTEND_V2.exists():
|
||
|
|
print(f"ERROR: frontend-v2 directory not found: {FRONTEND_V2}", file=sys.stderr)
|
||
|
|
return 2
|
||
|
|
run([npm_cmd(), "run", "build:safe"], cwd=FRONTEND_V2)
|
||
|
|
|
||
|
|
if args.with_pytest:
|
||
|
|
run([sys.executable, "scripts/run_btpanel_tests.py"])
|
||
|
|
run([sys.executable, "-m", "pytest", "tests/test_publish_release_bundle.py"])
|
||
|
|
else:
|
||
|
|
print("\n[release-gate] pytest skipped. Use --with-pytest under Python 3.12/3.13 to run BtPanel tests.")
|
||
|
|
|
||
|
|
print("\n[release-gate] OK")
|
||
|
|
return 0
|
||
|
|
|
||
|
|
|
||
|
|
if __name__ == "__main__":
|
||
|
|
raise SystemExit(main())
|