@@ -2,14 +2,88 @@ name: Release
22
33on :
44 push :
5+ branches : [main]
56 tags :
67 - " release-[0-9]+.[0-9]+.[0-9]+*"
78
89permissions :
910 contents : write
1011
1112jobs :
13+ prepare :
14+ runs-on : ubuntu-latest
15+ outputs :
16+ version : ${{ steps.decide.outputs.version }}
17+ tag : ${{ steps.decide.outputs.tag }}
18+ should_release : ${{ steps.decide.outputs.should_release }}
19+ steps :
20+ - uses : actions/checkout@v4
21+ with :
22+ fetch-depth : 0
23+
24+ - name : Determine version and tag
25+ id : decide
26+ shell : bash
27+ run : |
28+ set -euo pipefail
29+
30+ extract_toml_version() {
31+ # Read the first `version = "X.Y.Z"` line from the [package] table.
32+ awk -F'"' '/^version = /{print $2; exit}' "$1"
33+ }
34+
35+ DESKTOP_VER=$(extract_toml_version src-tauri/Cargo.toml)
36+ CLI_VER=$(extract_toml_version crates/ralph-cli/Cargo.toml)
37+ CONF_VER=$(python3 -c 'import json; print(json.load(open("src-tauri/tauri.conf.json"))["version"])')
38+ PKG_VER=$(python3 -c 'import json; print(json.load(open("package.json"))["version"])')
39+
40+ # src-tauri/Cargo.toml is canonical. Every other version string must
41+ # agree, otherwise the built artifacts would misrepresent themselves
42+ # and the updater's latest.json would disagree with the installed
43+ # binary's reported version.
44+ fail=0
45+ for pair in "src-tauri/Cargo.toml=$DESKTOP_VER" \
46+ "crates/ralph-cli/Cargo.toml=$CLI_VER" \
47+ "src-tauri/tauri.conf.json=$CONF_VER" \
48+ "package.json=$PKG_VER"; do
49+ file="${pair%%=*}"
50+ ver="${pair#*=}"
51+ if [[ "$ver" != "$DESKTOP_VER" ]]; then
52+ echo "::error file=$file::version mismatch: $file is '$ver', expected '$DESKTOP_VER' (from src-tauri/Cargo.toml)"
53+ fail=1
54+ fi
55+ done
56+ if [[ "$fail" -eq 1 ]]; then
57+ exit 1
58+ fi
59+
60+ VERSION="$DESKTOP_VER"
61+ TAG="release-${VERSION}"
62+
63+ if [[ "$GITHUB_REF" == refs/tags/release-* ]]; then
64+ # Direct tag push — build + release unconditionally.
65+ SHOULD_RELEASE=true
66+ else
67+ # Push to main — only release if this version hasn't been tagged.
68+ if git rev-parse -q --verify "refs/tags/$TAG" >/dev/null; then
69+ echo "Tag $TAG already exists — version unchanged, skipping release"
70+ SHOULD_RELEASE=false
71+ else
72+ git config user.name "github-actions[bot]"
73+ git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
74+ git tag -a "$TAG" -m "Release $VERSION"
75+ git push origin "$TAG"
76+ SHOULD_RELEASE=true
77+ fi
78+ fi
79+
80+ echo "version=$VERSION" >> "$GITHUB_OUTPUT"
81+ echo "tag=$TAG" >> "$GITHUB_OUTPUT"
82+ echo "should_release=$SHOULD_RELEASE" >> "$GITHUB_OUTPUT"
83+
1284 build-cli :
85+ needs : prepare
86+ if : needs.prepare.outputs.should_release == 'true'
1387 strategy :
1488 matrix :
1589 include :
@@ -113,6 +187,8 @@ jobs:
113187 path : ${{ matrix.artifact }}${{ runner.os == 'Windows' && '.exe' || '' }}
114188
115189 build-desktop :
190+ needs : prepare
191+ if : needs.prepare.outputs.should_release == 'true'
116192 strategy :
117193 matrix :
118194 include :
@@ -193,12 +269,12 @@ jobs:
193269 - name : Collect updater metadata
194270 shell : bash
195271 env :
196- TAG_NAME : ${{ github.ref_name }}
272+ TAG_NAME : ${{ needs.prepare.outputs.tag }}
273+ VERSION : ${{ needs.prepare.outputs.version }}
197274 BUNDLE_DIR : target/${{ matrix.target }}/release/bundle
198275 UPDATER_PLATFORM : ${{ matrix.updater_platform }}
199276 run : |
200277 set -euo pipefail
201- VERSION="${TAG_NAME#release-}"
202278 # Find the updater bundle + its .sig produced by Tauri. Each OS
203279 # has exactly one updater target: .app.tar.gz (mac), .nsis .exe
204280 # (windows), .AppImage (linux).
@@ -251,38 +327,35 @@ jobs:
251327 path : updater-meta/*.json
252328
253329 release :
254- needs : [build-cli, build-desktop]
330+ needs : [prepare, build-cli, build-desktop]
331+ if : needs.prepare.outputs.should_release == 'true'
255332 runs-on : ubuntu-latest
256333
257334 steps :
258- - name : Extract version from tag
259- id : version
260- run : echo "version=${GITHUB_REF_NAME#release-}" >> $GITHUB_OUTPUT
261-
262335 - name : Download all artifacts
263336 uses : actions/download-artifact@v4
264337 with :
265338 path : artifacts
266339
267340 - name : Assemble updater latest.json
341+ env :
342+ VERSION : ${{ needs.prepare.outputs.version }}
268343 run : |
269344 set -euo pipefail
270- VERSION="${{ steps.version.outputs.version }}"
271345 PUB_DATE=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
272346 python3 - <<PY
273347 import json, glob, os
274348 platforms = {}
275- version = None
349+ version = os.environ["VERSION"]
276350 for path in glob.glob("artifacts/updater-meta-*/*.json"):
277351 with open(path) as f:
278352 meta = json.load(f)
279- version = meta["version"]
280353 platforms[meta["platform"]] = {
281354 "signature": meta["signature"],
282355 "url": meta["url"],
283356 }
284357 out = {
285- "version": version or "$VERSION" ,
358+ "version": version,
286359 "notes": "See release notes on GitHub.",
287360 "pub_date": "$PUB_DATE",
288361 "platforms": platforms,
@@ -296,7 +369,8 @@ jobs:
296369 - name : Create GitHub Release
297370 uses : softprops/action-gh-release@v2
298371 with :
299- name : v${{ steps.version.outputs.version }}
372+ tag_name : ${{ needs.prepare.outputs.tag }}
373+ name : v${{ needs.prepare.outputs.version }}
300374 generate_release_notes : true
301375 body : |
302376 ## macOS notes
0 commit comments