#!/usr/bin/env python3 """ Version bumping script for haiku.rag workspace. Updates version in all pyproject.toml files and CHANGELOG.md. """ import re import subprocess import sys from datetime import date from pathlib import Path def get_current_version(file_path: Path) -> str: """Extract current version from pyproject.toml.""" content = file_path.read_text() match = re.search(r'^version = "([^"]+)"', content, re.MULTILINE) if not match: raise ValueError(f"Could not find version in {file_path}") return match.group(1) def update_version_in_file(file_path: Path, new_version: str) -> None: """Update version in a pyproject.toml file.""" content = file_path.read_text() updated = re.sub( r'^version = "[^"]+"', f'version = "{new_version}"', content, flags=re.MULTILINE ) file_path.write_text(updated) print(f"✓ Updated {file_path.relative_to(Path.cwd())}") def update_dependency_version(file_path: Path, new_version: str) -> None: """Update haiku.rag-slim dependency version in root pyproject.toml.""" content = file_path.read_text() updated = re.sub( r"(haiku\.rag-slim\[.*?\])==[0-9.]+", rf"\1=={new_version}", content ) file_path.write_text(updated) print(f"✓ Updated haiku.rag-slim dependency in {file_path.relative_to(Path.cwd())}") def update_example_dependencies(file_path: Path, new_version: str) -> None: """Update haiku.rag and haiku.rag-slim dependency versions in example pyproject.toml files.""" content = file_path.read_text() # Update haiku.rag-slim[...] >= X.Y.Z updated = re.sub( r"(haiku\.rag-slim\[.*?\])>=[0-9.]+", rf"\1>={new_version}", content ) # Update haiku.rag >= X.Y.Z updated = re.sub(r"(haiku\.rag)>=[0-9.]+", rf"\1>={new_version}", updated) file_path.write_text(updated) print(f"✓ Updated example dependencies in {file_path.relative_to(Path.cwd())}") def update_changelog(changelog_path: Path, new_version: str) -> None: """Update CHANGELOG.md with new version.""" content = changelog_path.read_text() today = date.today().isoformat() # Replace [Unreleased] with new version updated = re.sub( r"## \[Unreleased\]", f"## [Unreleased]\n\n## [{new_version}] - {today}", content, count=1, ) # Update comparison links # Find the old [Unreleased] link old_unreleased_match = re.search( r"\[Unreleased\]: https://github\.com/ggozad/haiku\.rag/compare/([^.]+)\.\.\.HEAD", updated, ) if old_unreleased_match: prev_version = old_unreleased_match.group(1) # Update [Unreleased] link updated = re.sub( r"\[Unreleased\]: https://github\.com/ggozad/haiku\.rag/compare/[^.]+\.\.\.HEAD", f"[Unreleased]: https://github.com/ggozad/haiku.rag/compare/{new_version}...HEAD", updated, ) # Add new version link after [Unreleased] updated = re.sub( r"(\[Unreleased\]: https://github\.com/ggozad/haiku\.rag/compare/[^\n]+\n)", f"\\1[{new_version}]: https://github.com/ggozad/haiku.rag/compare/{prev_version}...{new_version}\n", updated, ) changelog_path.write_text(updated) print(f"✓ Updated {changelog_path.relative_to(Path.cwd())}") def main(): if len(sys.argv) != 2: print("Usage: python scripts/bump_version.py ") print("Example: python scripts/bump_version.py 0.14.0") sys.exit(1) new_version = sys.argv[1] # Validate version format if not re.match(r"^\d+\.\d+\.\d+$", new_version): print(f"Error: Invalid version format '{new_version}'") print("Version must be in format: X.Y.Z (e.g., 0.14.0)") sys.exit(1) root = Path(__file__).parent.parent # Files to update pyproject_files = [ root / "pyproject.toml", root / "haiku_rag_slim" / "pyproject.toml", root / "evaluations" / "pyproject.toml", ] example_pyproject_files = [ root / "app" / "backend" / "pyproject.toml", root / "examples" / "a2a-server" / "pyproject.toml", ] changelog_file = root / "CHANGELOG.md" # Check all files exist for file in pyproject_files + example_pyproject_files + [changelog_file]: if not file.exists(): print(f"Error: {file} not found") sys.exit(1) # Get current version from root pyproject.toml current_version = get_current_version(pyproject_files[0]) print(f"Current version: {current_version}") print(f"New version: {new_version}") print() # Confirm response = input("Proceed with version bump? [y/N] ") if response.lower() != "y": print("Aborted.") sys.exit(0) print() # Update all pyproject.toml files for file in pyproject_files: update_version_in_file(file, new_version) # Update haiku.rag-slim dependency version in root pyproject.toml update_dependency_version(pyproject_files[0], new_version) # Update example project dependencies for file in example_pyproject_files: update_example_dependencies(file, new_version) # Update CHANGELOG.md update_changelog(changelog_file, new_version) # Run uv sync to update lock file print() print("Running uv sync...") try: subprocess.run(["uv", "sync"], check=True, cwd=root) print("✓ Lock file updated") except subprocess.CalledProcessError as e: print(f"Error: uv sync failed with exit code {e.returncode}") sys.exit(1) print() print(f"✓ Version bumped from {current_version} to {new_version}") if __name__ == "__main__": main()