Skip to content

Commit 6afbfbf

Browse files
committed
chore: add release command
1 parent c553ac6 commit 6afbfbf

2 files changed

Lines changed: 115 additions & 1 deletion

File tree

Makefile

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,12 @@
1-
.PHONY: lint
1+
.PHONY: lint release
22

33
lint:
44
@echo "Running linters... 🔄"
55
pre-commit install
66
pre-commit run -a
77
@echo "Linters completed. ✅"
8+
9+
release:
10+
@python tools/prepare_release.py
11+
@uv sync
12+
@uv lock --upgrade

tools/prepare_release.py

Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
#!/usr/bin/env python3
2+
# Copyright (c) 2025. All rights reserved.
3+
"""Prepare a new release by bumping version numbers."""
4+
5+
import re
6+
import sys
7+
from enum import StrEnum
8+
from pathlib import Path
9+
10+
from rich.console import Console
11+
from rich.prompt import Prompt
12+
13+
console = Console()
14+
15+
16+
class BumpType(StrEnum):
17+
"""Enum for version bump types."""
18+
19+
PATCH = "patch"
20+
MINOR = "minor"
21+
MAJOR = "major"
22+
23+
24+
def get_current_version(pyproject_path: Path) -> str:
25+
"""Extract current version from pyproject.toml.
26+
27+
Returns:
28+
Current version string.
29+
30+
"""
31+
content = pyproject_path.read_text(encoding="utf-8")
32+
match = re.search(r'version = "(\d+\.\d+\.\d+)"', content)
33+
if not match:
34+
console.print("[red]Could not find version in pyproject.toml[/red]")
35+
sys.exit(1)
36+
return match.group(1)
37+
38+
39+
def bump_version(current_version: str, bump_type: BumpType) -> str:
40+
"""Calculate new version based on bump type.
41+
42+
Returns:
43+
New version string.
44+
45+
"""
46+
major, minor, patch = map(int, current_version.split("."))
47+
if bump_type == BumpType.MAJOR:
48+
major += 1
49+
minor = 0
50+
patch = 0
51+
elif bump_type == BumpType.MINOR:
52+
minor += 1
53+
patch = 0
54+
elif bump_type == BumpType.PATCH:
55+
patch += 1
56+
return f"{major}.{minor}.{patch}"
57+
58+
59+
def update_file(path: Path, pattern: str, replacement: str) -> None:
60+
"""Update file content using regex pattern."""
61+
content = path.read_text(encoding="utf-8")
62+
if not re.search(pattern, content):
63+
console.print(f"[yellow]Warning: Pattern not found in {path.name}[/yellow]")
64+
return
65+
new_content = re.sub(pattern, replacement, content)
66+
path.write_text(new_content, encoding="utf-8")
67+
68+
69+
def main() -> None:
70+
"""Execute the release preparation process."""
71+
root_dir = Path(__file__).parent.parent
72+
pyproject_path = root_dir / "pyproject.toml"
73+
cli_path = root_dir / "src" / "cli.py"
74+
75+
if not pyproject_path.exists() or not cli_path.exists():
76+
console.print("[red]Could not find project files.[/red]")
77+
sys.exit(1)
78+
79+
current_version = get_current_version(pyproject_path)
80+
console.print(f"Current version: [bold cyan]{current_version}[/bold cyan]")
81+
82+
bump_type = Prompt.ask(
83+
"Select bump type",
84+
choices=[t.value for t in BumpType],
85+
default=BumpType.PATCH.value,
86+
)
87+
88+
new_version = bump_version(current_version, BumpType(bump_type))
89+
console.print(f"Bumping to: [bold green]{new_version}[/bold green]")
90+
91+
# Update pyproject.toml
92+
update_file(
93+
pyproject_path,
94+
r'version = "\d+\.\d+\.\d+"',
95+
f'version = "{new_version}"',
96+
)
97+
console.print(f"[green]✓[/green] Updated {pyproject_path.name}")
98+
99+
# Update src/cli.py
100+
update_file(
101+
cli_path,
102+
r'version="\d+\.\d+\.\d+"',
103+
f'version="{new_version}"',
104+
)
105+
console.print(f"[green]✓[/green] Updated {cli_path.name}")
106+
107+
108+
if __name__ == "__main__":
109+
main()

0 commit comments

Comments
 (0)