haiku.rag/scripts/bump_version.py
Yiorgis Gozadinos d7c131bc28
Package the plugin for Codex from the same bundle
The Claude Code plugin moves to plugins/haiku-rag/ and gains a Codex
manifest at .codex-plugin/plugin.json; both share one .mcp.json and one
haiku-rag Agent Skill. .agents/plugins/marketplace.json serves Codex, the
Claude marketplace points at the new path, and scripts/bump_version.py
bumps both manifests. The skill declares its compatibility.
2026-09-07 11:21:25 +03:00

199 lines
6.3 KiB
Python
Executable file

#!/usr/bin/env python3
"""
Version bumping script for haiku.rag workspace.
Updates version in all pyproject.toml files, both plugin manifests, 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 (with or without extras) >= 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_plugin_version(file_path: Path, new_version: str) -> None:
"""Update the version in a plugin manifest."""
content = file_path.read_text()
updated = re.sub(
r'^(\s*"version": )"[^"]+"',
rf'\1"{new_version}"',
content,
flags=re.MULTILINE,
)
file_path.write_text(updated)
print(f"✓ Updated {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 <new_version>")
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",
]
plugin_files = [
root / "plugins" / "haiku-rag" / ".claude-plugin" / "plugin.json",
root / "plugins" / "haiku-rag" / ".codex-plugin" / "plugin.json",
]
changelog_file = root / "CHANGELOG.md"
# Check all files exist
for file in (
pyproject_files + example_pyproject_files + plugin_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)
for file in plugin_files:
update_plugin_version(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()