diff --git a/Makefile b/Makefile index 56a08eb..03233d9 100644 --- a/Makefile +++ b/Makefile @@ -1,4 +1,4 @@ -.PHONY: install sync test lint format typecheck check generate docs clean build +.PHONY: install sync test lint format typecheck check generate docs clean build bump release # Bootstrap: install + dev deps, build the local venv. install sync: @@ -73,3 +73,21 @@ clean: # Build wheel + sdist via hatchling. build: uv build + +# Bump the version (pyproject.toml + src/zenrows/__version__.py) and relock. +# PART defaults to patch; pass PART=minor, PART=major, or an explicit X.Y.Z. +# Local-only: stages the change but does not commit, push, or tag. +PART ?= patch +bump: + uv run python scripts/bump_version.py $(PART) + uv lock + git add pyproject.toml src/zenrows/__version__.py uv.lock + +# Bump, commit, push to main, and cut the GitHub release that triggers the +# PyPI publish workflow (.github/workflows/release.yml). Pushes to main and +# publishes a public release — confirm the diff before running this. +release: bump + $(eval NEW_VERSION := $(shell grep -m1 '^version = ' pyproject.toml | sed -E 's/version = "(.*)"/\1/')) + git commit -m "chore: bump version to $(NEW_VERSION)" + git push origin main + gh release create "v$(NEW_VERSION)" --title "v$(NEW_VERSION)" --generate-notes diff --git a/scripts/bump_version.py b/scripts/bump_version.py new file mode 100644 index 0000000..6ef1d1d --- /dev/null +++ b/scripts/bump_version.py @@ -0,0 +1,68 @@ +"""Bump the SDK version in pyproject.toml and src/zenrows/__version__.py. + +Usage: + uv run python scripts/bump_version.py patch|minor|major| + +Keeps the two hardcoded version strings in sync (there's no dynamic +versioning set up — hatchling reads pyproject.toml's `version` directly). +""" + +import re +import sys +from pathlib import Path + +ROOT = Path(__file__).resolve().parent.parent +PYPROJECT = ROOT / "pyproject.toml" +VERSION_FILE = ROOT / "src" / "zenrows" / "__version__.py" + +VERSION_RE = re.compile(r"^(\d+)\.(\d+)\.(\d+)$") + + +def current_version() -> str: + text = PYPROJECT.read_text() + match = re.search(r'^version = "(.*)"$', text, flags=re.MULTILINE) + if not match: + raise SystemExit("could not find version = \"...\" in pyproject.toml") + return match.group(1) + + +def next_version(current: str, bump: str) -> str: + match = VERSION_RE.match(current) + if match: + major, minor, patch = (int(part) for part in match.groups()) + if bump == "major": + return f"{major + 1}.0.0" + if bump == "minor": + return f"{major}.{minor + 1}.0" + if bump == "patch": + return f"{major}.{minor}.{patch + 1}" + # Not patch/minor/major, or current version isn't plain semver: treat + # the argument as an explicit version string. + if not VERSION_RE.match(bump): + raise SystemExit(f"'{bump}' is not 'patch', 'minor', 'major', or a X.Y.Z version") + return bump + + +def write_version(path: Path, pattern: str, replacement: str) -> None: + text = path.read_text() + new_text, count = re.subn(pattern, replacement, text, count=1, flags=re.MULTILINE) + if count != 1: + raise SystemExit(f"could not find version string to replace in {path}") + path.write_text(new_text) + + +def main() -> None: + if len(sys.argv) != 2: + raise SystemExit(__doc__) + + old = current_version() + new = next_version(old, sys.argv[1]) + + write_version(PYPROJECT, r'^version = ".*"$', f'version = "{new}"') + write_version(VERSION_FILE, r'^__version__ = ".*"$', f'__version__ = "{new}"') + + print(f"{old} -> {new}") + + +if __name__ == "__main__": + main()