Releasing YAGA¶
The release workflow is .github/workflows/release.yml. It validates the selected source,
runs the full local gate on GitHub's Linux runner, and retains the exact wheel and source
distribution inspected by the package build gate. Publishing uses those artifacts without rebuilding.
One-time PyPI setup¶
Configure a PyPI trusted publisher with these values (use a pending publisher when creating the project):
| Field | Value |
|---|---|
| PyPI project | yaga-cli |
| GitHub owner | dariuszpanas |
| Repository | yaga |
| Workflow filename | release.yml |
| Environment | pypi |
Create the GitHub pypi environment with required maintainer approval and deployment rules
allowing release tags. Protect v* tags against unauthorized creation, updates, and deletion.
No PyPI API token or repository secret is needed. Only the upload job receives id-token: write;
it checks out no source and runs no build scripts. Keep that separation when editing the workflow.
Rehearse without publishing¶
Once the workflow is merged, select Actions → Release → Run workflow on the default branch.
The manual route runs validation and uploads the distributions artifact to the workflow run.
It never runs the PyPI upload or GitHub Release jobs. It also retains the selected changelog
section as a separate release-notes artifact. Manual runs on other branches are skipped.
Download the artifact to inspect the wheel and source distribution. A rehearsal is build evidence, not proof that PyPI's publisher/environment setup works: OIDC authorization happens on publication.
Inspect the description embedded in the wheel, rendered with readme_renderer.markdown, in a
browser. The build gate checks the Markdown content type, absolute HTTPS links, and preserved
public logo markup; it deliberately does not make network requests. Before publication, verify
image and link URLs anonymously, including documentation fragments and the package's project
URLs. The logo must load from the public documentation site. Repository and example links need
the source repository to be public; authenticated maintainer access does not prove they work for
package users. Twine's metadata check does not prove remote images or links are reachable.
Publish a version¶
- Update
pyproject.toml,VERSIONinsrc/yaga/version_requirement.py, and user-facing version references. Tests require the Action runtime version to match package metadata. Convert the candidate changelog heading into## [0.1.0] - YYYY-MM-DD, using the actual release date, and retainUnreleasedabove it. The Beta classifier is a maturity label; version0.1.0is a normal PyPI version, not the PEP 440 prerelease0.1.0b1. UseYAGA 0.1.0as the GitHub Release title; keep the maturity label in package metadata rather than appending(Beta)to the title. - Merge the release changes and require all hosted CI lanes to pass for the exact commit, including Windows. Resolve dependency PR failures before choosing the candidate.
- After publication is explicitly authorized, create and push the matching
v0.1.0tag at that verified commit. Use the corresponding version for later releases. - The workflow requires the tagged commit to be on the default branch, checks that the tag matches
package metadata and a dated changelog entry, and reruns
uv run make ci. Only then does it offer thepypienvironment approval and upload the validated distributions with attestations. - Verify the PyPI project files and install
yaga-cli==0.1.0from PyPI in a clean tool environment. Confirmyaga --helpand the automatically created GitHub Release. Its wheel and source archive must be the same artifacts published to PyPI.
This workflow accepts final vMAJOR.MINOR.PATCH tags and does not publish TestPyPI packages.
After PyPI publication succeeds, a separate job creates the GitHub Release from the validated
changelog section and original distributions. Only that job has contents: write; it checks out
no source and has no OIDC permission. The PyPI job retains its isolated id-token: write permission.
Read the Docs configuration is independent of package upload.
If only the GitHub Release job fails, repair or rerun that job without republishing to PyPI. If a release entry already exists after a partial failure, inspect its draft state and assets and complete it using the retained artifacts; the workflow deliberately refuses to overwrite an entry.
If validation fails, fix the source and choose a new verified candidate before publishing. If an upload is interrupted, inspect PyPI before rerunning: package versions/files cannot be overwritten, and the workflow deliberately does not suppress existing-file errors.