Skip to content

Commit bf45c15

Browse files
committed
feat: add initial implementation of setup-release nox session and corresponding script prior to review
1 parent 8017439 commit bf45c15

File tree

2 files changed

+143
-0
lines changed

2 files changed

+143
-0
lines changed

noxfile.py

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,7 @@
7272

7373
BUMP_VERSION_SCRIPT: Path = SCRIPTS_FOLDER / "bump-version.py"
7474
GET_RELEASE_NOTES_SCRIPT: Path = SCRIPTS_FOLDER / "get-release-notes.py"
75+
SETUP_RELEASE_SCRIPT: Path = SCRIPTS_FOLDER / "setup-release.py"
7576
TAG_VERSION_SCRIPT: Path = SCRIPTS_FOLDER / "tag-version.py"
7677

7778

@@ -228,6 +229,20 @@ def merge_demo_feature(session: Session, demo: RepoMetadata) -> None:
228229
session.run("uv", "run", MERGE_DEMO_FEATURE_SCRIPT, *args)
229230

230231

232+
@nox.session(python=DEFAULT_TEMPLATE_PYTHON_VERSION, name="setup-release")
233+
def setup_release(session: Session) -> None:
234+
"""Prepare a release by creating a release branch and bumping the version.
235+
236+
Creates a release branch from develop, bumps the version using CalVer,
237+
and creates the initial bump commit. Does not push any changes.
238+
239+
Usage:
240+
nox -s setup-release # Auto-increment micro for current month
241+
nox -s setup-release -- 5 # Force micro version to 5
242+
"""
243+
session.install_and_run_script(SETUP_RELEASE_SCRIPT, *session.posargs)
244+
245+
231246
@nox.session(python=False, name="bump-version")
232247
def bump_version(session: Session) -> None:
233248
"""Bump version using CalVer (YYYY.MM.MICRO).

scripts/setup-release.py

Lines changed: 128 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,128 @@
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

Comments
 (0)