Same decisions, rationale, rejected alternatives and known issues, said with less padding: ~5,530 -> ~4,730 words (-15%). Every fact from the first draft is kept; only the wording, duplicated lead-ins and restated context are cut.
5.6 KiB
Publishing
Publishing is CI-only: local npm publish is not supported, and the maintainer
triggers releases from main. The mechanics are in
scripts/release.sh and
scripts/release-notes.sh; the job graph is
.gitea/workflows/ci.yml.
Release steps
- All intended changes are merged to
mainand passing CI. - The maintainer runs
npm run create:release. VS Code opens CHANGELOG.md to finalize the[Unreleased]notes; because pubv refuses a dirty tree, the edit is committed first (then folded into the release commit), and pubv suggests a version from those notes to confirm or edit. scripts/release.shcreates one release commit (graduated changelog +package.jsonbump, amended together), tags it, and pushes.- CI fires on both pushes.
publishruns on the tag (build + publish checks + release page +npm publish), whilerelease-gaterecognizes the release commit and skipsbuild/maintain: the tag verifies the identical SHA, so no work is duplicated. The publish checks pass before the artifact is published, and the release page is created from the matching Keep-a-Changelog section beforenpm publish, so a broken page fails CI without consuming a version andnpm publishstays the last step.
CI-only publishing
Decision (2026-09)
Releases are cut by CI from main; there is no local npm publish and no
publish:* script in the check chain.
Why
- The tag is the artifact marker: CI verifies the exact commit it points at, so a local publish could ship something the tag does not describe.
publish:publint/publish:attwvalidate the publishable artifact, which needs a fresh build; they are not source-correctness checks and do not belong incheck.
The release commit is assembled from two tools
Decision (2026-09)
create:release uses pubv for the changelog graduation and bump heuristic,
then npm version for the package.json + lockfile bump, amended into a single
release commit.
Why
- We want hand-written Keep-a-Changelog notes, an
[Unreleased]->## [x.y.z] - DATEgraduation, and a tag on the exact commit that gets published — and no single tool did both the graduation and thepackage.jsonbump. - Split by strength:
pubv(tiny, changelog-driven) owns preflight, the interactive major/minor/patch heuristic, and graduating and committingCHANGELOG.md(no tag, no push);npm versionsyncspackage.json+ the lockfile;--amendfolds them into one commit; the tag is created after the amend so it is never orphaned. - The notes are finalized before pubv because its bump heuristic reads the
[Unreleased]body — editing afterwards would inform the changelog only, not the version. The staging commit that satisfies pubv's clean-tree check is folded back into the single release commit.
Rejected
- The conventional-commits family: the history is gitmoji, not Conventional, and the notes are hand-written (see workflow.md § Commit messages).
changesets/rtk: config plus a heavier flow that fights the CI-only publish.knope/kacl/bestikk: changelog-only (nopackage.jsonbump) and 5-year / 2-year / brand-new maintenance.pubvalone: it never writespackage.json.versions(silverwind): good Gitea support, but pairing it with a hand-rolled promote became a ~180-line script, which this ~30-line version replaces.
The version has one source of truth
Decision (2026-09)
The version is derived from the graduated ## [x.y.z] heading in
CHANGELOG.md and written to package.json +
package-lock.json by npm version. The README has no version line and does
not link package.json.
Why
release.shreads the version from the changelog, so the changelog is the input andpackage.jsonthe derived copy — one direction, no drift.- npm and Gitea render the version from package metadata, so a README copy would be a third place to update for no reader benefit, and a link would only move the lookup, not remove the copy.
Rejected
- A
## Versionline in the README: it makesrelease.shresponsible for a third file. - Linking
package.jsonfrom the README: it invites a hand-maintained duplicate the link does not keep in sync.
Release notes are extracted from the changelog
Decision (2026-09)
scripts/release-notes.sh <tag> prints the Keep-a-Changelog section for the tag
and exits non-zero when it is missing.
Why
- CI reuses the release body from the same file that drove the version, so the page and the changelog cannot disagree.
- Failing on a missing section means a release can never publish an empty body.
A leading
vis tolerated so bothv1.2.3and1.2.3match## [1.2.3].
Token gates
Decision (2026-09)
The Gitea release page uses the run's automatic token (github.token).
npm publish is gated on NPM_TOKEN, lifted into job-level env. A final
always() step fails the job unless both halves reported success.
Why
- The automatic token needs only
contents: write, so the release page needs no secret gate. secretsis not allowed in a stepif, soNPM_TOKENmust be lifted into job-levelenv; an unset secret then skips the publish instead of attempting an unauthenticated one.- A tag is all-or-nothing: without the
always()guard a skipped or failed npm half would leave the job silently green. The guard turns it red.
Set NPM_TOKEN (npm publish rights) under Settings -> Actions -> Secrets.