This document describes the complete release process for the MAP Framework, from preparation to verification and troubleshooting.
Quick Start: Use the Release Checklist Template to create a trackable GitHub issue for each release.
- Overview
- Pre-Release Checklist
- Version Bumping
- Creating a GitHub Release
- Verification
- Rollback Procedures
- PyPI Trusted Publishing Setup
- Troubleshooting
Repository: https://github.com/azalio/map-framework PyPI Project: mapify-cli Package Manager: pip/uv CI/CD: GitHub Actions with PyPI OIDC Trusted Publishing
1. Pre-release checks → 2. Version bump → 3. Push commit & tag → 4. Create GitHub release → 5. CI/CD auto-publishes → 6. Verify on PyPI
The release process is semi-automated:
- Manual: Version bumping, tagging, GitHub release creation
- Automated: CI/CD testing, building, and PyPI publishing (triggered by git tag)
- Source Distribution:
mapify-cli-X.Y.Z.tar.gz - Wheel:
mapify_cli-X.Y.Z-py3-none-any.whl - Published to: PyPI via OIDC trusted publishing (no API tokens needed)
Before starting the release process, verify all requirements are met:
# Run full CI/CD test suite locally
pytest tests/ --cov=src/mapify_cli --cov-report=term-missing
# Run linters
black src/ tests/ --check
ruff check src/ tests/
mypy src/
# Verify package builds successfully
python -m build
twine check dist/*Expected Results:
- ✅ All tests pass (100% success rate)
- ✅ No linting errors
- ✅ Type checking passes
- ✅ Package builds without errors
- ✅
twine checkreports no issues
# Verify README.md is up to date
cat README.md | grep -i "version"
# Check CHANGELOG.md has [Unreleased] section with changes
grep -A 20 "## \\[Unreleased\\]" CHANGELOG.md
# Verify all doc links are valid (manual review)
grep -r "\\[.*\\](.*\\.md)" docs/ README.mdRequirements:
- ✅ README.md reflects current features
- ✅ CHANGELOG.md has [Unreleased] section with all changes since last release
- ✅ All documentation links are valid
- ✅ Installation instructions are accurate
# Check for security vulnerabilities
pip install pip-audit
pip-audit
# Verify dependency versions in pyproject.toml
cat pyproject.toml | grep -A 20 "dependencies"
# Test installation in clean environment
uv venv .venv-test
source .venv-test/bin/activate
pip install .
mapify --version
mapify --help
deactivate
rm -rf .venv-testRequirements:
- ✅ No known security vulnerabilities
- ✅ All dependencies are pinned to compatible versions
- ✅ Package installs successfully in clean environment
- ✅ CLI commands work correctly
# Verify on main branch
git branch --show-current
# Expected: main
# Verify working directory is clean
git status
# Expected: "nothing to commit, working tree clean"
# Pull latest changes
git pull origin main
# Verify all CI checks pass on main
gh run list --branch main --limit 1
gh run view # View latest run detailsRequirements:
- ✅ On
mainbranch - ✅ Working directory is clean (no uncommitted changes)
- ✅ Local branch is up to date with origin/main
- ✅ Latest CI run passed all checks
# Verify OIDC configuration is set up (see section below)
# This only needs to be done once per project
# Check release workflow exists
cat .github/workflows/release.ymlRequirements:
- ✅ PyPI OIDC trusted publisher configured (see PyPI Trusted Publishing Setup)
- ✅ release.yml workflow exists and is valid
MAP Framework follows Semantic Versioning 2.0.0:
- MAJOR (X.0.0): Breaking changes, incompatible API/workflow changes
- MINOR (x.Y.0): New features, backward compatible additions
- PATCH (x.y.Z): Bug fixes and minor improvements
Use the scripts/bump-version.sh script to automate version bumping:
# Syntax
./scripts/bump-version.sh <major|minor|patch|X.Y.Z>
# Examples
./scripts/bump-version.sh patch # 1.0.0 → 1.0.1 (bug fixes)
./scripts/bump-version.sh minor # 1.0.0 → 1.1.0 (new features)
./scripts/bump-version.sh major # 1.0.0 → 2.0.0 (breaking changes)
./scripts/bump-version.sh 1.2.3 # explicit version- Validates version format (semantic versioning)
- Checks for duplicate git tags (prevents version collisions)
- Updates
pyproject.tomlversion field - Updates
CHANGELOG.md:- Moves [Unreleased] content to new [X.Y.Z] section
- Adds current date
- Creates new empty [Unreleased] section
- Creates git commit with conventional commit format
- Creates annotated git tag
vX.Y.Zwith changelog excerpt
# Push commit to main
git push origin main
# Push tag (this triggers release workflow)
git push origin v1.0.1IMPORTANT: Pushing the tag triggers the CI/CD release workflow. Make sure:
- ✅ Commit is pushed first (so tag points to correct commit)
- ✅ CI checks passed on main before pushing tag
- ✅ Tag name matches version in pyproject.toml (script ensures this)
After pushing the tag, create a GitHub release to make it official and notify users.
# Example: Create release from tag with CHANGELOG excerpt
# Note: Uses awk (BSD/GNU compatible) instead of sed for better portability
gh release create v1.0.1 \
--title "MAP Framework v1.0.1" \
--notes "$(awk '/## \[1.0.1\]/,/## \[/' CHANGELOG.md | sed '$d')"
# Alternative: Via file (if command substitution has issues)
awk '/## \[1.0.1\]/,/## \[/' CHANGELOG.md | sed '$d' > /tmp/release-notes.md
gh release create v1.0.1 \
--title "MAP Framework v1.0.1" \
--notes-file /tmp/release-notes.md
rm /tmp/release-notes.md# Check release workflow triggered by tag push
gh run list --workflow=release.yml --limit 1
# View workflow run details
gh run view --log# Wait 2-5 minutes for PyPI to process upload
sleep 120
# Check package page exists
curl -f https://pypi.org/project/mapify-cli/1.0.1/
# Verify package metadata
pip index versions mapify-cli# Create temporary virtual environment
python3 -m venv .venv-test
source .venv-test/bin/activate
pip install mapify-cli==1.0.1
mapify --version
deactivate
rm -rf .venv-test# Delete remote tag
git push --delete origin v1.0.1
# Delete local tag
git tag -d v1.0.1Go to PyPI web interface:
- https://pypi.org/manage/project/mapify-cli/release/1.0.1/
- Click "Options" → "Yank release"
- Provide reason
Effect of yanking:
- ✅
pip install mapify-cliwill skip v1.0.1 - ✅
pip install mapify-cli==1.0.1still works - ✅ Package files remain available
One-time setup required before first release. Uses OpenID Connect (OIDC) for secure, token-free authentication.
- Log in to PyPI: https://pypi.org/account/login/
- Navigate to: https://pypi.org/manage/account/publishing/
- Add a new publisher:
- PyPI Project Name:
mapify-cli - Owner:
azalio - Repository name:
map-framework - Workflow name:
release.yml - Environment name: (leave empty)
- PyPI Project Name:
- Click "Add"
Check .github/workflows/release.yml has required permissions:
permissions:
id-token: write # Required for PyPI OIDC authentication
contents: read # Required for checkout# Delete incorrect tag
git push --delete origin v1.0.2
git tag -d v1.0.2
# Update pyproject.toml
./scripts/bump-version.sh 1.0.2
# Push correct tag
git push origin main
git push origin v1.0.2# Check what's changed
git status
# Commit changes
git add .
git commit -m "chore: prepare for release"-
Version Validation:
- Tag format is
vX.Y.Z - Tag version matches
pyproject.tomlversion - Version doesn't already exist on PyPI
- Tag format is
-
Git State:
- On
mainbranch - Working directory clean
- Tag pushed to origin
- On
-
CI/CD:
- Workflow triggered by tag push
- All CI tests pass
- Build step succeeds
-
PyPI OIDC:
- Trusted publisher configured correctly
- Workflow has
id-token: writepermission
# 1. Pre-release checks
git checkout main
git pull origin main
git status # Verify clean
pytest tests/ --cov # Run tests
# 2. Version bump
./scripts/bump-version.sh patch # or minor/major
# 3. Review and push
git show # Review commit
git push origin main
git push origin v1.0.1
# 4. Create GitHub release
gh release create v1.0.1 \
--title "MAP Framework v1.0.1" \
--notes "$(awk '/## \[1.0.1\]/,/## \[/' CHANGELOG.md | sed '$d')"
# 5. Monitor CI/CD
gh run watch
# 6. Verify on PyPI
curl -f https://pypi.org/project/mapify-cli/1.0.1/
pip index versions mapify-cli