Skip to content

Add immutable releases with automated PyPI publishing#140

Merged
wscourge merged 9 commits intomainfrom
wiktor/ome-542-python-library-immutable-releases
Apr 17, 2026
Merged

Add immutable releases with automated PyPI publishing#140
wscourge merged 9 commits intomainfrom
wiktor/ome-542-python-library-immutable-releases

Conversation

@wscourge
Copy link
Copy Markdown
Contributor

@wscourge wscourge commented Apr 9, 2026

Summary

  • Rewrite .github/workflows/release.yml to run the full lint+test matrix on v* tag push, build sdist+wheel, create a GitHub Release with attached distribution files, and publish to PyPI via OIDC trusted publishing with build attestations
  • Add RELEASING.md with release process (version bump via PR, tag, automated publish), pre-release handling, and security documentation
  • Add bin/release.sh to automate the entire release flow end-to-end
  • Add pyproject.toml with PEP 517/518 build-system declaration
  • Replace CHANGELOG.md contents with a link to the GitHub Releases page, where release notes are now auto-generated from merged PR titles
  • Clean up stale CI references (Code Climate workflow name, Travis CI badge, orphaned coverage XML, outdated manual release instructions in README)
  • Fix Python version classifiers (drop untested 3.9, add tested 3.14)
  • Remove legacy setup.py publish command

bin/release.sh

bin/release.sh <patch|minor|major>

The script automates the full release process:

  1. Checks prerequisites (git, gh, jq, python3)
  2. Verifies CI is green on main, exits with a link to the failed run if not
  3. Lists open PRs targeting main and asks for confirmation to continue
  4. Lists PRs merged since the last tag (what's being released) and asks for confirmation
  5. Bumps chartmogul/version.py
  6. Stashes uncommitted work, creates a release branch, commits, pushes, and opens a PR
  7. Polls for the PR to be merged (every 10s)
  8. Tags the merge commit and pushes the tag
  9. Polls for the release workflow to complete (every 10s)
  10. Restores stashed work and prints links to the GitHub Release and PyPI package

If interrupted (Ctrl-C) during either polling step, it prints the remaining manual steps and links needed to finish the release by hand.

Pre-merge admin setup

  • Immutable Releases
    • Settings > General > Releases > Enable "Immutable releases"
  • Tag Protection Ruleset
    • Settings > Rules > Rulesets > New ruleset targeting tags matching v*
    • Enable deletion prevention
    • Enable force-push prevention
    • Enable update prevention
  • GitHub Actions Environment
    • Settings > Environments > New environment named pypi
    • Optionally configure required reviewers for an approval gate on publish
  • PyPI Trusted Publishing
    • On pypi.org go to Trusted Publisher Management
    • Add a trusted publisher with repository chartmogul/chartmogul-python, workflow release.yml, environment pypi (I didn't want to save it w/o GHA env in place)
image

Post-merge steps

  • Verify automated publishing
    • Tag a pre-release (vX.Y.Z-rc1) and confirm the full pipeline runs
    • If the previous item is OK, run bin/release.sh patch and confirm it works end to end
  • Remove manual PyPI access
    • Remove publish permissions for human accounts on pypi.org (CI is now the sole publisher)

Test plan

  • Run python3 -m build locally and verify both sdist and wheel are produced with expected contents
  • Push a test pre-release tag to exercise the full workflow (lint → test → build → publish → release)
  • Verify the GitHub Release is created with distribution files attached
  • Verify the package appears on PyPI with correct version and attestations
  • Run bin/release.sh patch against a sandbox/fork to validate the interactive flow

wscourge and others added 2 commits April 9, 2026 16:00
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Comment thread README.md Outdated
@pkopac
Copy link
Copy Markdown
Contributor

pkopac commented Apr 9, 2026

Settings updated ✅

wscourge and others added 2 commits April 10, 2026 06:06
Use the gh CLI already available on the runner, fixing the zizmor
superfluous-actions warning.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Use GITHUB_REF_NAME env var instead of github.ref_name template
expansion to fix zizmor code-injection warning. Replace --require-hashes
recommendation with uv per review feedback.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Comment thread .github/workflows/release.yml Outdated
- name: Create GitHub Release
env:
GH_TOKEN: ${{ github.token }}
run: gh release create "$GITHUB_REF_NAME" --generate-notes
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Zizmor points out that the branch name itself could potentially contain injection, I am not sure about that 😅 but if you put it into env it should not be evaluated, therefore nullify injection danger.

Replace manual twine upload with CI-based publishing triggered by tag
push. The release workflow now runs lint, tests, version verification,
builds sdist+wheel, publishes to PyPI via OIDC, and creates a GitHub
Release with attached distribution files.

- Add pyproject.toml with PEP 517/518 build-system declaration
- Remove legacy setup.py publish command
- Rewrite release.yml with lint/test/build/publish/release jobs
- Update RELEASING.md with automated pipeline and setup docs
Adds SLSA provenance attestations so users can cryptographically verify
that published packages were built from this repository by the release
workflow.
- Rename test workflow from "Run specs and generate Code Climate report"
  to "Test" (Code Climate is no longer used)
- Remove orphaned coverage XML generation (no service consumes it)
- Replace Travis CI badge with GitHub Actions badge
- Replace outdated manual releasing instructions with pointer to
  RELEASING.md
- Drop Python 3.9 classifier (untested), add 3.14 (tested)
bin/release.sh takes patch/minor/major, checks prerequisites and CI
status, shows open and merged PRs for review, bumps the version, opens
a release PR, waits for merge, tags, and polls until the release
workflow completes. If interrupted, prints remaining manual steps.

Route version bumps through PR flow instead of pushing to main
directly. Replace CHANGELOG.md contents with a link to GitHub Releases
where notes are now auto-generated from merged PR titles.
The release workflow uses actions/cache for pip dependencies in the
lint and test jobs. These are low-confidence findings since the cache
only contains pip packages (not executable code) and the jobs have
read-only permissions.
@wscourge wscourge marked this pull request as draft April 16, 2026 10:42
@wscourge wscourge changed the title Add immutable release enforcement and security docs Add immutable releases with automated PyPI publishing Apr 16, 2026
@wscourge wscourge marked this pull request as ready for review April 17, 2026 11:56
@wscourge wscourge requested a review from pkopac April 17, 2026 11:56
@pkopac
Copy link
Copy Markdown
Contributor

pkopac commented Apr 17, 2026

trusted publishing set up ✅

@wscourge wscourge merged commit f9de025 into main Apr 17, 2026
7 checks passed
@wscourge wscourge deleted the wiktor/ome-542-python-library-immutable-releases branch April 17, 2026 14:19
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants