#!/usr/bin/env python3 import argparse import os import shutil from pathlib import Path from script_common import newest_file, resolve_repo_root def copy_tree_contents(src: Path, dst: Path) -> None: dst.mkdir(parents=True, exist_ok=True) for item in src.iterdir(): target = dst / item.name if item.is_dir(): if target.exists(): shutil.rmtree(target) shutil.copytree(item, target) else: shutil.copy2(item, target) def main() -> int: parser = argparse.ArgumentParser(description="Sync newest built assets into output folder") parser.add_argument("--repo-root", default=None) parser.add_argument("--output-dir", default="output") parser.add_argument("--web-build-dir", default=None, help="Path to web build output") parser.add_argument("--sidecar-bin-dir", default=None, help="Path to sidecar bin root") parser.add_argument("--gateway-bin-dir", default=None, help="Path to gateway bin root") parser.add_argument("--tauri-target-dir", default=None, help="Path to tauri target root") args = parser.parse_args() repo_root = resolve_repo_root(args.repo_root) output_dir = (repo_root / args.output_dir).resolve() output_dir.mkdir(parents=True, exist_ok=True) web_build = (repo_root / args.web_build_dir).resolve() if args.web_build_dir else None if web_build is None: web_build = next((p for p in repo_root.rglob("build") if (p.parent / "package.json").exists()), None) if web_build is not None and web_build.exists(): web_out = output_dir / "webgateway" / "wwwroot" copy_tree_contents(web_build, web_out) print(f"Synced web assets -> {web_out}") sidecar_bin = (repo_root / args.sidecar_bin_dir).resolve() if args.sidecar_bin_dir else None if sidecar_bin is None: sidecar_proj = next((p.parent for p in repo_root.rglob("*.csproj") if "sidecar" in str(p).lower()), None) sidecar_bin = sidecar_proj / "bin" if sidecar_proj else None if sidecar_bin is not None: if os.name == "nt": sidecar_exe = newest_file(sidecar_bin, "*.exe") else: candidates = [p for p in sidecar_bin.rglob("*") if p.is_file() and not p.suffix and os.access(p, os.X_OK)] sidecar_exe = max(candidates, key=lambda p: p.stat().st_mtime) if candidates else None if sidecar_exe is not None: copy_tree_contents(sidecar_exe.parent, output_dir) print(f"Synced sidecar -> {output_dir}") gateway_bin = (repo_root / args.gateway_bin_dir).resolve() if args.gateway_bin_dir else None if gateway_bin is None: gateway_proj = next((p.parent for p in repo_root.rglob("*.csproj") if "gateway" in str(p).lower()), None) gateway_bin = gateway_proj / "bin" if gateway_proj else None if gateway_bin is not None: if os.name == "nt": gw_exe = newest_file(gateway_bin, "*.exe") else: candidates = [p for p in gateway_bin.rglob("*") if p.is_file() and not p.suffix and os.access(p, os.X_OK)] gw_exe = max(candidates, key=lambda p: p.stat().st_mtime) if candidates else None if gw_exe is not None: gw_out = output_dir / "webgateway" copy_tree_contents(gw_exe.parent, gw_out) print(f"Synced gateway -> {gw_out}") tauri_target = (repo_root / args.tauri_target_dir).resolve() if args.tauri_target_dir else None if tauri_target is None: tauri_target = next((p for p in repo_root.rglob("src-tauri") if (p / "target").exists()), None) tauri_target = tauri_target / "target" if tauri_target else None if tauri_target is not None: if os.name == "nt": app_exe = newest_file(tauri_target, "*.exe") else: candidates = [p for p in tauri_target.rglob("*") if p.is_file() and not p.suffix and os.access(p, os.X_OK)] app_exe = max(candidates, key=lambda p: p.stat().st_mtime) if candidates else None if app_exe is not None: shutil.copy2(app_exe, output_dir / app_exe.name) print(f"Synced desktop app ({app_exe.name}) -> {output_dir}") print("Sync complete.") return 0 if __name__ == "__main__": raise SystemExit(main())