|
| 1 | +# /// script |
| 2 | +# requires-python = ">=3.10" |
| 3 | +# dependencies = [ |
| 4 | +# "cookiecutter", |
| 5 | +# "python-dotenv", |
| 6 | +# "typer", |
| 7 | +# "tomli>=2.0.0;python_version<'3.11'", |
| 8 | +# ] |
| 9 | +# /// |
| 10 | +"""Script responsible for preparing a release of the cookiecutter-robust-python template.""" |
| 11 | + |
| 12 | +import sys |
| 13 | +from pathlib import Path |
| 14 | +from typing import Annotated |
| 15 | +from typing import Any |
| 16 | +from typing import Optional |
| 17 | + |
| 18 | +import typer |
| 19 | +from cookiecutter.utils import work_in |
| 20 | + |
| 21 | +from util import bump_version |
| 22 | +from util import calculate_calver |
| 23 | +from util import git |
| 24 | +from util import REPO_FOLDER |
| 25 | +from util import TEMPLATE |
| 26 | + |
| 27 | + |
| 28 | +try: |
| 29 | + import tomllib |
| 30 | +except ModuleNotFoundError: |
| 31 | + import tomli as tomllib |
| 32 | + |
| 33 | + |
| 34 | +cli: typer.Typer = typer.Typer() |
| 35 | + |
| 36 | + |
| 37 | +@cli.callback(invoke_without_command=True) |
| 38 | +def main( |
| 39 | + micro: Annotated[ |
| 40 | + Optional[int], |
| 41 | + typer.Argument(help="Override micro version (default: auto-increment)") |
| 42 | + ] = None, |
| 43 | +) -> None: |
| 44 | + """Prepare a release by creating a release branch and bumping the version. |
| 45 | +
|
| 46 | + Creates a release branch from develop, bumps the version using CalVer, |
| 47 | + and creates the initial bump commit. Does not push any changes. |
| 48 | +
|
| 49 | + CalVer format: YYYY.MM.MICRO |
| 50 | + """ |
| 51 | + try: |
| 52 | + current_version: str = get_current_version() |
| 53 | + new_version: str = calculate_calver(current_version, micro) |
| 54 | + |
| 55 | + typer.secho(f"Setting up release: {current_version} -> {new_version}", fg="blue") |
| 56 | + |
| 57 | + setup_release(current_version=current_version, new_version=new_version, micro=micro) |
| 58 | + |
| 59 | + typer.secho(f"Release branch created: release/{new_version}", fg="green") |
| 60 | + typer.secho("Next steps:", fg="blue") |
| 61 | + typer.secho(f" 1. Review changes and push: git push -u origin release/{new_version}", fg="white") |
| 62 | + typer.secho(" 2. Create a pull request to main", fg="white") |
| 63 | + except Exception as error: |
| 64 | + typer.secho(f"error: {error}", fg="red") |
| 65 | + sys.exit(1) |
| 66 | + |
| 67 | + |
| 68 | +def get_current_version() -> str: |
| 69 | + """Read current version from pyproject.toml.""" |
| 70 | + pyproject_path: Path = REPO_FOLDER / "pyproject.toml" |
| 71 | + with pyproject_path.open("rb") as f: |
| 72 | + data: dict[str, Any] = tomllib.load(f) |
| 73 | + return data["project"]["version"] |
| 74 | + |
| 75 | + |
| 76 | +def setup_release(current_version: str, new_version: str, micro: Optional[int] = None) -> None: |
| 77 | + """Prepares a release of the cookiecutter-robust-python template. |
| 78 | +
|
| 79 | + Creates a release branch from develop, bumps the version, and creates a release commit. |
| 80 | + Rolls back on error. |
| 81 | + """ |
| 82 | + with work_in(REPO_FOLDER): |
| 83 | + try: |
| 84 | + _setup_release(current_version=current_version, new_version=new_version, micro=micro) |
| 85 | + except Exception as error: |
| 86 | + _rollback_release(version=new_version) |
| 87 | + raise error |
| 88 | + |
| 89 | + |
| 90 | +def _setup_release(current_version: str, new_version: str, micro: Optional[int] = None) -> None: |
| 91 | + """Internal setup release logic.""" |
| 92 | + develop_branch: str = TEMPLATE.develop_branch |
| 93 | + release_branch: str = f"release/{new_version}" |
| 94 | + |
| 95 | + # Create release branch from develop |
| 96 | + typer.secho(f"Creating branch {release_branch} from {develop_branch}...", fg="blue") |
| 97 | + git("checkout", "-b", release_branch, develop_branch) |
| 98 | + |
| 99 | + # Bump version |
| 100 | + typer.secho(f"Bumping version to {new_version}...", fg="blue") |
| 101 | + bump_version(new_version) |
| 102 | + |
| 103 | + # Sync dependencies |
| 104 | + typer.secho("Syncing dependencies...", fg="blue") |
| 105 | + git("add", ".") |
| 106 | + |
| 107 | + # Create bump commit |
| 108 | + typer.secho("Creating bump commit...", fg="blue") |
| 109 | + git("commit", "-m", f"bump: version {current_version} → {new_version}") |
| 110 | + |
| 111 | + |
| 112 | +def _rollback_release(version: str) -> None: |
| 113 | + """Rolls back to the pre-existing state on error.""" |
| 114 | + develop_branch: str = TEMPLATE.develop_branch |
| 115 | + release_branch: str = f"release/{version}" |
| 116 | + |
| 117 | + typer.secho(f"Rolling back release {version}...", fg="yellow") |
| 118 | + |
| 119 | + # Checkout develop and discard changes |
| 120 | + git("checkout", develop_branch, ignore_error=True) |
| 121 | + git("checkout", ".", ignore_error=True) |
| 122 | + |
| 123 | + # Delete the release branch if it exists |
| 124 | + git("branch", "-D", release_branch, ignore_error=True) |
| 125 | + |
| 126 | + |
| 127 | +if __name__ == "__main__": |
| 128 | + cli() |
0 commit comments