Skip to content

Commit 33683c4

Browse files
ci: add release.yml to publish to pypi and release to github (by "ap bump --release" or "ap release") using github workflows
1 parent df255d0 commit 33683c4

File tree

8 files changed

+140
-3
lines changed

8 files changed

+140
-3
lines changed

.env.example

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,2 @@
1-
UV_PUBLISH_TOKEN=... # Your PyPI API token for publishing to pypi.org
21
AP_AUTO_SYNC=0 # if = 1, Auto-sync pyproject.toml+afterpython.toml-> myst.yml files
32
AP_MOLAB_BADGE=1 # if = 1, auto-add molab badges to jupyter notebooks

.github/workflows/release.yml

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
name: Release to PyPI and GitHub
2+
3+
on:
4+
push:
5+
tags:
6+
- 'v*' # Trigger on all version tags (v0.1.0, v0.1.0rc1, v0.1.0.dev4, etc.)
7+
8+
jobs:
9+
release:
10+
runs-on: ubuntu-latest
11+
permissions:
12+
contents: write # Required for creating GitHub releases
13+
id-token: write # Required for PyPI trusted publishing
14+
15+
steps:
16+
- name: Checkout code
17+
uses: actions/checkout@v4
18+
19+
- name: Set up Python
20+
uses: actions/setup-python@v5
21+
with:
22+
python-version: '3.11'
23+
24+
- name: Install uv
25+
uses: astral-sh/setup-uv@v5
26+
with:
27+
version: "latest"
28+
29+
- name: Build package
30+
run: uv build
31+
32+
- name: Publish to PyPI
33+
run: uv publish
34+
35+
- name: Determine if pre-release
36+
id: check_prerelease
37+
run: |
38+
# Extract version from tag (remove 'v' prefix)
39+
VERSION="${GITHUB_REF_NAME#v}"
40+
41+
# Check if this is a pre-release or dev version
42+
# Matches: dev, rc, alpha, beta, a1, b2 (but not 'stable-version')
43+
if [[ "$VERSION" =~ (dev|rc|alpha|beta|a[0-9]+|b[0-9]+) ]]; then
44+
echo "prerelease=true" >> $GITHUB_OUTPUT
45+
else
46+
echo "prerelease=false" >> $GITHUB_OUTPUT
47+
fi
48+
49+
- name: Create GitHub Release
50+
uses: softprops/action-gh-release@v2
51+
with:
52+
tag_name: ${{ github.ref_name }}
53+
name: ${{ github.ref_name }}
54+
generate_release_notes: false
55+
prerelease: ${{ steps.check_prerelease.outputs.prerelease }}
56+
files: dist/* # Attach built packages (wheel + sdist)

afterpython/cli/commands/bump.py

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,18 @@ def bump(ctx, release: bool, pre: bool):
3737
# bump using cz bump's default behavior
3838
if release:
3939
subprocess.run(["ap", "cz", "bump", *ctx.args])
40+
41+
# Get the new version after bump
42+
metadata = read_metadata()
43+
new_version = metadata.version
44+
if new_version is None:
45+
raise click.ClickException("Unable to read version after bump")
46+
47+
tag = f"v{new_version}"
48+
49+
# Auto-push the specific tag to trigger release workflow
50+
click.echo(f"\n🚀 Pushing tag {tag} to trigger release workflow...")
51+
subprocess.run(["git", "push", "origin", tag])
4052
else:
4153
metadata = read_metadata()
4254
version = metadata.version
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
import subprocess
2+
3+
import click
4+
5+
6+
@click.command()
7+
@click.option(
8+
"--force",
9+
"-f",
10+
is_flag=True,
11+
help="Force release even for dev versions (not recommended)",
12+
)
13+
def release(force: bool):
14+
"""Manually trigger a release by pushing the current version tag.
15+
16+
This command is used to manually publish dev versions or pre-releases to PyPI
17+
and create GitHub releases. It pushes the tag for the current version, which
18+
triggers the GitHub Actions release workflow.
19+
20+
By default, releases are only allowed for non-dev versions (stable releases
21+
and pre-releases like rc, alpha, beta). Use --force to release dev versions.
22+
23+
The release workflow will:
24+
- Run tests in CI
25+
- Publish to PyPI (if tests pass)
26+
- Create a GitHub release (if tests pass)
27+
28+
Examples:
29+
ap bump --pre # Bump to pre-release (e.g., 0.1.0rc1)
30+
ap release # Push tag → triggers release workflow
31+
32+
ap bump # Bump dev version (e.g., 0.1.0.dev4)
33+
ap release --force # Push tag → triggers release workflow (dev version)
34+
"""
35+
from afterpython.tools.pyproject import read_metadata
36+
37+
# Get current version from pyproject.toml
38+
metadata = read_metadata()
39+
version = metadata.version
40+
41+
if version is None:
42+
raise click.ClickException("Unable to read version from pyproject.toml")
43+
44+
# Check if this is a dev version
45+
if version.is_devrelease and not force:
46+
raise click.ClickException(
47+
f"Cannot release dev version '{version}' without --force flag.\n"
48+
f"Dev versions are typically not published to PyPI.\n"
49+
f"Use 'ap bump --release' for stable releases (auto-releases),\n"
50+
f"or 'ap bump --pre' then 'ap release' for pre-releases,\n"
51+
f"or 'ap release --force' to release this dev version anyway."
52+
)
53+
54+
tag = f"v{version}"
55+
56+
click.echo(f"🏷️ Pushing tag {tag} to trigger release workflow...")
57+
result = subprocess.run(["git", "push", "origin", tag], capture_output=False)
58+
59+
if result.returncode != 0:
60+
click.echo(f"\n❌ Failed to push tag (exit code {result.returncode})", err=True)
61+
raise click.ClickException("Git push failed")
62+
63+
click.echo(f"✅ Tag {tag} pushed successfully")
64+
click.echo("\n📋 Check GitHub Actions to see the release workflow progress.")
65+
66+
if version.is_devrelease:
67+
click.echo(" ⚠️ Warning: Publishing a dev version to PyPI and GitHub")

afterpython/cli/main.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
from afterpython.cli.commands.commitizen import commitizen
2323
from afterpython.cli.commands.commit import commit
2424
from afterpython.cli.commands.bump import bump
25+
from afterpython.cli.commands.release import release
2526

2627

2728
@tui(command="tui", help="Open terminal UI")
@@ -67,3 +68,4 @@ def afterpython_group(ctx):
6768
afterpython_group.add_command(commitizen, name="cz")
6869
afterpython_group.add_command(commit)
6970
afterpython_group.add_command(bump)
71+
afterpython_group.add_command(release)

afterpython/cz.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
[tool.commitizen]
22
name = "cz_conventional_commits"
33
# name = "cz_customize"
4-
tag_format = "$version"
4+
tag_format = "v$version"
55
version_scheme = "pep440"
66
version_provider = "uv"
77
update_changelog_on_bump = false

afterpython/doc/references/roadmap.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,4 +7,5 @@ This roadmap is tentative and subject to change
77
- full-text search engine using pagefind
88
- AI chatbot like kapa.ai using WebLLM
99
- incremental build, only build changed content (for `ap dev`)
10+
- integrate with `git-cliff` for changelog generation
1011
- integrate with `pixi`, supports `conda install`

afterpython/tools/commitizen.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
"tool": {
66
"commitizen": {
77
"name": "cz_conventional_commits",
8-
"tag_format": "$version",
8+
"tag_format": "v$version",
99
"version_scheme": "pep440",
1010
"version_provider": "uv",
1111
"update_changelog_on_bump": False,

0 commit comments

Comments
 (0)