#!/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())