This document describes how to publish a new version of greybeard to PyPI.
greybeard uses PyPI's Trusted Publishing for secure, token-free releases.
-
Create the package on PyPI (first release only):
# Build locally uv pip install build python -m build # Upload manually with twine (one time only) uv pip install twine twine upload dist/*
-
Configure Trusted Publisher on PyPI:
- Go to https://pypi.org/manage/project/greybeard/settings/publishing/
- Add a new publisher:
- PyPI Project Name:
greybeard - Owner:
btotharye(your GitHub username) - Repository name:
greybeard - Workflow name:
publish.yml - Environment name:
pypi
- PyPI Project Name:
- Save
After this one-time setup, all future releases are automatic via GitHub Actions.
Version is stored in a single location: greybeard/__init__.py
The version is automatically read from there by pyproject.toml at build time, so you only need to update greybeard/__init__.py.
The easiest way to create a release:
./release.sh 0.2.0This script will:
- ✅ Create a release branch (
release-0.2.0) - ✅ Update
greybeard/__init__.pywith the new version - ✅ Create/initialize
CHANGELOG.md(if it doesn't exist) - ✅ Commit the version bump
- ✅ Push the release branch
- ✅ Open your browser to create a PR
After the PR is merged:
git checkout main
git pull origin main
git tag -a v0.2.0 -m "Release v0.2.0"
git push origin v0.2.0greybeard/__init__.py has __version__ = "0.2.0", the tag must be v0.2.0.
Then create the GitHub Release, and the workflow will automatically validate and publish to PyPI!
If you prefer manual control, follow these steps:
git checkout main
git pull origin main
git checkout -b release-0.2.0Edit greybeard/__init__.py:
__version__ = "0.2.0" # Update thisNote: pyproject.toml automatically reads the version from here, so no manual update is needed there.
If you're maintaining a CHANGELOG.md, add release notes:
## [0.2.0] - 2026-03-01
### Added
- New feature X
### Fixed
- Bug Y
### Changed
- Improved Zgit add greybeard/__init__.py CHANGELOG.md # if you have one
git commit -m "chore: bump version to 0.2.0"
git push origin release-0.2.0- Go to your repository and create a PR from
release-0.2.0tomain - Wait for CI to pass
- Get approval (if required)
- Merge the PR
After the PR is merged:
git checkout main
git pull origin main
git tag -a v0.2.0 -m "Release v0.2.0"
git push origin v0.2.0greybeard/__init__.py. If __version__ = "0.2.0", the tag must be v0.2.0.
- Go to https://github.com/btotharye/greybeard/releases/new
- Tag: Select
v0.2.0(the tag you just pushed) - *8Release title**:
v0.2.0orgreybeard v0.2.0 - Description: Summarize changes from CHANGELOG or write release notes
- Click Publish release
When you create the GitHub Release, the publishing workflow starts:
- Validation: Checks that
greybeard/__init__.pyversion matches the git tag (e.g., both are "0.2.0") - Build: Creates distribution files (
sdistandwheel) using hatchling - Publish: Publishes to PyPI using trusted publishing
- Done: New version appears at https://pypi.org/project/greybeard/
If the version mismatch validation fails, the workflow stops and alerts you to fix the discrepancy.
Monitor the workflow at: https://github.com/btotharye/greybeard/actions/workflows/publish.yml
To test the release process without publishing to production PyPI:
- Go to https://test.pypi.org/manage/project/greybeard/settings/publishing/
- Add the same trusted publisher configuration as above, but with:
- Environment name:
testpypi
- Environment name:
Instead of creating a GitHub Release, manually trigger the workflow:
- Go to https://github.com/btotharye/greybeard/actions/workflows/publish.yml
- Click Run workflow
- Select your branch
- Click Run workflow
This will publish to TestPyPI only, allowing you to test installation:
uv pip install --index-url https://test.pypi.org/simple/ --extra-index-url https://pypi.org/simple/ greybeardFollow Semantic Versioning:
- MAJOR (
1.0.0→2.0.0): Breaking changes - MINOR (
0.1.0→0.2.0): New features, backward compatible - PATCH (
0.1.0→0.1.1): Bug fixes, backward compatible
PyPI doesn't allow re-uploading the same version. Increment the version number.
The trusted publisher isn't configured. Follow the "One-time PyPI Setup" section above.
Check:
- The
pypienvironment exists in GitHub repo settings - The trusted publisher is configured on PyPI
- The workflow has
id-token: writepermissions (already configured)
Use manual workflow dispatch and modify the workflow to skip the publish-to-pypi job.
Using the helper script (recommended):
./release.sh 0.2.0
# Script updates greybeard/__init__.py, commits, and opens PR
# After PR is merged:
git checkout main
git pull origin main
git tag -a v0.2.0 -m "Release v0.2.0"
git push origin v0.2.0
# Create GitHub release when browser opens
# GitHub Actions validates version match, builds, and publishesManual approach:
# 1. Create release branch
git checkout main
git pull origin main
git checkout -b release-0.2.0
# 2. Update version in greybeard/__init__.py
# Change __version__ = "X.Y.Z" to the new version
# 3. Commit changes
git add greybeard/__init__.py
git commit -m "chore: bump version to 0.2.0"
git push origin release-0.2.0
# 4. Create and merge PR
# 5. After PR merge, tag and push (tag must match version!)
git checkout main
git pull origin main
git tag -a v0.2.0 -m "Release v0.2.0"
git push origin v0.2.0
# 6. Create GitHub Release at github.com/yourrepo/releases/new
# Select the v0.2.0 tag you just pushed
# 7. GitHub Actions validates, builds, and publishes to PyPISee the GitHub Actions workflow or open an issue.