From d2f3e1ca279afa84dd899ac81771ace748a93993 Mon Sep 17 00:00:00 2001 From: Julien Herr Date: Mon, 25 May 2026 16:08:24 +0200 Subject: [PATCH] ci(release): derive release version from the tag, not the commit MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The release job built whatever version package.json held at the tagged commit — but main always carries a -develop suffix, so a vX.Y.Z bundle would have reported X.Y.Z-develop. Make the tag the source of truth: strip the suffix in the ephemeral CI checkout before building (never committed), and fail fast when the tag base doesn't match package.json's base (wrong-commit guard). Update CONTRIBUTING with the tag-driven flow. Co-Authored-By: Claude Opus 4.7 --- .github/workflows/release.yml | 18 ++++++++++++++++++ CONTRIBUTING.md | 29 +++++++++++++++++++---------- 2 files changed, 37 insertions(+), 10 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index a5e621d..8f65c41 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -21,6 +21,24 @@ jobs: - run: npm ci + # The tag is the source of truth for a release version. main always carries + # a `-develop` pre-release suffix, so strip it here (in the ephemeral CI + # checkout only — never committed) so the built bundle reports the bare + # X.Y.Z. Guard against tagging the wrong commit: the tag's base must match + # package.json's base version. + - name: Align package.json version to the tag + env: + TAG_NAME: ${{ github.ref_name }} + run: | + VERSION="${TAG_NAME#v}" + PKG_BASE="$(node -p 'require("./package.json").version.split("-")[0]')" + if [ "$VERSION" != "$PKG_BASE" ]; then + echo "Tag $TAG_NAME (base $VERSION) does not match package.json base ($PKG_BASE)." >&2 + echo "Tag the commit whose package.json is ${VERSION}-develop." >&2 + exit 1 + fi + npm version "$VERSION" --no-git-tag-version --allow-same-version + - run: npm run build - name: Locate bundled output diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 52c4f51..e84a53b 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -75,21 +75,30 @@ Common types: `feat`, `fix`, `refactor`, `docs`, `test`, `chore`. ## Releasing The running version is read from `package.json` `version` and inlined at build -time (footer, `/health`, `/api/v1/stats`). Between releases the working tree -carries a `-develop` pre-release suffix so a dev build is never mistaken for a -shipped one — `0.3.0-develop` sorts _below_ `0.3.0` per SemVer, meaning "heading -toward 0.3.0, not yet released". +time (footer, `/health`, `/api/v1/stats`). `main` **always** carries a +`-develop` pre-release suffix (e.g. `0.3.0-develop`) so a dev build is never +mistaken for a shipped one — `0.3.0-develop` sorts _below_ `0.3.0` per SemVer, +meaning "heading toward 0.3.0, not yet released". -To cut a release `X.Y.Z`: +**The git tag is the source of truth for a release version**, not a commit on +`main`. The Release workflow (`.github/workflows/release.yml`) triggers on a +`v*` tag, strips the `-develop` suffix in its ephemeral checkout so the published +bundle reports the bare `X.Y.Z`, then builds and creates the GitHub Release. It +fails fast if the tag's base doesn't match `package.json`'s base version, which +catches tagging the wrong commit. You never commit a bare `X.Y.Z` to `main`. + +To cut release `X.Y.Z` (its base must equal `main`'s current `X.Y.Z-develop`): ```bash -npm version X.Y.Z --no-git-tag-version # drop the -develop suffix -# commit, tag vX.Y.Z, push, deploy (npm run deploy) -npm version X.Y+1.0-develop --no-git-tag-version # reopen the next cycle +git tag vX.Y.Z && git push origin vX.Y.Z # the workflow aligns + builds + publishes ``` -So `main` should always read `*-develop`; only a tagged release commit carries a -bare `X.Y.Z`. +Then reopen the next cycle on `main`: + +```bash +npm version -develop --no-git-tag-version # e.g. 0.4.0-develop (or 0.3.1-develop for a patch line) +# commit + push +``` ## Reporting bugs and requesting features