11name : Release
22
3- # Manual (workflow_dispatch) only — a deliberate choice, not a cost concern. The
4- # repo is public so GitHub Actions is free, but we don't want every tag push to
5- # kick off a 4-platform build. The default path is the local macOS release
6- # script (`scripts/release-local.sh`); use this workflow when you want CI to
7- # build/publish a tagged release directly on this repo (notably the
8- # Windows/Linux artifacts the mac-only local script can't produce).
3+ # Runs automatically on every `v*` tag push, building and publishing all four
4+ # platforms (macOS arm64 + x86_64, Linux, Windows). This repo is public, so
5+ # Actions minutes are free — the earlier manual-only design was a deliberate
6+ # "don't build 4 platforms on every tag" choice, which the operator revisited
7+ # (2026-07-16): the Windows/Linux artifacts are the point, and dispatching by
8+ # hand every time was the thing actually costing something.
9+ #
10+ # `workflow_dispatch` is kept for re-running a tag whose build failed, or for
11+ # releasing an old tag.
12+ #
13+ # ⚠ `scripts/release-local.sh` is now REDUNDANT for a normal release, and must
14+ # NOT be run for a tag this workflow is already building: it does
15+ # `gh release edit --draft=false`, which would publish the release before the
16+ # Windows/Linux jobs have uploaded — exactly the partial-`latest.json` failure
17+ # (v0.1.0-alpha.54) that the draft-until-all-platforms-finish design below
18+ # exists to prevent. Keep the script for the case CI can't cover (e.g. Actions
19+ # down, or a mac-only hotfix).
920on :
21+ push :
22+ tags :
23+ - ' v*'
1024 workflow_dispatch :
1125 inputs :
1226 tag :
1327 description : ' Tag to release (e.g. v0.1.0-alpha.7)'
1428 required : true
1529
16- # Re-dispatching the same tag cancels the previous in-flight run so we don't
17- # build the same release twice.
30+ # The tag being released is `inputs.tag || github.ref_name` throughout: on a tag
31+ # push `inputs` is empty so the fallback picks the pushed ref; on a manual
32+ # dispatch the input wins. The expression is repeated rather than hoisted into
33+ # workflow-level `env` because `concurrency` below can't read the `env` context.
34+
35+ # Re-dispatching (or re-pushing) the same tag cancels the previous in-flight run
36+ # so we don't build the same release twice.
1837concurrency :
19- group : release-${{ inputs.tag }}
38+ group : release-${{ inputs.tag || github.ref_name }}
2039 cancel-in-progress : true
2140
2241permissions :
@@ -31,10 +50,15 @@ jobs:
3150 outputs :
3251 body : ${{ steps.extract.outputs.body }}
3352 steps :
53+ # Read CHANGELOG.md as it was AT THE TAG, not at the default branch —
54+ # otherwise a manual dispatch of an old tag would splice in notes main has
55+ # since edited.
3456 - uses : actions/checkout@v4
57+ with :
58+ ref : ${{ inputs.tag || github.ref_name }}
3559 - id : extract
3660 env :
37- TAG : ${{ inputs.tag }}
61+ TAG : ${{ inputs.tag || github.ref_name }}
3862 run : |
3963 VERSION="${TAG#v}"
4064 {
@@ -78,8 +102,13 @@ jobs:
78102 runs-on : ${{ startsWith(matrix.platform, 'macos') && (vars.MACOS_RUNNER || 'macos-latest') || matrix.platform }}
79103
80104 steps :
105+ # Build the tag's source, not the default branch's. Without this the
106+ # artifacts would be whatever main happens to hold while carrying the
107+ # tag's name — silently shipping unreleased code under a released version.
81108 - name : Checkout
82109 uses : actions/checkout@v4
110+ with :
111+ ref : ${{ inputs.tag || github.ref_name }}
83112
84113 - name : Setup Node
85114 uses : actions/setup-node@v4
@@ -138,8 +167,8 @@ jobs:
138167 # leave this unset, which disables sync instead of hardcoding a relay.
139168 VITE_NORTHSTAR_SYNC_WORKER_URL : ${{ vars.NORTHSTAR_SYNC_WORKER_URL }}
140169 with :
141- tagName : ${{ inputs.tag }}
142- releaseName : Northstar ${{ inputs.tag }}
170+ tagName : ${{ inputs.tag || github.ref_name }}
171+ releaseName : Northstar ${{ inputs.tag || github.ref_name }}
143172 releaseBody : |
144173 ${{ needs.notes.outputs.body }}
145174
@@ -186,7 +215,7 @@ jobs:
186215 - name : Publish the completed draft release
187216 env :
188217 GH_TOKEN : ${{ secrets.GITHUB_TOKEN }}
189- TAG : ${{ inputs.tag }}
218+ TAG : ${{ inputs.tag || github.ref_name }}
190219 run : |
191220 set -euo pipefail
192221 release_id=$(gh api "repos/${GITHUB_REPOSITORY}/releases" --paginate \
0 commit comments