♻️ Tighten the development/ prose
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.
This commit is contained in:
1 parent
7ea84b66fa
commit
74c39e1346
7 files changed
+341
-409
No files matched your search
+53
-58
@@ -1,31 +1,28 @@
|
||||
# Publishing
|
||||
|
||||
Publishing is CI-only by policy. Local `npm publish` is not supported. The
|
||||
maintainer triggers releases from `main`; the mechanics live in
|
||||
Publishing is CI-only: local `npm publish` is not supported, and the maintainer
|
||||
triggers releases from `main`. The mechanics are in
|
||||
[scripts/release.sh](../scripts/release.sh) and
|
||||
[scripts/release-notes.sh](../scripts/release-notes.sh), and the job graph lives
|
||||
in [.gitea/workflows/ci.yml](../.gitea/workflows/ci.yml).
|
||||
[scripts/release-notes.sh](../scripts/release-notes.sh); the job graph is
|
||||
[.gitea/workflows/ci.yml](../.gitea/workflows/ci.yml).
|
||||
|
||||
## Release steps
|
||||
|
||||
1. All intended changes are merged to `main` and passing CI.
|
||||
2. The maintainer runs `npm run create:release`. VS Code opens
|
||||
[CHANGELOG.md](../CHANGELOG.md) to finalize the `[Unreleased]` notes; because
|
||||
pubv refuses a dirty tree, any edit is committed first (then folded into the
|
||||
release commit), and pubv's interactive prompt suggests a version from those
|
||||
notes — the maintainer confirms or edits it.
|
||||
3. `scripts/release.sh` creates a single release commit (graduated changelog +
|
||||
`package.json` bump, amended into one commit), tags it, and pushes everything
|
||||
to Gitea.
|
||||
4. CI fires on both pushes: the `publish` job runs on the tag (`build` +
|
||||
publish-tier checks + release page + `npm publish`), while the branch run's
|
||||
`release-gate` job recognizes the release commit and skips
|
||||
`build`/`maintain` — the tag verifies the identical SHA, so no work is
|
||||
duplicated. The publish-tier checks must pass before the artifact is
|
||||
published. The `publish` job also creates the Gitea release page from the
|
||||
matching Keep-a-Changelog section; it runs _before_ `npm publish` so a broken
|
||||
page fails CI without consuming a version, and `npm publish` stays the last
|
||||
step.
|
||||
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.
|
||||
3. `scripts/release.sh` creates one release commit (graduated changelog +
|
||||
`package.json` bump, amended together), tags it, and pushes.
|
||||
4. CI fires on both pushes. `publish` runs on the tag (build + publish checks +
|
||||
release page + `npm publish`), while `release-gate` recognizes the release
|
||||
commit and skips `build`/`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
|
||||
_before_ `npm publish`, so a broken page fails CI without consuming a version
|
||||
and `npm publish` stays the last step.
|
||||
|
||||
## CI-only publishing
|
||||
|
||||
@@ -39,8 +36,8 @@ Releases are cut by CI from `main`; there is no local `npm publish` and no
|
||||
- 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:attw` validate the _publishable artifact_, which
|
||||
needs a fresh build; they are not source-correctness checks, so they do not
|
||||
belong in `check`.
|
||||
needs a fresh build; they are not source-correctness checks and do not belong
|
||||
in `check`.
|
||||
|
||||
## The release commit is assembled from two tools
|
||||
|
||||
@@ -53,32 +50,31 @@ release commit.
|
||||
#### Why
|
||||
|
||||
- We want hand-written Keep-a-Changelog notes, an `[Unreleased]` ->
|
||||
`## [x.y.z] - DATE` graduation, and a tag that marks the exact commit on
|
||||
`main` that gets published.
|
||||
- No single tool did both the `[Unreleased]` graduation _and_ the
|
||||
`package.json` bump. Splitting by strength: `pubv` (tiny, changelog-driven)
|
||||
owns preflight, the interactive major/minor/patch heuristic, and graduating and
|
||||
committing `CHANGELOG.md` (no tag, no push); `npm version` syncs
|
||||
`package.json` + the lockfile; `--amend` folds them into pubv's single commit;
|
||||
the tag is created _after_ the amend so it is never orphaned.
|
||||
- The notes are finalized in VS Code _before_ pubv: the `[Unreleased]` body is
|
||||
what pubv's bump heuristic reads, so editing afterwards would inform the
|
||||
changelog only, not the version choice. The staging commit that makes pubv
|
||||
accept the tree is folded back into the single release commit.
|
||||
`## [x.y.z] - DATE` graduation, and a tag on the exact commit that gets
|
||||
published — and no single tool did both the graduation and the `package.json`
|
||||
bump.
|
||||
- Split by strength: `pubv` (tiny, changelog-driven) owns preflight, the
|
||||
interactive major/minor/patch heuristic, and graduating and committing
|
||||
`CHANGELOG.md` (no tag, no push); `npm version` syncs `package.json` + the
|
||||
lockfile; `--amend` folds 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
|
||||
we want hand-written notes (see
|
||||
the notes are hand-written (see
|
||||
[workflow.md § Commit messages](./workflow.md#commit-messages)).
|
||||
- `changesets` / `rtk`: config plus a heavier version/publish flow that fights
|
||||
the CI-only publish.
|
||||
- `knope` / `kacl` / `bestikk`: changelog-only — they don't bump `package.json`
|
||||
— and they carry 5-year / 2-year / brand-new maintenance.
|
||||
- `pubv` alone: verified it never writes `package.json`.
|
||||
- `versions` (silverwind): great Gitea support, but pairing it with a hand-rolled
|
||||
promote became a ~180-line script to maintain, which is exactly what this
|
||||
~30-line version replaces.
|
||||
- `changesets` / `rtk`: config plus a heavier flow that fights the CI-only
|
||||
publish.
|
||||
- `knope` / `kacl` / `bestikk`: changelog-only (no `package.json` bump) and
|
||||
5-year / 2-year / brand-new maintenance.
|
||||
- `pubv` alone: it never writes `package.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
|
||||
|
||||
@@ -86,38 +82,37 @@ release commit.
|
||||
|
||||
The version is derived from the graduated `## [x.y.z]` heading in
|
||||
[CHANGELOG.md](../CHANGELOG.md) and written to `package.json` +
|
||||
`package-lock.json` by `npm version`. The README carries no version line and
|
||||
does not link `package.json`.
|
||||
`package-lock.json` by `npm version`. The README has no version line and does
|
||||
not link `package.json`.
|
||||
|
||||
#### Why
|
||||
|
||||
- `release.sh` reads the version from the changelog, so the changelog is the
|
||||
input and `package.json` is the derived copy — one direction, no drift.
|
||||
input and `package.json` the 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 without removing the copy.
|
||||
the lookup, not remove the copy.
|
||||
|
||||
#### Rejected
|
||||
|
||||
- A `## Version` line in the README: it would make `release.sh` responsible for
|
||||
a third file.
|
||||
- A `## Version` line in the README: it makes `release.sh` responsible for a
|
||||
third file.
|
||||
- Linking `package.json` from the README: it invites a hand-maintained duplicate
|
||||
that the link does not keep in sync.
|
||||
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 the section is missing.
|
||||
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 with an empty
|
||||
body. A leading `v` is tolerated so both `v1.2.3` and `1.2.3` match the
|
||||
`## [1.2.3]` heading.
|
||||
- Failing on a missing section means a release can never publish an empty body.
|
||||
A leading `v` is tolerated so both `v1.2.3` and `1.2.3` match `## [1.2.3]`.
|
||||
|
||||
## Token gates
|
||||
|
||||
@@ -129,11 +124,11 @@ The Gitea release page uses the run's automatic token (`github.token`).
|
||||
|
||||
#### Why
|
||||
|
||||
- The automatic token only needs `contents: write`, so no secret gate is needed
|
||||
for the release page.
|
||||
- `secrets` is not an allowed context in a step `if`, so `NPM_TOKEN` must be
|
||||
lifted into job-level `env`; an unset secret then skips the publish instead of
|
||||
attempting an unauthenticated one.
|
||||
- The automatic token needs only `contents: write`, so the release page needs no
|
||||
secret gate.
|
||||
- `secrets` is not allowed in a step `if`, so `NPM_TOKEN` must be lifted into
|
||||
job-level `env`; 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.
|
||||
|
||||
|
||||
Reference in new issue
Block a user