Skip to content

Commit 77df1a0

Browse files
committed
Auto bump version
1 parent c3ca52f commit 77df1a0

2 files changed

Lines changed: 101 additions & 0 deletions

File tree

scripts/bump_version.py

Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
#!/usr/bin/env python3
2+
import json
3+
import os
4+
import re
5+
import sys
6+
from pathlib import Path
7+
from urllib.error import HTTPError, URLError
8+
from urllib.request import urlopen
9+
10+
ROOT = Path(__file__).resolve().parent.parent
11+
PYPROJECT = ROOT / "pyproject.toml"
12+
INIT = ROOT / "src" / "diffio" / "__init__.py"
13+
14+
BUMP_KINDS = {"major", "minor", "patch"}
15+
16+
17+
def read_project_name(text: str) -> str:
18+
match = re.search(r'^name\s*=\s*"([^"]+)"\s*$', text, flags=re.M)
19+
if not match:
20+
raise RuntimeError("Could not find project name in pyproject.toml")
21+
return match.group(1)
22+
23+
24+
def parse_version(value: str) -> tuple[int, int, int]:
25+
parts = value.split(".")
26+
if len(parts) != 3 or any(not part.isdigit() for part in parts):
27+
raise ValueError(f"Unsupported version format: {value}")
28+
return tuple(int(part) for part in parts)
29+
30+
31+
def fetch_latest_version(name: str) -> str:
32+
base_url = os.environ.get("PYPI_BASE_URL", "https://pypi.org/pypi").rstrip("/")
33+
url = f"{base_url}/{name}/json"
34+
try:
35+
with urlopen(url, timeout=20) as response:
36+
data = json.load(response)
37+
except (HTTPError, URLError, TimeoutError) as exc:
38+
print(f"Failed to fetch {url}: {exc}", file=sys.stderr)
39+
return "0.0.0"
40+
41+
versions = [
42+
version
43+
for version in data.get("releases", {}).keys()
44+
if re.fullmatch(r"\d+\.\d+\.\d+", version)
45+
]
46+
if not versions:
47+
return "0.0.0"
48+
return max(versions, key=parse_version)
49+
50+
51+
def bump_version(current: str, kind: str) -> str:
52+
major, minor, patch = parse_version(current)
53+
if kind == "major":
54+
major += 1
55+
minor = 0
56+
patch = 0
57+
elif kind == "minor":
58+
minor += 1
59+
patch = 0
60+
else:
61+
patch += 1
62+
return f"{major}.{minor}.{patch}"
63+
64+
65+
def update_file(path: Path, pattern: str, replacement: str) -> None:
66+
text = path.read_text()
67+
updated, count = re.subn(pattern, replacement, text, flags=re.M)
68+
if count != 1:
69+
raise RuntimeError(f"Expected one replacement in {path}, got {count}")
70+
path.write_text(updated)
71+
72+
73+
def main() -> int:
74+
bump = (sys.argv[1] if len(sys.argv) > 1 else os.environ.get("BUMP", "patch")).lower()
75+
if bump not in BUMP_KINDS:
76+
print("Usage: python scripts/bump_version.py [major|minor|patch]", file=sys.stderr)
77+
return 1
78+
79+
pyproject_text = PYPROJECT.read_text()
80+
name = read_project_name(pyproject_text)
81+
latest = fetch_latest_version(name)
82+
next_version = bump_version(latest, bump)
83+
84+
update_file(PYPROJECT, r'^version\s*=\s*".*"$', f'version = "{next_version}"')
85+
update_file(INIT, r'^__version__\s*=\s*".*"$', f'__version__ = "{next_version}"')
86+
87+
print(f"Bumped {name} from {latest} to {next_version}.")
88+
return 0
89+
90+
91+
if __name__ == "__main__":
92+
raise SystemExit(main())

scripts/publish.sh

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
#!/usr/bin/env bash
2+
set -euo pipefail
3+
4+
ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
5+
cd "$ROOT"
6+
7+
python scripts/bump_version.py "${1:-patch}"
8+
python -m build
9+
python -m twine upload dist/*

0 commit comments

Comments
 (0)