Publish DevRunbook source
Managed validation / full (push) Successful in 3m18s

This commit is contained in:
DevRunbook release export
2026-09-03 04:09:17 +02:00
commit cfd2804e27
928 changed files with 161642 additions and 0 deletions
+96
View File
@@ -0,0 +1,96 @@
#!/usr/bin/env python3
"""Validate and create a deterministic DevRunbook build-pack ZIP."""
from __future__ import annotations
import argparse
import hashlib
import os
import subprocess
import sys
import zipfile
from pathlib import Path
ROOT = Path(__file__).resolve().parents[1]
DEFAULT_OUTPUT = ROOT.parent / "DevRunbook_Autonomous_Build_Pack_v1_2.zip"
FIXED_TIMESTAMP = (2026, 7, 27, 0, 0, 0)
GENERATED = {"FILE_INDEX.txt", "PACK_MANIFEST.sha256"}
def regular_files() -> list[Path]:
files: list[Path] = []
for path in ROOT.rglob("*"):
if path.is_symlink():
raise RuntimeError(f"Symlinks are not allowed in the build pack: {path.relative_to(ROOT)}")
if path.is_file():
files.append(path)
return sorted(files, key=lambda p: p.relative_to(ROOT).as_posix())
def sha256(path: Path) -> str:
digest = hashlib.sha256()
with path.open("rb") as handle:
for chunk in iter(lambda: handle.read(1024 * 1024), b""):
digest.update(chunk)
return digest.hexdigest()
def run_validator() -> None:
subprocess.run([sys.executable, str(ROOT / "scripts/validate_pack.py")], cwd=ROOT, check=True)
def generate_integrity_files() -> None:
for name in GENERATED:
(ROOT / name).unlink(missing_ok=True)
planned = [p.relative_to(ROOT).as_posix() for p in regular_files()]
planned.extend(sorted(GENERATED))
planned = sorted(set(planned))
(ROOT / "FILE_INDEX.txt").write_text("\n".join(planned) + "\n", encoding="utf-8", newline="\n")
manifest_paths = [p for p in regular_files() if p.name != "PACK_MANIFEST.sha256"]
lines = [f"{sha256(path)} {path.relative_to(ROOT).as_posix()}" for path in manifest_paths]
(ROOT / "PACK_MANIFEST.sha256").write_text("\n".join(lines) + "\n", encoding="utf-8", newline="\n")
def write_zip(output: Path) -> None:
output.parent.mkdir(parents=True, exist_ok=True)
temp = output.with_suffix(output.suffix + ".tmp")
temp.unlink(missing_ok=True)
prefix = ROOT.name
with zipfile.ZipFile(temp, "w", compression=zipfile.ZIP_DEFLATED, compresslevel=9, strict_timestamps=True) as archive:
for path in regular_files():
relative = path.relative_to(ROOT).as_posix()
info = zipfile.ZipInfo(f"{prefix}/{relative}", FIXED_TIMESTAMP)
info.create_system = 3
info.flag_bits |= 0x800
mode = 0o755 if path.parent.name == "scripts" and path.suffix == ".py" else 0o644
info.external_attr = (mode & 0xFFFF) << 16
info.compress_type = zipfile.ZIP_DEFLATED
archive.writestr(info, path.read_bytes(), compress_type=zipfile.ZIP_DEFLATED, compresslevel=9)
os.replace(temp, output)
def file_digest(path: Path) -> str:
return hashlib.sha256(path.read_bytes()).hexdigest()
def main() -> int:
parser = argparse.ArgumentParser()
parser.add_argument("--output", type=Path, default=DEFAULT_OUTPUT)
args = parser.parse_args()
output = args.output.resolve()
if ROOT in output.parents:
raise SystemExit("Output ZIP must be outside the build-pack directory")
run_validator()
generate_integrity_files()
run_validator()
write_zip(output)
subprocess.run([sys.executable, str(ROOT / "scripts/verify_archive.py"), str(output)], check=True)
print(f"Archive: {output}")
print(f"SHA-256: {file_digest(output)}")
return 0
if __name__ == "__main__":
raise SystemExit(main())