ci(release): toggle-gated PyPI publishing for all release tags (+ docs)#319
ci(release): toggle-gated PyPI publishing for all release tags (+ docs)#319gbrlcustodio wants to merge 6 commits into
Conversation
RELEASE.md claimed only pipefy-cli and pipefy-mcp-server publish to PyPI (with pipefy-sdk held back), but release.yml stages and uploads all five wheels. The dependency closure forces this: cli and mcp depend on sdk and auth, which depend on infra, so a public pip install only resolves when every package is on PyPI. Reconcile the docs to match the workflow and note the single-batch upload and pending-publisher requirements.
9da6d65 to
746b5f3
Compare
… tags Split PyPI publishing into a separate publish-pypi job that runs after the GitHub Release, scoped to a dedicated pypi environment with id-token: write. The release job keeps contents: write and uploads the built dist/ as an artifact the publish job consumes. Replace the v1.-only step gate with a job-level toggle, vars.PYPI_PUBLISH_ENABLED == 'true'. The version-based gate was only ever a stand-in for the PyPI account not being provisioned yet; the intended end state publishes every release tag, betas included. Off by default so nothing attempts PyPI until the account and the five pending publishers exist, then the sec team flips the variable with no code change. Reframe RELEASE.md around the toggle instead of the v0.x/v1 split.
…upload The publish-pypi staging step re-globbed and re-asserted exactly one wheel per package, but the release job already verifies that before uploading the immutable artifact. Collapse the step to a wheels-only copy (dist-pypi still exists to exclude sdists from the PyPI upload). Gate the dist artifact upload on PYPI_PUBLISH_ENABLED so non-publishing releases don't pack and store an artifact nobody downloads.
- skip-existing on the PyPI upload so a re-pushed tag at an already-published version is idempotent instead of failing on PyPI's 400. - retention-days: 1 on the dist artifact (a pure intra-run handoff). - concurrency group so two tags pushed together don't race the artifact/version. - Verify expected wheels now also asserts the total wheel count, so a future workspace member can't be silently published by the cp dist/*.whl staging. - setup-python in publish-pypi (the smoke step no longer relies on the runner shim) and a nullglob guard with a clear message on the staging copy.
Published wheels declared bare, unpinned inter-package deps (pipefy-cli -> pipefy-sdk/pipefy-auth -> pipefy-infra), so once on PyPI a user could resolve pipefy-cli==X against a newer pipefy-sdk and get a mismatched, broken install. Pin each sibling to ==<lockstep version> so the closure installs in lockstep. bump_version.py now rewrites these pins on every bump and verify() asserts them, keeping the pins in lockstep with the shared version. The [tool.uv.sources] workspace mapping still overrides them for in-repo dev.
Publishing is now controlled by the PYPI_PUBLISH_ENABLED toggle, not the v1.0 tag, so docs that tied PyPI to v1.0 were stale. Fix the RELEASE.md post-publish smoke command to allow pre-releases for beta tags, correct the sdk README's 'gated separately' claim to the single-batch model, and reword README/MIGRATION/ DEPRECATION/cli/mcp install notes to key on PyPI presence rather than v1.0.
|
Closing this in favor of the changes that already landed plus two focused follow-ups. The core goal here (publish all five wheels to PyPI on every release tag) shipped in #365 via a simpler path: drop the Given that, a rebase of this branch is not worth it: the diff is stale against the #361 SDK rename ( The genuinely valuable pieces are carried forward, rewritten for the current name and version:
Not carried forward (can be a separate hardening PR if wanted): splitting |
What
Make PyPI publishing a separate, toggle-gated CI job that covers every release tag (betas included), and reconcile
RELEASE.mdwith the workflow.Why
RELEASE.mdclaimed onlypipefy-cliandpipefy-mcp-serverpublish to PyPI, gated tov1.tags. Two problems:release.ymlstages and uploads all five wheels, and the dependency closure forces that:pipefy-cli/pipefy-mcp-serverdepend onpipefy-sdk/pipefy-auth, which depend onpipefy-infra. Published wheels declare these as bare, unpinned deps (the[tool.uv.sources]workspace mapping applies only in dev), so a publicpip installonly resolves if every package in the closure is on PyPI.v1.-only gate was a stand-in for the PyPI account not being provisioned yet, not a deliberate policy. The intended end state publishes all release tags, including thev0.2.0-beta.*line.Changes
.github/workflows/release.ymlpublish-pypijob (needs: release) under a dedicatedpypiGitHub Actions environment, withid-token: write. Thereleasejob keepscontents: writeand now uploadsdist/as an artifact the publish job consumes. Least-privilege per job; the top-levelpermissionsblock is removed.startsWith('v1.') && !contains('-')step gate with a job-level toggle:if: vars.PYPI_PUBLISH_ENABLED == 'true'. Off by default (job skips), so betas and finals don't attempt PyPI until the account and the five pending publishers exist. The sec team flips the variable to turn it on, no code change.RELEASE.mdPYPI_PUBLISH_ENABLEDistrue, for all release tags.pipefy, repoai-toolkit, workflowrelease.yml, environmentpypi), the toggle, and the optional environment protection rules.Sec-team / maintainer prerequisites (before flipping the toggle)
pipefy-sdk,pipefy-mcp-server,pipefy-cli,pipefy-auth,pipefy-infra), all pointing at ownerpipefy, repoai-toolkit, workflowrelease.yml, environmentpypi. All five names are currently available on PyPI.pypiGitHub Actions environment (add protection rules if you want an approval gate).PYPI_PUBLISH_ENABLED=true. One missing publisher fails the whole release, so do this only once all five exist.