The release is a manual, multi-platform publish to PyPI, driven by
.github/workflows/release.yml (workflow_dispatch-only — it never runs on its
own). It builds the wheel matrix + sdist, publishes to PyPI, pushes the version
commit + tag to main, and creates a GitHub Release. Publishing uses PyPI
Trusted Publishing (OIDC) — no long-lived secret — and attaches PEP 740
provenance attestations.
-
Configure a PyPI Trusted Publisher (the recommended, secret-free path). On https://pypi.org/manage/account/publishing/ add a GitHub publisher. For the first release the project does not exist yet, so use the pending publisher form there:
- PyPI Project Name:
processkit-py(the bareprocesskitis taken on PyPI; the import name is stillprocesskit). - Owner / Repository: this repo.
- Workflow name:
release.yml(the filename, exactly). - Environment name: leave blank. The
publishjob sets no GitHub environment, so filling this in makes the OIDC subject mismatch and PyPI rejects the upload.
See https://docs.pypi.org/trusted-publishers/. Nothing else is needed —
release.ymlalready grantsid-token: writeand mints the credential per run.Fallback (no trusted publisher): set a
PYPI_API_TOKENrepository secret to a PyPI API token; the publish action uses it instead. (Trusted publishing is preferred — no rotation, no secret to leak.) - PyPI Project Name:
-
(Optional) TestPyPI dry-run publisher. To use
test-release.yml, add the same kind of publisher on https://test.pypi.org/manage/account/publishing/ with workflow nametest-release.yml(Environment blank), or set aTESTPYPI_API_TOKENsecret. -
GitHub App for the push to protected
main— required (mainhas branch protection). The release pushes the version commit +v<version>tag tomainas theZelAnton-release-botApp, which sits in the ruleset's bypass list; the defaultgithub-actions[bot]cannot be granted a ruleset bypass (system actor, not an App). The repo variableRELEASE_APP_ID(3951739, the shared App) is already set — add the secretRELEASE_APP_PRIVATE_KEY= the App's.pemprivate key (the same App/key as the sibling repos). See release-token-bypass.md. Until the secret is set the App step is skipped and the push falls back toGITHUB_TOKEN, which the protection rejects — so set it before the first release.
scripts/release/toolchain.env is the single source of truth for the exact
cibuildwheel, maturin, twine, and Rust versions used to build and check
release artifacts. Both release workflows load it, and the reusable matrix passes
the same Rust version to host builds and Linux containers. The wheel build installs
the snapshot's maturin into each fresh build environment before running PEP 517
without a second dependency resolution. rust-toolchain.toml mirrors the Rust pin
for local development but does not independently select the release toolchain.
Update the snapshot deliberately: verify the selected releases in PyPI and the
official Rust stable channel, edit scripts/release/toolchain.env, mirror its Rust
version in rust-toolchain.toml, and run:
uv run python scripts/release/toolchain.py verify
uv run pytest tests/test_release_scripts.pyThe verifier fails on floating ranges, stable, missing consumers, or drift between
the snapshot and rust-toolchain.toml. Finish by dispatching the TestPyPI dry-run;
only its full matrix proves the new snapshot on every release target.
- Dry-run first (recommended): Actions → Test release (TestPyPI) → Run
workflow. This builds the full wheel matrix (manylinux + musllinux + macOS +
Windows) and uploads to TestPyPI — the only thing that exercises the real
cibuildwheelbuild + OIDC upload path. Fix any failure before the real run. - Release: Actions → Release → Run workflow (from
main) → pick the bump (patch/minor/major; ignored on the first release, which seeds the version frompyproject.toml). The version is never typed by hand — the latestv*tag drives the next number.
The pipeline then: computes the version + release notes → builds wheels + sdist →
strict twine check → publishes to PyPI (the single irreversible pivot) →
atomically pushes the version commit + v<version> tag to main → creates the
GitHub Release (wheels + sdist + SHA256SUMS).
- After the first release: uncomment the PyPI badge in
README.md, and refresh the README prose that still says the release is pending (the build-from-source intro and the "first release to PyPI is pending" note). Done for v1.0.0 — not needed again for subsequent releases.
The guides in docs/ render as an mdBook
site at https://zelanton.github.io/processkit-py/. On a push to main,
docs.yml builds the book, validates its rendered local links and anchors, and
publishes it to GitHub Pages. Pull requests run the same build and checks but
never deploy.
Preview the docs locally with mdbook serve --open, or run just docs for the
same build-and-link-check sequence CI uses.
The ordering is built so failures are safe to recover:
- Before or at the PyPI publish — nothing was pushed to the remote (and no tag
exists yet, so a re-run recomputes the same version). Just re-run the workflow;
skip-existingmakes any file a partial upload already landed a no-op. - After the tag is pushed to
main— the package is on PyPI and the tag is onmain. Do not re-run the whole workflow (a re-run computes the next version and orphans this release). Finish by hand: the failing step prints the exactgh releasecommand to run. - The atomic tag push can lose a race with an ordinary push to
mainthat lands mid-run (it leaves package-on-PyPI / tag-not-pushed). Cut releases whenmainis quiet; if it happens, push the tag + version commit by hand, then create the Release as above.