Files
Nexus/scripts/prepare_release_artifact.py
2026-07-08 22:31:31 +08:00

138 lines
4.0 KiB
Python

#!/usr/bin/env python3
"""Prepare and verify a Nexus release artifact end-to-end on the local machine.
This script intentionally performs only local operations:
- run local release gate
- generate release diff manifest
- scan manifest entries for known environment leaks
- generate clean release tarball
- scan release tarball
- write release metadata with SHA256
- validate the tarball with deploy/apply-release-bundle.sh --validate-only
"""
from __future__ import annotations
import argparse
import json
import os
import shutil
import subprocess
import sys
from pathlib import Path
ROOT = Path(__file__).resolve().parents[1]
def run(cmd: list[str], *, cwd: Path = ROOT) -> None:
print("\n$ " + " ".join(str(c) for c in cmd), flush=True)
subprocess.run(cmd, cwd=cwd, check=True)
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 bash_path(path: Path) -> str:
"""Convert a Windows path to a Git Bash compatible /c/... path."""
resolved = path.resolve()
if os.name != "nt":
return str(resolved)
drive = resolved.drive.rstrip(":").lower()
rest = resolved.as_posix().split(":", 1)[1]
return f"/{drive}{rest}"
def main() -> int:
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument("--old-snapshot", required=True, type=Path)
parser.add_argument("--output-dir", required=True, type=Path)
parser.add_argument("--name", default="nexus-release-20260708")
parser.add_argument("--skip-pytest", action="store_true", help="Run gate without BtPanel pytest.")
parser.add_argument("--skip-validate-only", action="store_true", help="Do not run apply-release-bundle.sh --validate-only.")
args = parser.parse_args()
output_dir = args.output_dir.resolve()
output_dir.mkdir(parents=True, exist_ok=True)
manifest = output_dir / "Nexus-release-diff-manifest-20260708.md"
tarball = output_dir / f"{args.name}.tar.gz"
metadata_json = output_dir / f"{args.name}.metadata.json"
metadata_md = output_dir / "Nexus-release-artifact-metadata-20260708.md"
gate_cmd = [sys.executable, "scripts/run_local_release_gate.py"]
if not args.skip_pytest:
gate_cmd.append("--with-pytest")
run(gate_cmd)
run([
sys.executable,
"scripts/generate_release_diff.py",
"--old",
str(args.old_snapshot.resolve()),
"--new",
str(ROOT),
"--output",
str(manifest),
])
run([
sys.executable,
"scripts/scan_release_manifest.py",
"--root",
str(ROOT),
"--manifest",
str(manifest),
])
run([
sys.executable,
"scripts/generate_release_bundle.py",
"--root",
str(ROOT),
"--output",
str(tarball),
])
run([sys.executable, "scripts/scan_release_bundle.py", str(tarball)])
run([
sys.executable,
"scripts/write_release_metadata.py",
str(tarball),
"--json-output",
str(metadata_json),
"--markdown-output",
str(metadata_md),
])
metadata = json.loads(metadata_json.read_text(encoding="utf-8"))
sha256 = metadata["sha256"]
if not args.skip_validate_only:
bash = bash_cmd()
if not bash:
raise SystemExit("ERROR: bash not found; cannot run apply-release-bundle.sh --validate-only")
script = ROOT / "deploy" / "apply-release-bundle.sh"
bash_line = (
f"cd {bash_path(ROOT)} && "
f"bash {bash_path(script)} --validate-only "
f"--tarball {bash_path(tarball)} "
f"--sha256 {sha256}"
)
run([bash, "-lc", bash_line])
print("\n[prepare-release-artifact] OK")
print(f"tarball={tarball}")
print(f"sha256={sha256}")
print(f"manifest={manifest}")
print(f"metadata={metadata_json}")
return 0
if __name__ == "__main__":
raise SystemExit(main())