Skip to content

Commit c8aaa2e

Browse files
committed
chore: add desktop github release flow
1 parent 771ce13 commit c8aaa2e

File tree

13 files changed

+559
-12
lines changed

13 files changed

+559
-12
lines changed
Lines changed: 213 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,213 @@
1+
name: Desktop Release
2+
3+
on:
4+
workflow_dispatch:
5+
inputs:
6+
version:
7+
description: Release version to publish (for example 2026.4.7)
8+
required: true
9+
type: string
10+
prerelease:
11+
description: Publish this release as a prerelease
12+
required: true
13+
default: false
14+
type: boolean
15+
push:
16+
tags:
17+
- "v*.*.*"
18+
19+
permissions:
20+
contents: write
21+
22+
concurrency:
23+
group: desktop-release-${{ github.event_name }}-${{ github.ref }}
24+
cancel-in-progress: false
25+
26+
jobs:
27+
prepare:
28+
name: Prepare release tag
29+
if: github.event_name == 'workflow_dispatch'
30+
runs-on: ubuntu-latest
31+
outputs:
32+
tag: ${{ steps.release_meta.outputs.tag }}
33+
version: ${{ steps.release_meta.outputs.version }}
34+
steps:
35+
- uses: actions/checkout@v4
36+
with:
37+
fetch-depth: 0
38+
39+
- uses: actions/setup-node@v4
40+
with:
41+
node-version: 22
42+
43+
- id: release_meta
44+
env:
45+
INPUT_VERSION: ${{ github.event.inputs.version }}
46+
run: |
47+
version="${INPUT_VERSION#v}"
48+
echo "version=$version" >> "$GITHUB_OUTPUT"
49+
echo "tag=v$version" >> "$GITHUB_OUTPUT"
50+
51+
- name: Fail if tag already exists
52+
env:
53+
RELEASE_TAG: ${{ steps.release_meta.outputs.tag }}
54+
run: |
55+
if git ls-remote --exit-code --tags origin "refs/tags/$RELEASE_TAG" >/dev/null 2>&1; then
56+
echo "::error::Release tag $RELEASE_TAG already exists on origin. Use a new version or recreate the tag deliberately before releasing."
57+
exit 1
58+
fi
59+
60+
- name: Sync repository versions
61+
env:
62+
RELEASE_VERSION: ${{ steps.release_meta.outputs.version }}
63+
run: node scripts/sync-version.mjs --set "$RELEASE_VERSION"
64+
65+
- name: Commit release metadata
66+
env:
67+
RELEASE_VERSION: ${{ steps.release_meta.outputs.version }}
68+
run: |
69+
git config user.name "github-actions[bot]"
70+
git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
71+
git add VERSION Cargo.lock Cargo.toml package.json apps/desktop/package.json apps/desktop/src-tauri/tauri.conf.json packages/contracts/package.json packages/core/package.json packages/sidecar/package.json packages/cli/package.json
72+
if ! git diff --cached --quiet; then
73+
git commit -m "chore(release): cut v$RELEASE_VERSION"
74+
fi
75+
76+
- name: Create release tag
77+
env:
78+
RELEASE_TAG: ${{ steps.release_meta.outputs.tag }}
79+
run: git tag "$RELEASE_TAG"
80+
81+
- name: Push release commit and tag
82+
env:
83+
RELEASE_TAG: ${{ steps.release_meta.outputs.tag }}
84+
RELEASE_BRANCH: ${{ github.ref_name }}
85+
run: |
86+
git push origin "HEAD:$RELEASE_BRANCH"
87+
git push origin "$RELEASE_TAG"
88+
89+
release-info:
90+
name: Resolve release metadata
91+
needs: [prepare]
92+
if: always() && (github.event_name == 'push' || needs.prepare.result == 'success')
93+
runs-on: ubuntu-latest
94+
outputs:
95+
tag: ${{ steps.meta.outputs.tag }}
96+
version: ${{ steps.meta.outputs.version }}
97+
prerelease: ${{ steps.meta.outputs.prerelease }}
98+
ref: ${{ steps.meta.outputs.ref }}
99+
steps:
100+
- id: meta
101+
env:
102+
EVENT_NAME: ${{ github.event_name }}
103+
PUSH_TAG: ${{ github.ref_name }}
104+
PUSH_REF: ${{ github.ref }}
105+
INPUT_PRERELEASE: ${{ github.event.inputs.prerelease }}
106+
PREPARED_TAG: ${{ needs.prepare.outputs.tag }}
107+
PREPARED_VERSION: ${{ needs.prepare.outputs.version }}
108+
run: |
109+
if [ "$EVENT_NAME" = "workflow_dispatch" ]; then
110+
tag="$PREPARED_TAG"
111+
version="$PREPARED_VERSION"
112+
prerelease="$INPUT_PRERELEASE"
113+
case "$version" in
114+
*-*) prerelease="true" ;;
115+
esac
116+
ref="refs/tags/$tag"
117+
else
118+
tag="$PUSH_TAG"
119+
version="${PUSH_TAG#v}"
120+
case "$version" in
121+
*-*) prerelease="true" ;;
122+
*) prerelease="false" ;;
123+
esac
124+
ref="$PUSH_REF"
125+
fi
126+
echo "tag=$tag" >> "$GITHUB_OUTPUT"
127+
echo "version=$version" >> "$GITHUB_OUTPUT"
128+
echo "prerelease=$prerelease" >> "$GITHUB_OUTPUT"
129+
echo "ref=$ref" >> "$GITHUB_OUTPUT"
130+
131+
create-release:
132+
name: Create GitHub release
133+
needs: [release-info]
134+
if: always() && needs.release-info.result == 'success'
135+
runs-on: ubuntu-latest
136+
outputs:
137+
release_id: ${{ steps.release.outputs.release_id }}
138+
steps:
139+
- id: release
140+
env:
141+
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
142+
GH_REPO: ${{ github.repository }}
143+
RELEASE_TAG: ${{ needs.release-info.outputs.tag }}
144+
RELEASE_VERSION: ${{ needs.release-info.outputs.version }}
145+
RELEASE_PRERELEASE: ${{ needs.release-info.outputs.prerelease }}
146+
run: |
147+
if gh release view "$RELEASE_TAG" >/dev/null 2>&1; then
148+
release_id="$(gh release view "$RELEASE_TAG" --json databaseId --jq '.databaseId')"
149+
else
150+
args=(
151+
release create "$RELEASE_TAG"
152+
--title "OpenGoat v$RELEASE_VERSION"
153+
--generate-notes
154+
--notes "Download the installer for your platform from the assets below."
155+
)
156+
if [ "$RELEASE_PRERELEASE" = "true" ]; then
157+
args+=(--prerelease)
158+
fi
159+
gh "${args[@]}"
160+
release_id="$(gh release view "$RELEASE_TAG" --json databaseId --jq '.databaseId')"
161+
fi
162+
echo "release_id=$release_id" >> "$GITHUB_OUTPUT"
163+
164+
publish:
165+
name: Build and publish desktop bundles
166+
needs: [release-info, create-release]
167+
if: always() && needs.release-info.result == 'success' && needs.create-release.result == 'success'
168+
strategy:
169+
fail-fast: false
170+
matrix:
171+
include:
172+
- os: macos-latest
173+
target: aarch64-apple-darwin
174+
- os: windows-latest
175+
target: x86_64-pc-windows-msvc
176+
runs-on: ${{ matrix.os }}
177+
steps:
178+
- uses: actions/checkout@v4
179+
with:
180+
fetch-depth: 0
181+
ref: ${{ needs.release-info.outputs.ref }}
182+
183+
- uses: pnpm/action-setup@v4
184+
185+
- uses: actions/setup-node@v4
186+
with:
187+
node-version: 22
188+
cache: pnpm
189+
190+
- uses: dtolnay/rust-toolchain@stable
191+
with:
192+
targets: ${{ matrix.target }}
193+
194+
- uses: Swatinem/rust-cache@v2
195+
196+
- name: Install dependencies
197+
run: pnpm install --frozen-lockfile
198+
199+
- name: Verify release version sync
200+
run: pnpm release:check-version
201+
202+
- uses: tauri-apps/tauri-action@v0.6.2
203+
env:
204+
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
205+
with:
206+
projectPath: ./apps/desktop
207+
tauriScript: pnpm tauri
208+
releaseId: ${{ needs.create-release.outputs.release_id }}
209+
tagName: ${{ needs.release-info.outputs.tag }}
210+
args: --target ${{ matrix.target }} --ci
211+
retryAttempts: 2
212+
includeUpdaterJson: false
213+
assetNamePattern: OpenGoat_[version]_[platform]_[arch][_setup].[ext]

.github/workflows/release.yml

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -106,7 +106,7 @@ jobs:
106106
if: steps.check_changesets.outputs.has_changesets == 'true'
107107
run: |
108108
VERSION="$(node -p "require('./packages/cli/package.json').version")"
109-
git add packages/contracts/package.json packages/core/package.json packages/cli/package.json CHANGELOG.md .changeset
109+
git add VERSION Cargo.lock Cargo.toml package.json apps/desktop/package.json apps/desktop/src-tauri/tauri.conf.json packages/contracts/package.json packages/core/package.json packages/sidecar/package.json packages/cli/package.json CHANGELOG.md .changeset
110110
git commit -m "chore: release ${VERSION}"
111111
git tag "v${VERSION}"
112112
git push
@@ -131,4 +131,8 @@ jobs:
131131
exit 1
132132
fi
133133
134-
gh release create "v${VERSION}" --title "v${VERSION}" --notes-file "$NOTES_FILE"
134+
if gh release view "v${VERSION}" >/dev/null 2>&1; then
135+
gh release edit "v${VERSION}" --title "v${VERSION}" --notes-file "$NOTES_FILE"
136+
else
137+
gh release create "v${VERSION}" --title "v${VERSION}" --notes-file "$NOTES_FILE"
138+
fi

CONTRIBUTING.md

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44

55
We use **Changesets** to gather release notes, but we release using **CalVer** (Date-based versioning, e.g., `YYYY.M.D`).
66

7-
### How to Create a Release
7+
### Package Releases
88

99
1. **Work as usual**: Make your changes in a feature branch.
1010
2. **Add a changeset**: Before merging, run `npm run changeset`.
@@ -19,6 +19,16 @@ We use **Changesets** to gather release notes, but we release using **CalVer** (
1919
- Publish to NPM.
2020
- Push the version commit and tag back to the repo.
2121

22+
### Desktop Releases
23+
24+
Desktop installers are published through the GitHub Actions workflow `Desktop Release`.
25+
26+
- Run it manually with a target version such as `2026.4.7`, or let it react automatically to a pushed `v2026.4.7` tag.
27+
- The workflow synchronizes `VERSION`, package metadata, Tauri config, and Cargo workspace version before building desktop artifacts.
28+
- It publishes macOS and Windows installers to the matching GitHub release.
29+
30+
See [`docs/releases.md`](docs/releases.md) for the desktop release workflow details and repository prerequisites.
31+
2232
### Prerequisite
2333

24-
To publish to NPM, the repository needs the `NPM_TOKEN` secret configured in GitHub context.
34+
To publish packages from GitHub Actions, npm trusted publishing must remain configured for this repository.

Cargo.lock

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ authors = ["OpenGoat"]
77
edition = "2024"
88
license = "Proprietary"
99
rust-version = "1.94"
10-
version = "0.1.0"
10+
version = "2026.2.23"
1111

1212
[workspace.dependencies]
1313
reqwest = { version = "0.13.2", default-features = false, features = ["http2", "json", "rustls"] }

VERSION

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
2026.2.23

apps/desktop/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{
22
"name": "@opengoat/desktop",
33
"private": true,
4-
"version": "0.1.0",
4+
"version": "2026.2.23",
55
"type": "module",
66
"scripts": {
77
"dev": "vite",

apps/desktop/src-tauri/tauri.conf.json

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,12 @@
11
{
22
"$schema": "https://schema.tauri.app/config/2",
33
"productName": "OpenGoat",
4-
"version": "0.1.0",
4+
"version": "2026.2.23",
55
"identifier": "com.opengoat.app",
66
"build": {
77
"beforeDevCommand": "pnpm --dir ../../ desktop:frontend:dev",
88
"devUrl": "http://localhost:1430",
9-
"beforeBuildCommand": "pnpm --dir ../../ desktop:frontend:build && pnpm --dir ../../ sidecar:bundle",
9+
"beforeBuildCommand": "pnpm --dir ../../ desktop:frontend:release-build && pnpm --dir ../../ sidecar:bundle",
1010
"frontendDist": "../dist"
1111
},
1212
"app": {
@@ -27,6 +27,9 @@
2727
},
2828
"bundle": {
2929
"active": true,
30+
"macOS": {
31+
"signingIdentity": "-"
32+
},
3033
"resources": {
3134
"../../../packages/sidecar/.bundle/": "sidecar/"
3235
},

docs/releases.md

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
# Desktop Releases
2+
3+
OpenGoat desktop releases are versioned from the root [`VERSION`](../VERSION) file.
4+
5+
## What ships
6+
7+
The desktop release workflow publishes:
8+
9+
- macOS desktop bundles for Apple Silicon as `.app` and `.dmg`
10+
- Windows desktop installers as `.msi` and NSIS `.exe`
11+
12+
The workflow is defined in [`/.github/workflows/desktop-release.yml`](../.github/workflows/desktop-release.yml).
13+
14+
## How to cut a desktop release
15+
16+
Use GitHub Actions and run the `Desktop Release` workflow manually.
17+
18+
Inputs:
19+
20+
- `version`: release version without the `v` prefix, for example `2026.4.7`
21+
- `prerelease`: set to `true` when publishing a prerelease
22+
23+
The workflow will:
24+
25+
1. Synchronize the repository version metadata to the requested release version
26+
2. Commit the version bump as `chore(release): cut vX.Y.Z`
27+
3. Create and push the `vX.Y.Z` tag
28+
4. Create the GitHub release if it does not already exist
29+
5. Build macOS and Windows desktop bundles
30+
6. Upload the generated installers to that release
31+
32+
The workflow also runs automatically on `v*` tag pushes. That makes it compatible with the existing Changesets-based npm release flow: once the npm release workflow tags a version, desktop artifacts are attached to the same GitHub release.
33+
34+
## Repository prerequisites
35+
36+
- GitHub Actions must have `Read and write permissions` so the workflow can push the release commit and tag with `GITHUB_TOKEN`
37+
- If your default branch is protected, GitHub Actions must be allowed to push the automated `chore(release): cut vX.Y.Z` commit, or you should cut releases from an unprotected release branch
38+
- Hosted runners must stay enabled for both `macos-latest` and `windows-latest`
39+
40+
## Signing
41+
42+
Local and CI macOS builds use Tauri's ad-hoc signing identity (`-`) by default. That is suitable for internal testing only.
43+
44+
For broader production distribution, add platform signing credentials before relying on these artifacts:
45+
46+
- macOS: Developer ID Application certificate and notarization credentials
47+
- Windows: code-signing certificate

package.json

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@opengoat/opengoat-monorepo",
3-
"version": "0.1.0",
3+
"version": "2026.2.23",
44
"private": true,
55
"description": "OpenGoat monorepo workspace",
66
"type": "module",
@@ -14,6 +14,7 @@
1414
"desktop:dev": "pnpm --filter @opengoat/desktop tauri dev",
1515
"desktop:frontend:build": "pnpm --filter @opengoat/desktop build",
1616
"desktop:frontend:dev": "pnpm --filter @opengoat/desktop dev",
17+
"desktop:frontend:release-build": "pnpm --filter @opengoat/desktop exec vite build",
1718
"sidecar:bundle": "pnpm --filter @opengoat/sidecar bundle:prepare",
1819
"sidecar:build": "pnpm --filter @opengoat/sidecar build",
1920
"sidecar:dev": "pnpm --filter @opengoat/sidecar dev",
@@ -26,6 +27,8 @@
2627
"test:watch": "vitest",
2728
"typecheck": "pnpm --filter @opengoat/contracts build && pnpm --filter @opengoat/core typecheck && pnpm --filter opengoat typecheck",
2829
"changeset": "changeset",
30+
"release:check-version": "node scripts/sync-version.mjs --check",
31+
"release:sync-version": "node scripts/sync-version.mjs",
2932
"version-packages": "node scripts/release.mjs",
3033
"release": "node scripts/release.mjs",
3134
"prepare": "husky",

0 commit comments

Comments
 (0)