"""deploy_gas.py -- WBS-5.2 GAS auto-deploy script Bundles src/gas_adapter_parts/ + src/gas/ -> Temp/gas_deploy/ then clasp push. Usage: python tools/deploy_gas.py [--dry-run] [--skip-push] """ import os import shutil import json import argparse import subprocess from pathlib import Path ROOT = Path(__file__).resolve().parent.parent SRC_PARTS = ROOT / "src" / "gas_adapter_parts" SRC_GAS = ROOT / "src" / "gas" DEPLOY_DIR = ROOT / "Temp" / "gas_deploy" # Resolve a file from multiple candidate directories def _find(filename: str) -> Path | None: candidates = [ SRC_PARTS / filename, SRC_GAS / "core" / filename, SRC_GAS / "collection" / filename, SRC_GAS / "engines" / filename, SRC_GAS / "reports" / filename, ] for c in candidates: if c.exists(): return c return None # dst_name -> [src_file, ...] (concatenated in order) BUNDLE_MAP: dict[str, list[str]] = { "appsscript.json": ["appsscript.json"], "gas_lib.gs": ["gas_lib.gs"], "data_feed_base.gs": ["data_feed_base.gs"], "gas_apex_runtime_core.gs":["gas_apex_runtime_core.gs"], # gdc_01 + gdc_02 bundled as single file (GAS project legacy name) "gas_data_collect.gs": [ "gdc_01_fetch_fundamentals.gs", "gdc_02_account_satellite.gs", ], "gdf_01_price_metrics.gs": ["gdf_01_price_metrics.gs"], "gdf_02_harness_assembly.gs": ["gdf_02_harness_assembly.gs"], "gdf_03_portfolio_gates.gs": ["gdf_03_portfolio_gates.gs"], "gdf_04_execution_quality.gs":["gdf_04_execution_quality.gs"], "gdf_05_alpha_engines.gs": ["gdf_05_alpha_engines.gs"], "gdf_06_rebalance.gs": ["gdf_06_rebalance.gs"], "gas_data_feed.gs": ["gas_data_feed.gs"], "gas_harness_rows.gs": ["gas_harness_rows.gs"], "gas_report.gs": ["gas_report.gs"], "gas_event_calendar.gs": ["gas_event_calendar.gs"], "gas_apex_alpha_watch.gs": ["gas_apex_alpha_watch.gs"], } SCRIPT_ID = "1xfeBAeeknmnBtSvrIqWXO_2hc3ByeriLUOSuOOB4YxLLHhN3zdnL7tVh" def build_deploy(dry_run: bool = False) -> bool: print("[deploy_gas] src_parts=" + str(SRC_PARTS)) print("[deploy_gas] src_gas= " + str(SRC_GAS)) print("[deploy_gas] dst= " + str(DEPLOY_DIR)) if not dry_run: if DEPLOY_DIR.exists(): shutil.rmtree(DEPLOY_DIR) DEPLOY_DIR.mkdir(parents=True, exist_ok=True) ok = True for dst_name, src_files in BUNDLE_MAP.items(): dst_path = DEPLOY_DIR / dst_name parts: list[str] = [] for sf in src_files: src_path = _find(sf) if src_path is None: print(" WARN: " + sf + " not found") ok = False continue parts.append(src_path.read_text(encoding="utf-8")) if not parts: continue content = "\n".join(parts) tag = "[DRY]" if dry_run else "write" print(" " + tag + " " + dst_name + " (" + str(len(content)) + " chars)") if not dry_run: dst_path.write_text(content, encoding="utf-8") if not dry_run: clasp_cfg = { "scriptId": SCRIPT_ID, "rootDir": str(DEPLOY_DIR.relative_to(ROOT)).replace("\\", "/"), } (ROOT / ".clasp.json").write_text( json.dumps(clasp_cfg, ensure_ascii=False, indent=2), encoding="utf-8" ) print(" write .clasp.json -> rootDir=" + clasp_cfg["rootDir"]) return ok def clasp_push() -> bool: print("[deploy_gas] clasp push -f ...") res = subprocess.run( ["npx", "@google/clasp", "push", "-f"], cwd=str(ROOT), shell=True, capture_output=True, text=True, encoding="utf-8", errors="replace", ) print(res.stdout) if res.stderr: print("STDERR: " + res.stderr[:500]) if res.returncode == 0: print("[deploy_gas] clasp push OK") return True print("[deploy_gas] clasp push FAILED rc=" + str(res.returncode)) return False def main() -> None: parser = argparse.ArgumentParser(description="GAS auto-deploy") parser.add_argument("--dry-run", action="store_true", help="List files without writing") parser.add_argument("--skip-push", action="store_true", help="Bundle only, skip clasp push") args = parser.parse_args() ok = build_deploy(dry_run=args.dry_run) if not ok: print("[deploy_gas] Some source files missing -- check warnings above") raise SystemExit(1) if args.dry_run or args.skip_push: print("[deploy_gas] dry-run/skip-push -- push skipped") return if not clasp_push(): raise SystemExit(1) print("[deploy_gas] Done. To run_all: python tools/automate_routine.py") if __name__ == "__main__": main()