Releasing¶
This project ships releases automatically. As a maintainer you only ever need to merge pull requests; the rest is handled by GitHub Actions.
📋 Before merging the release PR, run the UAT checklist end-to-end on a real Windows + Visio machine. Section J (release artefacts) verifies the artefacts produced by this very pipeline.
How a release happens¶
- You merge feature PRs to
mainwith Conventional Commit titles (feat:,fix:,feat!:for breaking changes, etc.). release-pleaseopens a Release PR that bumps the version invisiowings/__init__.py,setup.py, and.release-please-manifest.json, and updatesCHANGELOG.mdfrom the new commits.- You merge the Release PR. That creates the
vX.Y.Ztag, publishes a GitHub Release, and triggers.github/workflows/publish.yml. publish.ymlruns:- Builds wheel + sdist (
python -m build), validates withtwine check. - Builds the standalone Windows EXE via PyInstaller, smoke-tests it, and attaches it to the Release.
- Generates a CycloneDX SBOM (
sbom.cdx.json) and a license report (licenses.json). - Signs both with Sigstore via keyless OIDC, producing
sbom.cdx.json.sigstoreandlicenses.json.sigstorebundles, and attaches them to the Release. - Uploads the wheel + sdist to PyPI via OIDC Trusted Publishing — no long-lived API token required.
The whole pipeline is idempotent and re-runnable through
workflow_dispatch.
One-time setup: RELEASE_PLEASE_TOKEN¶
release-please opens its Release PR from inside the workflow. Doing so
with the default GITHUB_TOKEN requires the global org/repo setting
"Allow GitHub Actions to create and approve pull requests", which also
lets every other workflow approve its own PRs. To keep that capability
scoped to this single workflow we use a fine-grained PAT instead.
Create it once:
- https://github.com/settings/personal-access-tokens/new — choose
fine-grained PAT, give it a name like
visiowings-release-please. - Repository access: only this repository (
visiowings). - Permissions (Repository):
- Contents → Read and write (push branches + tags)
- Pull requests → Read and write (open the Release PR)
- Set an expiry of 12 months (or your org's policy) and copy the token.
- Add it to the repo as a secret:
Settings → Secrets and variables → Actions → New repository secret→ nameRELEASE_PLEASE_TOKEN, value = the PAT.
The workflow then runs as the PAT owner instead of github-actions[bot],
so the Release PR is attributed to that user.
Verifying a release¶
Every release artefact can be verified offline by anyone — no maintainer key required, since Sigstore uses short-lived certificates tied to the GitHub Actions OIDC identity.
pip install sigstore # one-time
# Download the artefact and its .sigstore bundle from the Release page,
# then verify:
sigstore verify github \
--bundle sbom.cdx.json.sigstore \
--cert-identity 'https://github.com/twobeass/visiowings/.github/workflows/publish.yml@refs/tags/vX.Y.Z' \
--cert-oidc-issuer 'https://token.actions.githubusercontent.com' \
sbom.cdx.json
A successful run prints OK: sbom.cdx.json and proves the file was
produced by publish.yml running on the vX.Y.Z tag.
Supply-chain posture: OpenSSF Scorecard¶
.github/workflows/scorecard.yml runs the
OpenSSF Scorecard every Monday and
on every push to main. Results are uploaded to GitHub
code-scanning (Security tab → Code scanning alerts) so regressions
in branch protection, pinned actions, signed releases, etc. show up
alongside CodeQL findings.
To run a local Scorecard against the public repo:
docker run --rm -e GITHUB_AUTH_TOKEN=$GITHUB_TOKEN \
gcr.io/openssf/scorecard:stable \
--repo=github.com/twobeass/visiowings
One-time setup: PyPI Trusted Publishing¶
This needs to be done once per project, before the first automated release lands on PyPI. After that, GitHub Actions can publish without any secret tokens.
- Sign in at https://pypi.org/ as an account that owns (or will own)
the
visiowingsproject. If the project does not exist yet, create the pending publisher first; PyPI will reserve the name for you. - Open the account settings: https://pypi.org/manage/account/publishing/
- Click Add a new pending publisher with these values:
| Field | Value |
|---|---|
| PyPI Project Name | visiowings |
| Owner | twobeass |
| Repository name | visiowings |
| Workflow name | publish.yml |
| Environment name | pypi |
- Save. The first time
publish.ymlruns against this configuration, PyPI promotes the pending publisher to a configured trusted publisher and uploads the dists.
If you decide to publish to TestPyPI first, repeat the steps at
https://test.pypi.org/manage/account/publishing/ and add a separate
job to publish.yml keyed off the testpypi environment.
Cutting a manual release (escape hatch)¶
If release-please is broken or you need to ship out of band:
# 1. Bump the version in pyproject.toml, visiowings/__init__.py, setup.py
# 2. Update CHANGELOG.md
# 3. Tag and push
git tag vX.Y.Z
git push origin vX.Y.Z
# 4. Create a Release on GitHub for that tag
# -> publish.yml will run automatically
Yanking a bad release¶
If a release is broken on PyPI, yank it (it stays installable for users who explicitly request that version, but disappears from the resolver):
# Web: https://pypi.org/manage/project/visiowings/release/X.Y.Z/
# CLI:
pip install twine
twine yank visiowings X.Y.Z --reason "regression in <area>"
Then ship a fix release immediately.