#!/usr/bin/env python3
"""Version bumping script for persistproc."""
import argparse
import re
import subprocess
import sys
from pathlib import Path
def get_project_dir() -> Path:
"""Get the project directory (parent of scripts/)."""
return Path(__file__).parent.parent
def get_current_version() -> str:
"""Extract current version from pyproject.toml."""
pyproject_path = get_project_dir() / "pyproject.toml"
content = pyproject_path.read_text()
match = re.search(r'^version = "([^"]+)"', content, re.MULTILINE)
if not match:
raise ValueError("Could not find version in pyproject.toml")
return match.group(1)
def parse_version(version: str) -> tuple[int, int, int]:
"""Parse version string into major, minor, patch components."""
parts = version.split(".")
if len(parts) != 3:
raise ValueError(f"Invalid version format: {version}")
try:
return int(parts[0]), int(parts[1]), int(parts[2])
except ValueError as err:
raise ValueError(f"Invalid version format: {version}") from err
def bump_version(version: str, bump_type: str) -> str:
"""Bump version based on type."""
major, minor, patch = parse_version(version)
if bump_type == "major":
major += 1
minor = 0
patch = 0
elif bump_type == "minor":
minor += 1
patch = 0
elif bump_type == "patch":
patch += 1
else:
raise ValueError(f"Invalid bump type: {bump_type}")
return f"{major}.{minor}.{patch}"
def update_file(file_path: Path, pattern: str, replacement: str) -> None:
"""Update a file with regex pattern replacement."""
content = file_path.read_text()
new_content = re.sub(pattern, replacement, content, flags=re.MULTILINE)
if content == new_content:
raise ValueError(f"Pattern not found in {file_path}")
file_path.write_text(new_content)
def update_version_files(new_version: str) -> None:
"""Update all files that contain version information."""
project_dir = get_project_dir()
# Update pyproject.toml
update_file(
project_dir / "pyproject.toml",
r'^version = "[^"]+"',
f'version = "{new_version}"',
)
# Update persistproc/__init__.py
update_file(
project_dir / "persistproc" / "__init__.py",
r'^__version__ = "[^"]+"',
f'__version__ = "{new_version}"',
)
def sync_dependencies() -> None:
"""Update uv.lock by syncing dependencies."""
project_dir = get_project_dir()
result = subprocess.run(
["uv", "sync", "--extra", "docs", "--extra", "dev"],
cwd=project_dir,
capture_output=True,
text=True,
)
if result.returncode != 0:
print(f"Warning: uv sync failed: {result.stderr}", file=sys.stderr)
def create_commit(new_version: str) -> None:
"""Create a git commit with the version."""
project_dir = get_project_dir()
# Add all changes
result = subprocess.run(
["git", "add", "-A"],
cwd=project_dir,
capture_output=True,
text=True,
)
if result.returncode != 0:
print(f"Warning: git add failed: {result.stderr}", file=sys.stderr)
return
# Create commit
commit_message = f"v{new_version}"
result = subprocess.run(
["git", "commit", "-m", commit_message],
cwd=project_dir,
capture_output=True,
text=True,
)
if result.returncode != 0:
print(f"Warning: git commit failed: {result.stderr}", file=sys.stderr)
return
print(f"✅ Created commit: {commit_message}")
def main() -> None:
"""Main function."""
parser = argparse.ArgumentParser(description="Bump version for persistproc")
group = parser.add_mutually_exclusive_group()
group.add_argument("--major", action="store_const", const="major", dest="bump_type")
group.add_argument("--minor", action="store_const", const="minor", dest="bump_type")
group.add_argument("--patch", action="store_const", const="patch", dest="bump_type")
parser.set_defaults(bump_type="patch")
args = parser.parse_args()
try:
current_version = get_current_version()
print(f"Current version: {current_version}")
new_version = bump_version(current_version, args.bump_type)
print(f"New version: {new_version}")
update_version_files(new_version)
sync_dependencies()
create_commit(new_version)
print("✅ Version bumped successfully")
print("📝 Files updated:")
print(" - pyproject.toml")
print(" - persistproc/__init__.py")
print(" - uv.lock")
except Exception as e:
print(f"Error: {e}", file=sys.stderr)
sys.exit(1)
if __name__ == "__main__":
main()