Skip to content

chore(release): 0.6.0 stable, trusted publishing, and a package fit to hand to someone (#47, #49) - #48

Merged
fsecada01 merged 3 commits into
masterfrom
chore/component-framework-47-release-0-6-0
Jul 31, 2026
Merged

chore(release): 0.6.0 stable, trusted publishing, and a package fit to hand to someone (#47, #49)#48
fsecada01 merged 3 commits into
masterfrom
chore/component-framework-47-release-0-6-0

Conversation

@fsecada01

@fsecada01 fsecada01 commented Jul 31, 2026

Copy link
Copy Markdown
Owner

Closes #47. Closes #49.

Promotes 0.6.0b0 to a final release, adds the workflow that publishes it, and fixes the things that made the package installable but not yet fit to hand to someone.

Why stable, and why this repo first

cf-ui's wheel declares Requires-Dist: component-framework>=0.4 — the [tool.uv.sources] git pin does not travel into wheel metadata — so pip install cf-ui cannot resolve until this package is on PyPI. Ordering is forced.

Publishing the beta would have worked only by accident. A specifier with no pre-release marker resolves to a pre-release only when no final release exists, so >=0.40.6.0b0 today, but that resolution would flip silently the first time any stable version appeared — and a stable cf-ui 0.2.0 would have shipped depending on a beta.

Publishing (#47)

Trusted publishing (OIDC), not a stored API token. The publish job requests id-token: write, mints a short-lived token, and PyPI exchanges it for upload rights. Gated behind a pypi GitHub environment, so you can add a required-reviewer rule if you want a human ack on every release.

Three guards run before anything is uploaded, because a bad upload can be yanked but never replaced:

  • twine check on both the wheel and the sdist
  • the git tag must match pyproject.toml's version, so a v0.6.0 tag cannot publish a wheel built from a different number
  • the wheel must contain the client assets and py.typed. Those ship inside the package with no explicit hatchling include, so a packaging regression produces a wheel that installs and imports cleanly and then serves no interactivity — nothing in the test suite would notice, because every test runs against the source tree. Verified it can fail: a wheel rebuilt without py.typed and component-client.js exits 1 naming both.

What the audit found (#49)

I built the wheel, installed it into a clean venv, and checked every documented import against the installed package rather than the source tree. Six samples did not work, and two pieces of metadata were missing.

The README described an install nobody could perform

Every instruction was pip install -e ".[extra]" — an editable install from a checkout — and the section opened with "Not on PyPI yet", which stops being true the moment this uploads. The README is the PyPI landing page, so the one line a visitor arriving there needs was the one that was missing.

It now leads with pip install "component-framework[fastapi]", notes the quoting (bare brackets are glob syntax in zsh), shows the missing-extra ImportError a reader will actually meet, and demotes the editable install to a contributor note.

Six samples that do not run

Where Documented Reality
README (composition) SlotComponent, CompositeComponent from core.composition Neither exists. The whole example was invented, down to a components = {...} attribute nothing reads. Real API: a Component with a slots ClassVar, assembled with compose()
README (testing) mount_component, dispatch_event, assert_state(component, …) None exist; it also omitted the required component_class
docs/LOCKED_FIELDS.md from component_framework import Component, registry Top-level exports only CorruptStateError, StateSigner
docs/CBV_GUIDE.md RateLimitMixin from adapters.django_views Lives in adapters.django_ratelimit — as the README said all along
docs/examples/ecommerce.md ×2 Do not parse: a bare ... in a list literal, and a method whose body was only a comment

Packaging

  • py.typed. The codebase is type-checked in CI and ships component-client.d.ts so TypeScript consumers get types for the client JS — but with no PEP 561 marker, every Python consumer running mypy or pyright saw the whole package as untyped. The types existed; they just were not advertised.
  • A testing extra declaring pytest. component_framework.testing imports pytest at module scope — it ships fixtures and a pytest-style base class — but pytest was only reachable through dev-base, so following the README's testing sample after pip install component-framework[fastapi] raised ModuleNotFoundError.
  • Classifiers: 7 → 15. Adds the license (which is what PyPI's sidebar reads), the frameworks the adapters target, and Typing :: Typed.

Why all six shipped

Nothing here read the documentation. tests/test_docs_samples.py now parses every fenced python block in the README, CONTRIBUTING and docs/, and resolves every from component_framework… import … against the real package.

Run against the previous text it produces 8 failures — all six samples, plus the "Not on PyPI" claim. Three of those eight were ones my own manual audit had missed (RateLimitMixin and both ecommerce parse errors), which is the argument for the guard rather than a careful read.

Ported from cf-ui's test_docs_samples.py, which exists for exactly the same reason: ComponentCatalog and <CfCard> sat in that README for two releases.

Verified

  • 659 tests pass; ruff clean; CI green on all four Python versions
  • A clean install now resolves 34 documented imports where it resolved 15
  • Wheel: License-File: LICENSE, 15 classifiers, py.typed present, twine check PASSED on wheel and sdist
  • Name component-framework is free on PyPI

Manual step before this can publish

Trusted publishing needs a pending publisher registered before the first upload — the workflow will fail without it. On pypi.org → Your projects → Publishing → add a pending publisher:

Field Value
PyPI Project Name component-framework
Owner fsecada01
Repository name component-framework
Workflow name release.yml
Environment name pypi

Then merge this and push a tag: git tag v0.6.0 && git push origin v0.6.0.

Not in this PR

  • The pre-existing dirty working tree (justfile, specs/, .claude/) is untouched — none of it is staged here.
  • The README's 22 relative links resolve on GitHub and 404 on PyPI. Real, but the Documentation project URL points at the live docs site, so a PyPI visitor is one click from working links.
  • Heads up, unrelated: the ty prek hook exits 1 locally on master as well as on this branch, while CI's lint job passes. CI installs the ty==0.0.25 pin from .[dev]; a locally-installed ty on PATH (0.0.64 here) shadows it and fails on the intentionally warn-level diagnostics. Worth a separate ticket — it means local prek is not the CI parity check the config comment claims it is.

🤖 Generated with Claude Code

https://claude.ai/code/session_01NhqNRBg83czKfr8L6FF5xf

Promotes 0.6.0b0 to a final release with no code changes, and adds the
workflow that publishes it.

cf-ui's wheel declares `component-framework>=0.4` — the `[tool.uv.sources]`
git pin does not travel into metadata — so `pip install cf-ui` cannot resolve
until this package is on PyPI. This repo goes first.

Publishing the beta would have worked only by accident: a specifier with no
pre-release marker resolves to a pre-release just when no final release
exists, so the resolution would have changed silently the first time any
stable version appeared, and a stable cf-ui would have depended on a beta.

Publishing uses trusted publishing (OIDC) rather than a stored API token. Two
guards before upload, because a bad upload can be yanked but never replaced:
`twine check` on both artifacts, and an assertion that the tag matches the
version in pyproject.toml.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NhqNRBg83czKfr8L6FF5xf
Built the wheel, installed it into a clean venv, and checked every documented
import against the installed package rather than the source tree. The package
was installable; it was not yet fit to hand to someone.

The README described an install nobody could perform. Every instruction was
`pip install -e ".[extra]"` — an editable install from a checkout — and the
section opened with "Not on PyPI yet", which stops being true the moment
0.6.0 uploads. The README *is* the PyPI landing page, so the one line a
visitor arriving there needs was the one that was missing. It now leads with
`pip install "component-framework[fastapi]"`, notes the quoting (bare
brackets are glob syntax in zsh), shows the missing-extra ImportError a
reader will actually meet, and demotes the editable install to a contributor
note.

Six samples did not work:

* README composition — imported `SlotComponent` and `CompositeComponent`
  from `core.composition`, which exports neither. The whole example was
  invented, down to a `components = {...}` attribute nothing reads. The real
  API is a `Component` with a `slots` ClassVar, assembled with `compose()`.
* README testing — `mount_component`, `dispatch_event`, and `assert_state`
  with a positional component argument; none exist. It also omitted the
  required `component_class`.
* `docs/LOCKED_FIELDS.md` — imported `Component`/`registry` from the
  top-level package, which exports only `CorruptStateError`/`StateSigner`.
* `docs/CBV_GUIDE.md` — imported `RateLimitMixin` from `adapters.django_views`;
  it lives in `adapters.django_ratelimit`, as the README said all along.
* Two `docs/examples/ecommerce.md` blocks did not parse — a bare `...` in a
  list literal, and a method whose body was only a comment.

Packaging:

* `py.typed`. The codebase is type-checked in CI and ships
  `component-client.d.ts` for the JS, but with no PEP 561 marker every Python
  consumer running mypy or pyright saw the package as untyped. The types
  existed; they were not advertised.
* A `testing` extra declaring pytest. `component_framework.testing` imports
  pytest at module scope, but pytest was only reachable through `dev-base`,
  so following the README's testing sample after
  `pip install component-framework[fastapi]` raised ModuleNotFoundError.
* Classifiers for the license (what PyPI's sidebar reads), the frameworks the
  adapters target, and `Typing :: Typed`. Seven became fifteen.
* The release workflow now refuses to hand off a wheel missing the client
  assets or `py.typed` — both ship inside the package with no explicit
  include, so a regression there yields a wheel that installs and imports
  cleanly and then serves no interactivity. Verified it can fail: a wheel
  rebuilt without them exits 1 naming both.

Why all six shipped: nothing here read the documentation.
`tests/test_docs_samples.py` now parses every fenced python block in the
README, CONTRIBUTING and docs/, and resolves every `from component_framework…
import …` against the real package. Run against the previous text it goes red
on all six, plus the "Not on PyPI" claim — 8 failures, of which 3 were ones
the manual audit had missed. Ported from cf-ui's guard of the same name,
which exists because `ComponentCatalog` and `<CfCard>` sat in that README for
two releases.

Verified: 659 tests pass, ruff clean, and a clean install now resolves 34
documented imports where it resolved 15.

Closes #49

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NhqNRBg83czKfr8L6FF5xf
@fsecada01 fsecada01 changed the title chore(release): 0.6.0 stable, and trusted publishing to PyPI (#47) chore(release): 0.6.0 stable, trusted publishing, and a package fit to hand to someone (#47, #49) Jul 31, 2026
Conflict was the Installation section, which this branch rewrote wholesale
and master only re-linked. Kept this branch's version and re-ran the
absolutiser over it — the new section had introduced fresh relative links of
its own (CHANGELOG.md, CONTRIBUTING.md).

Merging surfaced a hole in the guard. The README claimed twice that the
package was not on PyPI, in two different wordings, and
`test_the_readme_documents_installing_from_pypi` pinned the exact string
"Not on PyPI" — so it sailed straight past "Not yet published to PyPI" in
line 3 of the same file. The pattern is now a regex over both phrasings, and
a companion test pins it against the two real sentences it replaced.

Two link guards added while the rule is fresh:

* every README link must be absolute — relative ones are dead on the PyPI
  page, and the docs site cannot cover for them because it is a pdoc API
  reference that does not publish the markdown guides at all;
* every absolute link into this repo must point at a path `git ls-files`
  knows. An absolute link to a moved file 404s silently, which is worse than
  the relative link it replaced. Checked against git rather than the
  filesystem: a path that exists locally but is untracked still 404s on
  github.com.

All three go red on their exact defect (reintroduced one at a time) and
green when restored. 662 tests pass.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NhqNRBg83czKfr8L6FF5xf
@fsecada01
fsecada01 merged commit ab8901a into master Jul 31, 2026
7 checks passed
@fsecada01
fsecada01 deleted the chore/component-framework-47-release-0-6-0 branch July 31, 2026 13:50
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

1 participant