#!/usr/bin/env python3 """Pack a per-channel weifile zip from the already-built staged tree. Does not rebuild natives or details. Requires `tools/build.py --apply` first. 1. Copy staged weifile (storage/app/channel-builder-new/out/weifile) 2. Replace CACACACA in index.js with the 8-char channel id 3. Zip as {artifact-root}/channel-source-new/{CHANNELID}.zip """ from __future__ import annotations import argparse import json import tempfile from pathlib import Path from zipfile import ZIP_DEFLATED, ZipFile import build as xxbb_build RESULT_MARKER = xxbb_build.RESULT_MARKER INDEX_CHANNEL_PLACEHOLDER = xxbb_build.INDEX_CHANNEL_PLACEHOLDER WEIFILE_ROOT = xxbb_build.WEIFILE_ROOT LAB_SEEDS_NAME = xxbb_build.LAB_SEEDS_NAME def staged_weifile_dir(state_root: Path) -> Path: return state_root / "out" / WEIFILE_ROOT def patch_channel_code(text: str, channel_code: str) -> str: if INDEX_CHANNEL_PLACEHOLDER not in text: raise SystemExit( f"index.js: missing {INDEX_CHANNEL_PLACEHOLDER} placeholder; " "re-run tools/build.py --apply" ) return text.replace(INDEX_CHANNEL_PLACEHOLDER, channel_code, 1) def write_zip(weifile_dir: Path, zip_path: Path) -> Path: zip_path.parent.mkdir(parents=True, exist_ok=True) if zip_path.exists(): zip_path.unlink() skip = {"_bak", "__pycache__", ".DS_Store", "decoded", "mm", "stages"} with ZipFile(zip_path, "w", compression=ZIP_DEFLATED) as archive: for path in sorted(weifile_dir.rglob("*")): if not path.is_file(): continue if path.name in skip or path.suffix == ".pyc": continue if any(part in skip for part in path.relative_to(weifile_dir).parts): continue rel = Path(WEIFILE_ROOT) / path.relative_to(weifile_dir) archive.write(path, rel.as_posix()) return zip_path def pack_channel( *, channel_code: str, weifile_src: Path, zip_out: Path, ) -> dict: code = xxbb_build.normalize_channel_code(channel_code) index_src = weifile_src / "index.js" if not index_src.is_file(): raise SystemExit( f"staged weifile missing ({index_src}). " "Run: python tools/build.py --apply" ) with tempfile.TemporaryDirectory() as tmp: dest = Path(tmp) / WEIFILE_ROOT xxbb_build.copy_tree(weifile_src, dest) index_path = dest / "index.js" index_path.write_text( patch_channel_code(index_path.read_text(encoding="utf-8"), code), encoding="utf-8", ) write_zip(dest, zip_out) return { "campaign": "xxbb", "builder_type": "new", "channel_code": code, "zip_path": str(zip_out), "weifile_src": str(weifile_src), } def main() -> int: parser = argparse.ArgumentParser( description="Pack staged weifile into public/channel-source-new/{CHANNELID}.zip" ) parser.add_argument("--channel-code", required=True, help="8-char channel id (e.g. FAFA9988)") parser.add_argument( "--state-root", type=Path, default=None, help=f"builder state (default: {xxbb_build.default_state_root()})", ) parser.add_argument( "--weifile-src", type=Path, default=None, help="staged weifile directory (default: /out/weifile)", ) parser.add_argument( "--zip-out", type=Path, required=True, help="output zip path", ) args = parser.parse_args() state_root = (args.state_root or xxbb_build.default_state_root()).resolve() weifile_src = (args.weifile_src or staged_weifile_dir(state_root)).resolve() zip_out = args.zip_out.resolve() result = pack_channel(channel_code=args.channel_code, weifile_src=weifile_src, zip_out=zip_out) seeds_path = state_root / LAB_SEEDS_NAME if seeds_path.is_file(): try: seeds = json.loads(seeds_path.read_text()) if isinstance(seeds, dict): result["seeds"] = { "deployment_seed": seeds.get("deployment_seed"), "reporting_seed": seeds.get("reporting_seed"), "channel_c": seeds.get("channel_c"), } result["domains"] = seeds.get("domains") or {"deployment": [], "reporting": []} except json.JSONDecodeError: pass print(f"packed {result['channel_code']} -> {zip_out}") print(RESULT_MARKER + json.dumps(result, separators=(",", ":"))) return 0 if __name__ == "__main__": raise SystemExit(main())