from __future__ import annotations from pathlib import Path import shutil, subprocess, sys, re ROOT = Path(__file__).resolve().parents[2] # repo root def say(msg: str) -> None: print(msg, flush=True) def ensure_dir(p: Path) -> None: p.mkdir(parents=True, exist_ok=True) def write_if_missing(path: Path, content: str) -> None: ensure_dir(path.parent) if not path.exists(): path.write_text(content, encoding="utf-8") def copy_if_exists(src: Path, dst: Path) -> None: if src.exists(): ensure_dir(dst.parent) shutil.copy2(src, dst) def copy_if_missing(src: Path, dst: Path) -> None: dst.parent.mkdir(parents=True, exist_ok=True) if not dst.exists(): shutil.copy2(src, dst) def run(cmd: list[str], cwd: Path | None = None) -> int: proc = subprocess.Popen(cmd, cwd=cwd, stdout=sys.stdout, stderr=sys.stderr) return proc.wait() def read_version(version_file: Path | None = None) -> str: vf = version_file or (ROOT / "VERSION") return (vf.read_text(encoding="utf-8").strip() if vf.exists() else "0.0.0") def write_version(new_version: str, version_file: Path | None = None) -> None: vf = version_file or (ROOT / "VERSION") vf.write_text(new_version.strip() + "\n", encoding="utf-8") _semver = re.compile(r"^(\d+)\.(\d+)\.(\d+)$") def bump_version(kind: str = "patch", version_file: Path | None = None) -> str: cur = read_version(version_file) m = _semver.match(cur) or _semver.match("0.1.0") # default if missing major, minor, patch = map(int, m.groups()) if kind == "major": major, minor, patch = major + 1, 0, 0 elif kind == "minor": minor, patch = minor + 1, 0 else: patch += 1 new = f"{major}.{minor}.{patch}" write_version(new, version_file) return new def bundle_path(version_file: Path | None = None) -> Path: """ Return the install bundle path for the current VERSION (e.g., install/cascadingdev-0.1.2). Raises FileNotFoundError if missing. """ ver = read_version(version_file) bp = ROOT / "install" / f"cascadingdev-{ver}" if not bp.exists(): raise FileNotFoundError(f"Bundle not found: {bp}. Build it with `cascadingdev build`.") return bp