Playbook: cut a release
Habitus uses Changesets for intent and changelog, and a v* tag to trigger publishing.
Why versioning is done locally
The usual Changesets automation (changesets/action, which opens a "Version Packages" PR) is GitHub-API-specific and does not work against Gitea. So the version bump happens on your machine and CI only reacts to the tag. Do not try to wire the action up; it will not work.
During development
Every PR that changes anything a consumer can observe adds a changeset:
npx changesetPick the bump and write the note. Write it for someone deciding whether to upgrade, not as a summary of your diff:
- Good:
HabButton now renders a real <a> when given as="a", so links appear in browser history. - Bad:
Refactored button internals.
Bump levels:
| Level | When |
|---|---|
patch | Bug fix, no API change |
minor | New component, new optional prop, additive behaviour |
major | Renamed or removed prop/slot/event/type, changed default, changed markup others may select on, changed token meaning |
A PR that only touches tests, docs prose or CI needs no changeset. CI runs changeset status as a prompt, not a hard gate — but if it flags you and you believe no changeset is needed, say why in the PR.
Releasing
Start clean on an up-to-date
main, withnpm run verifypassing.Apply the changesets. This bumps
package.jsonand writesCHANGELOG.md:shnpx changeset versionRead the changelog. This is the one moment to catch a mis-levelled changeset. If something additive was marked
major, or a breaking change slipped in asminor, fix it now —git checkout .and correct the changeset files rather than editing the generated changelog.Commit and tag. The tag must match the new version exactly; CI verifies this and fails the release if it does not.
shgit add . git commit -m "chore: release v$(node -p "require('./package.json').version")" git tag "v$(node -p "require('./package.json').version")" git push --follow-tagsWatch the release workflow.
.gitea/workflows/release.ymlre-verifies the tag against the manifest, runs lint, typecheck, tests and the build, then publishes to the Gitea registry and creates a release.
Docs deployment
Separate from the npm release and not tied to it: every push to main runs .gitea/workflows/docs-deploy.yml, which builds the workbench into an nginx image, pushes :latest and :<commit-sha> to git.livereader.com/livereader/habitus-docs, and notifies Watchtower.
To roll the docs back, retag a known-good SHA as :latest and poke Watchtower:
docker pull git.livereader.com/livereader/habitus-docs:<sha>
docker tag git.livereader.com/livereader/habitus-docs:<sha> \
git.livereader.com/livereader/habitus-docs:latest
docker push git.livereader.com/livereader/habitus-docs:latest
curl -fsS -X POST -H "Authorization: Bearer $WATCHTOWER_HTTP_API_TOKEN" \
"$WATCHTOWER_URL/v1/update?image=git.livereader.com/livereader/habitus-docs"If publishing fails
403/401— theLR_PUBLISH_TOKENrepository secret is missing or lackspackage:write.EPUBLISHCONFLICT/ version already exists — that version was already published. Registries do not allow republishing; bump again.- Tag/manifest mismatch — the guard did its job. Delete the tag, fix the version, re-tag.
Prereleases
For something consumers should try but not depend on:
npx changeset pre enter next
npx changeset version # -> 1.2.0-next.0Tag and push as normal, then npx changeset pre exit when stabilising.