from __future__ import annotations import argparse import asyncio import logging from pathlib import Path from .config import build_config, load_overrides_from_file from .pipeline import run_pipeline_from_config def package_project_root() -> Path: return Path(__file__).resolve().parents[1] def resolve_config_path(raw_path: str, *, project_root: Path) -> Path: path = Path(raw_path) if not path.is_absolute(): path = project_root / path return path.resolve() async def _main() -> int: project_root = package_project_root() parser = argparse.ArgumentParser(description="Run sync pipeline from config override file") parser.add_argument( "--config_path", required=True, help="Path to a JSON/YAML run profile. Relative paths are resolved from the repository root.", ) args = parser.parse_args() cfg_path = resolve_config_path(args.config_path, project_root=project_root) if not cfg_path.exists() or not cfg_path.is_file(): raise FileNotFoundError(f"Config file not found: {cfg_path}") overrides = load_overrides_from_file(cfg_path, project_root) config = build_config(project_root=project_root, overrides=overrides) mode = f"{config.local_datasource.type}->{config.remote_datasource.type}" print(f"🧩 Loaded config file: {cfg_path}") await run_pipeline_from_config( config, title=f"sync_state_machine pipeline ({mode})", print_summary=True, ) return 0 def main() -> int: try: return asyncio.run(_main()) except KeyboardInterrupt: print("\n⚠️ Interrupted by user") return 130 except Exception as exc: print(f"\n❌ Pipeline failed: {type(exc).__name__}: {exc}") return 1 finally: logging.shutdown() if __name__ == "__main__": raise SystemExit(main())