publishing.md

July 15, 2026 · View on GitHub

Publishing Releases

This is how to publish a Python package to PyPI from GitHub Actions, when using the simple-modern-uv template.

Thanks to the dynamic versioning plugin and the publish.yml workflow, you can simply create tagged releases (using standard format for the tag name, e.g. v0.1.0) on GitHub and the tag will trigger a release build, which then uploads it to PyPI.

First-Time Setup

This part is a little confusing the first time. Here is the simplest way to do it. For this project, the GitHub owner/repository is jlevy/chopdiff and the PyPI project name is chopdiff.

Note: These steps assume you already have a GitHub repo with your code pushed. If you’re setting up manually, create an empty GitHub repo (no README, no .gitignore, no license; the template already provides these) and push your code to it. See the README for details.

  1. Get a PyPI account at pypi.org and sign in.

  2. Pick a name for the project that isn’t already taken.

    • Go to https://pypi.org/project/chopdiff to see if another project with that name already exists.

    • If needed, update your pyproject.toml with the correct name.

  3. Authorize your repository to publish to PyPI:

    • Go to the publishing settings page.

    • Find “Trusted Publisher Management” and register your GitHub repo as a new “pending” trusted publisher.

    • Enter the project name, repo owner, repo name, and publish.yml as the workflow name. (You can leave the “environment name” field blank.)

  4. Create a release on GitHub:

    • Commit code and make sure it’s running correctly.

    • Go to your GitHub project page, then click on Actions tab.

    • Confirm all tests are passing in the last CI workflow. (If you want, you can even publish this template when it’s empty as just a stub project, to try all this out.)

    • Go to your GitHub project page, click on Releases.

    • Fill in the tag and the release name. Select to create a new tag, and pick a version. A good option is v0.1.0. (It’s wise to have it start with a v.)

    • Submit to create the release.

  5. Confirm it publishes to PyPI:

    • Watch for the release workflow in the GitHub Actions tab.

    • If it succeeds, you should see it appear at https://pypi.org/project/chopdiff.

Publishing Subsequent Releases

Follow this checklist for each new release.

Pre-Release Checklist

  1. Cut the release from an up-to-date main:

    git checkout main
    git pull origin main
    git status  # confirm a clean working tree
    
  2. Verify all changes are committed and pushed:

    git log origin/main..HEAD  # should be empty if pushed
    
  3. Run linting and tests locally:

    make
    uv run --locked --all-extras --group audit pip-audit
    

    The release workflow independently repeats locked installation, check-only linting, tests, and the vulnerability audit before it builds or publishes an artifact.

  4. Confirm CI is passing on main:

    gh run list --branch main --limit 3
    

    Or check the Actions tab on GitHub. The most recent run for the commit you’re about to tag must be green (a superseded older failure is fine).

  5. Determine the new version number:

    # Check current/latest version:
    gh release list --limit 1
    

    Use semantic versioning:

    • Patch: (e.g., v0.5.8v0.5.9) Bug fixes, minor changes

    • Minor: (e.g., v0.5.9v0.6.0) New features, backward-compatible

    • Major: (e.g., v0.6.0v1.0.0) Breaking changes

    Pre-1.0 convention: while the version is still 0.x, breaking changes bump the minor version (e.g. v0.2.6v0.3.0) rather than the major version, which is reserved for the first stable 1.0.0. Breaking changes are still called out explicitly in the changelog and release notes.

  6. Update the changelog:

    Add a section for the new version to CHANGELOG.md, grouped into Breaking changes / New features / Infrastructure (see existing entries). The GitHub release notes for the version should mirror this section. Commit the changelog (via a short “prepare release” PR) so it is on main at the tagged commit.

Create the Release

  1. Review changes since the last release:

    # Get the last release tag:
    LAST_TAG=$(gh release list --limit 1 --json tagName -q '.[0].tagName')
    
    # View commits since last release:
    git log ${LAST_TAG}..HEAD --oneline
    
    # View full diff:
    git diff ${LAST_TAG}..HEAD
    
  2. Write the release notes in a file, then create the release:

    Author the notes as plain Markdown in a file (see Release Notes Format below), then pass it with --notes-file. Writing the notes in a file keeps the shell out of the way: release notes routinely contain backticks and $, which a shell heredoc would try to run as commands or expand as variables. End the notes with a concrete compare link built from the actual tags (substitute the real LAST_TAG and NEW_TAG values into the URL), e.g. https://github.com/OWNER/PROJECT/compare/v0.1.0...v0.2.0.

    NEW_TAG="vX.Y.Z"  # Replace with the actual version
    
    # Edit release-notes.md in your editor, ending with the concrete compare link.
    
    gh release create "${NEW_TAG}" --title "${NEW_TAG}" --notes-file release-notes.md
    

    Alternatively, use --generate-notes for GitHub’s auto-generated notes.

  3. Verify the release published successfully:

    # Check the release workflow:
    gh run list --workflow=publish.yml --limit 1
    
    # Verify on PyPI (may take a minute):
    # https://pypi.org/project/chopdiff
    

    Once it appears on PyPI, smoke-test that the published artifact actually resolves and installs from PyPI. If your project exposes a CLI:

    uvx --from PROJECT==X.Y.Z PROJECT --version
    

Release Notes Format

Use this structure for release notes:

## What's Changed

### Bug Fixes

**Short title of fix**

Description of what was fixed and why it matters.

### New Features

**Short title of feature**

Description of the new capability.

### Breaking Changes

**Short title of breaking change**

Description of what changed and how to migrate.

### Full Changelog

https://github.com/jlevy/chopdiff/compare/vPREVIOUS...vNEW

Guidelines:

  • Use ## What's Changed as the top-level heading.

  • Group changes under ### Bug Fixes, ### New Features, ### Breaking Changes, etc. as appropriate.

  • Use **bold** for short titles of individual changes.

  • Include technical details only when helpful for users.

  • Always include the Full Changelog compare link at the end.

  • For small releases, a simple bullet list is acceptable instead of full sections.


This file was built with simple-modern-uv.