Skip to content

ci(release): publish to PyPI via trusted publishing (#45) - #46

Merged
fsecada01 merged 1 commit into
masterfrom
feat/component-framework-ui-45-release-workflow
Jul 31, 2026
Merged

ci(release): publish to PyPI via trusted publishing (#45)#46
fsecada01 merged 1 commit into
masterfrom
feat/component-framework-ui-45-release-workflow

Conversation

@fsecada01

Copy link
Copy Markdown
Owner

Closes #45.

A v* tag now builds, validates, and publishes cf-ui to PyPI. Matches the workflow component-framework got in fsecada01/component-framework#48, so both projects release the same way.

Trusted publishing, not a token

The publish job mints a short-lived OIDC token that PyPI exchanges for an upload token scoped to this project alone. No API token is created, stored, or rotated — there is no repo secret that could leak, and nothing to revoke if the repo is ever compromised. The upload is a separate job gated on a pypi GitHub environment, so protection rules can require a review before anything reaches PyPI.

Three gates before anything is uploaded

All three exist for the same reason: a bad upload can only be yanked, never replaced. PyPI does not let you re-upload a filename.

Gate Catches
twine check dist/* A malformed README renders as a broken PyPI page that cannot be fixed in place
Tag ≡ pyproject.toml version A wheel published under a name the tag does not describe
Wheel contains the templates The quiet one — see below

Why the template check earns its place

cf-ui is templates. They live inside the package specifically so hatchling includes them with no explicit [tool.hatch.build] config — which is exactly what makes a regression here silent. A packaging change that dropped templates/ produces a wheel that installs cleanly, imports cleanly, and renders nothing. No test in this repo would see it, because every test runs against the source tree, not the built artifact.

The check counts against the same 14-per-set invariant the unit suite already asserts, then requires the individual files a count would miss:

  • templates/cf_ui/assets.jinja — a sibling of templates/jinja/, so any theme-shaped glob skips it. That is precisely the mistake Litestar cannot render components — the installer never builds a JinjaX catalog #42 was: the Litestar installer globbed the theme folder and the asset macros vanished.
  • cf_ui_alpine.js, cf_ui_axes.css, cf_ui_axes.json, cf_ui_tailwind_plugin.mjs — the last three are build products of axes.py, and the .mjs is vendored.

Verification

Run against a real wheel built from this branch:

cf_ui-0.2.0-py3-none-any.whl: 171 entries
ok: 70 jinja, 14 wrappers, 70 partials, assets present

And proven able to fail — a wheel rebuilt with every cf_ui/templates/ entry stripped:

dropped 155 template entries
wheel is missing shipped content:
  only 0 JinjaX component templates (expected >= 14)
  only 0 cotton wrappers (expected >= 14)
  only 0 cotton theme partials (expected >= 14)
  missing cf_ui/templates/cf_ui/assets.jinja
exit=1

The step's heredoc framing is also checked: the YAML block scalar dedents to a PY terminator at column 0, so the script reaches bash intact.

Not in this PR

Cutting the 0.2.0 tag. Two things gate it:

  1. component-framework has to publish first. cf-ui's wheel metadata carries Requires-Dist: component-framework>=0.4 with no git source — [tool.uv.sources] pins do not travel into wheel metadata — so pip cannot resolve cf-ui until that name exists on PyPI.
  2. A pending publisher must be registered on PyPI, since trusted publishing cannot bootstrap itself for a project that does not exist yet. Owner fsecada01, repo component-framework-ui, workflow release.yml, environment pypi.

🤖 Generated with Claude Code

https://claude.ai/code/session_01NhqNRBg83czKfr8L6FF5xf

A `v*` tag now builds, validates, and publishes. Trusted publishing means no
API token is created, stored, or rotated: the job mints a short-lived OIDC
token that PyPI exchanges for an upload token scoped to this project alone.
There is no repo secret that could leak. The upload is a separate job gated
on a `pypi` environment, so protection rules can require a review before
anything reaches PyPI.

Three assertions run before the artifact is handed to the publish job, all
of them because a bad upload can only be yanked, never replaced:

* `twine check` on both distributions — a malformed README renders as a
  broken PyPI page that cannot be fixed in place.
* The tag must equal `pyproject.toml`'s version, or the wheel would publish
  under a name the tag does not describe.
* The wheel must actually contain the templates. This is the one worth
  spelling out: cf-ui *is* templates, and they ship inside the package so
  hatchling includes them with no explicit config — which is exactly what
  makes a regression here quiet. A packaging change that dropped them would
  produce a wheel that installs cleanly, imports cleanly, and renders
  nothing. The check counts JinjaX templates, cotton wrappers, and cotton
  theme partials against the 14-per-set invariant the unit suite already
  asserts, then requires `assets.jinja` (a sibling of `templates/jinja/`, so
  a theme-shaped glob would miss it — see #42) and the four static assets.

Verified against a real wheel: 70 jinja, 14 wrappers, 70 partials, assets
present. Verified it can fail: a wheel rebuilt with `cf_ui/templates/`
stripped exits 1 naming all four missing groups.

Closes #45

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NhqNRBg83czKfr8L6FF5xf
@fsecada01 fsecada01 added the enhancement New feature or request label Jul 31, 2026
@fsecada01
fsecada01 merged commit 0eb6288 into master Jul 31, 2026
7 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Add a release workflow: publish to PyPI via trusted publishing

1 participant