Skip to content

Commit 77bcc84

Browse files
committed
chore: add prerelease stamping workflow
1 parent d587f84 commit 77bcc84

3 files changed

Lines changed: 119 additions & 0 deletions

File tree

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
name: Stamp Prerelease Version
2+
3+
on:
4+
workflow_dispatch:
5+
inputs:
6+
base_version:
7+
description: Stable base version to stamp, e.g. 2.0.1
8+
required: true
9+
default: "2.0.1"
10+
timestamp:
11+
description: Optional UTC timestamp in YYYYMMDDHHMM form
12+
required: false
13+
14+
permissions:
15+
contents: write
16+
pull-requests: write
17+
18+
jobs:
19+
stamp:
20+
runs-on: ubuntu-latest
21+
steps:
22+
- uses: actions/checkout@v4
23+
with:
24+
ref: main
25+
26+
- name: Install uv
27+
uses: astral-sh/setup-uv@v5
28+
29+
- name: Set up Python
30+
run: uv python install 3.12
31+
32+
- name: Stamp prerelease version
33+
id: stamp
34+
run: |
35+
args=(--base-version "${{ inputs.base_version }}")
36+
if [ -n "${{ inputs.timestamp }}" ]; then
37+
args+=(--timestamp "${{ inputs.timestamp }}")
38+
fi
39+
40+
version=$(python scripts/set_prerelease_version.py "${args[@]}")
41+
echo "version=$version" >> "$GITHUB_OUTPUT"
42+
43+
- name: Refresh lockfile
44+
run: uv lock
45+
46+
- name: Create pull request
47+
uses: peter-evans/create-pull-request@v7
48+
with:
49+
branch: chore/stamp-prerelease-${{ steps.stamp.outputs.version }}
50+
delete-branch: true
51+
commit-message: chore: stamp prerelease version ${{ steps.stamp.outputs.version }}
52+
title: chore: stamp prerelease version ${{ steps.stamp.outputs.version }}
53+
body: |
54+
Stamps `pyproject.toml` and `uv.lock` for the next prerelease publish.
55+
56+
Next steps:
57+
- merge this PR to `main`
58+
- create a GitHub prerelease from `main`
59+
- let `publish.yml` push the prerelease to PyPI

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,7 @@ claude mcp add nteract -- env RUNTIMED_SOCKET_PATH="$HOME/Library/Caches/runt-ni
105105

106106
- `main` publishes prerelease `nteract` builds for the 2.x transition and tracks `runtimed 2.x` prereleases.
107107
- `release/1.9.x` is the stable maintenance line for desktop `1.4.x` and stays on `runtimed 1.9.0`.
108+
- use the `Stamp Prerelease Version` GitHub Actions workflow to open a PR with the next `2.x` prerelease version before publishing from `main`
108109

109110
## Available Tools
110111

scripts/set_prerelease_version.py

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
#!/usr/bin/env python3
2+
"""Stamp the project with a PEP 440 prerelease version."""
3+
4+
from __future__ import annotations
5+
6+
import argparse
7+
import re
8+
from datetime import datetime, timezone
9+
from pathlib import Path
10+
11+
12+
def parse_args() -> argparse.Namespace:
13+
parser = argparse.ArgumentParser(description=__doc__)
14+
parser.add_argument(
15+
"--base-version",
16+
required=True,
17+
help="Stable base version in X.Y.Z form, e.g. 2.0.1",
18+
)
19+
parser.add_argument(
20+
"--timestamp",
21+
help="UTC timestamp suffix in YYYYMMDDHHMM form. Defaults to current UTC minute.",
22+
)
23+
return parser.parse_args()
24+
25+
26+
def build_version(base_version: str, timestamp: str | None) -> str:
27+
if not re.fullmatch(r"\d+\.\d+\.\d+", base_version):
28+
raise SystemExit(f"Invalid base version '{base_version}'. Expected X.Y.Z.")
29+
30+
if timestamp is None:
31+
timestamp = datetime.now(timezone.utc).strftime("%Y%m%d%H%M")
32+
elif not re.fullmatch(r"\d{12}", timestamp):
33+
raise SystemExit(f"Invalid timestamp '{timestamp}'. Expected YYYYMMDDHHMM.")
34+
35+
return f"{base_version}a{timestamp}"
36+
37+
38+
def replace_version(pyproject_path: Path, version: str) -> None:
39+
text = pyproject_path.read_text()
40+
updated_text, count = re.subn(
41+
r'(?m)^version = "[^"]+"$',
42+
f'version = "{version}"',
43+
text,
44+
count=1,
45+
)
46+
if count != 1:
47+
raise SystemExit(f"Could not update version in {pyproject_path}")
48+
pyproject_path.write_text(updated_text)
49+
50+
51+
def main() -> None:
52+
args = parse_args()
53+
version = build_version(args.base_version, args.timestamp)
54+
replace_version(Path("pyproject.toml"), version)
55+
print(version)
56+
57+
58+
if __name__ == "__main__":
59+
main()

0 commit comments

Comments
 (0)