ci: publish only plain vX.Y.Z tags so suffixed dry-run tags stay drafts

This commit is contained in:
tiennm99 committed 2026-10-21 17:19:23 +07:00
1 parent e712978ff6
commit 81f29f2e4f
3 files changed
+19 -7

No files matched your search

+10 -3
View File
@@ -1,13 +1,16 @@
name: Release
# A pushed tag publishes the release that every client updates from: legacy
# Win32 clients (v0.5.0 and earlier) through the GitHub API's
# A pushed plain vX.Y.Z tag publishes the release that every client updates
# from: legacy Win32 clients (v0.5.0 and earlier) through the GitHub API's
# `/releases/latest`, Tauri clients through its `latest.json` asset. A
# dispatch run builds the same assets into a draft, which neither reaches.
# Suffixed tags (vX.Y.Z-suffix) are dispatch-only: both client comparators
# ignore the suffix, so publishing one would ship an rc as a stable update
# and hide the final vX.Y.Z behind it.
on:
push:
tags: ['v*.*.*']
tags: ['v[0-9]+.[0-9]+.[0-9]+']
workflow_dispatch:
inputs:
tag:
@@ -43,6 +46,10 @@ jobs:
echo "::error::Tag '$tag' is not of the form vX.Y.Z or vX.Y.Z-suffix."
exit 1
fi
if [ "$GITHUB_EVENT_NAME" != workflow_dispatch ] && [[ "$tag" == *-* ]]; then
echo "::error::Tag '$tag' has a suffix; suffixed tags are for draft dispatch runs only and are never published."
exit 1
fi
expected="${tag#v}"
cargo_version=$(sed -nE 's/^version[[:space:]]*=[[:space:]]*"([^"]+)".*/\1/p' Cargo.toml | head -n 1)
if [ "$cargo_version" != "$expected" ]; then
+7 -3
View File
@@ -1,7 +1,9 @@
# Cutting a release
The `release.yml` workflow builds and publishes on every pushed tag
matching `v*.*.*`. The workflow asserts that the pushed tag matches
The `release.yml` workflow builds and publishes on every pushed plain
`vX.Y.Z` tag. Pushing a tag with a suffix (`vX.Y.Z-rc1`) publishes
nothing: the updaters ignore suffixes, so an rc would reach every user
as a stable release. The workflow asserts that the pushed tag matches
`Cargo.toml` `version` and **fails fast on mismatch**, so the order
below matters: bump Cargo *before* you tag.
@@ -31,7 +33,9 @@ below matters: bump Cargo *before* you tag.
## Testing without a real tag
Use the workflow's `workflow_dispatch` input with a throwaway tag
like `v0.0.0-test`. The release is created as a **draft**, so it
that carries a suffix, like `v0.0.0-test`. The suffix keeps the tag
push from starting a publishing run; never use a plain `vX.Y.Z` tag for
a dry run, because pushing it publishes a release marked latest. The release is created as a **draft**, so it
does not show up on the public Releases feed or trigger self-updates
for users.
The tag must already exist and point at a commit whose `Cargo.toml`
@@ -150,6 +150,7 @@ host has no patchelf and no display.
- **`make-latest-json.js` scope.** Besides the listed guards it requires the exact asset set (any unexpected file fails), so a stray upload cannot slip into a release. It writes `<dir>/latest.json` and ignores an existing one, so a re-run is safe.
- **yamllint.** The repo had no yamllint config, and the default rules reject every GitHub workflow (`on:` as a truthy key, no `---`, 80-column lines), `ci.yml` included. `.yamllint.yaml` relaxes exactly those three rules (line length 140), so the check is reproducible.
- **Draft dry-run tag.** The tag check also runs for dispatch, so `v0.0.0-test` must point at a commit whose `Cargo.toml` says `0.0.0-test`; `docs/release-process.md` says so.
- **Suffixed tags never publish.** The push trigger matches only plain `vX.Y.Z`, and `prepare` also fails a non-dispatch run on a suffixed tag. Before this, pushing `v0.0.0-test` (or an rc tag) started a run that published it with `--latest`, and both client comparators ignore the suffix.
## Residual risk
@@ -179,7 +180,7 @@ script, not against a real draft.
| A missing secret silently produces an unsigned build | L x H | The signing step fails hard when the env var is empty. |
| An old client picks the wrong asset | L x H | Exactly one `*.exe` asset, enforced by `make-latest-json.js`. |
| The Tauri release is not the one `/releases/latest` returns | L x H | Tag runs publish non-draft, non-prerelease, `--latest`; check with `gh api repos/tiennm99/claude-code-usage-bubble/releases/latest -q .tag_name` after publishing. |
| A draft dry run reaches users | L x H | `/releases/latest` skips drafts; both drafts are deleted after inspection. |
| A draft dry run reaches users | L x H | `/releases/latest` skips drafts; the dry-run tag carries a suffix, which the push trigger and `prepare` refuse to publish; both drafts are deleted after inspection. |
## Rollback