138 lines
4.0 KiB
Python
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())
|