Skip to content

Desktop Release

Desktop Release #6

name: Desktop Release
on:
workflow_dispatch:
inputs:
version:
description: Release version to publish (for example 2026.4.7)
required: true
type: string
prerelease:
description: Publish this release as a prerelease
required: true
default: false
type: boolean
push:
tags:
- "v*.*.*"
permissions:
contents: write
concurrency:
group: desktop-release-${{ github.event_name }}-${{ github.ref }}
cancel-in-progress: false
jobs:
prepare:
name: Prepare release tag
if: github.event_name == 'workflow_dispatch'
runs-on: ubuntu-latest
outputs:
tag: ${{ steps.release_meta.outputs.tag }}
version: ${{ steps.release_meta.outputs.version }}
tag_exists: ${{ steps.check_tag.outputs.exists }}
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- uses: actions/setup-node@v4
with:
node-version: 22
- id: release_meta
env:
INPUT_VERSION: ${{ github.event.inputs.version }}
run: |
version="${INPUT_VERSION#v}"
echo "version=$version" >> "$GITHUB_OUTPUT"
echo "tag=v$version" >> "$GITHUB_OUTPUT"
- id: check_tag
name: Check if tag already exists
env:
RELEASE_TAG: ${{ steps.release_meta.outputs.tag }}
run: |
if git ls-remote --exit-code --tags origin "refs/tags/$RELEASE_TAG" >/dev/null 2>&1; then
echo "exists=true" >> "$GITHUB_OUTPUT"
echo "::notice::Release tag $RELEASE_TAG already exists on origin. Reusing the existing tag and release entry."
else
echo "exists=false" >> "$GITHUB_OUTPUT"
fi
- name: Sync repository versions
if: steps.check_tag.outputs.exists != 'true'
env:
RELEASE_VERSION: ${{ steps.release_meta.outputs.version }}
run: node scripts/sync-version.mjs --set "$RELEASE_VERSION"
- name: Commit release metadata
if: steps.check_tag.outputs.exists != 'true'
env:
RELEASE_VERSION: ${{ steps.release_meta.outputs.version }}
run: |
git config user.name "github-actions[bot]"
git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
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
if ! git diff --cached --quiet; then
git commit -m "chore(release): cut v$RELEASE_VERSION"
fi
- name: Create release tag
if: steps.check_tag.outputs.exists != 'true'
env:
RELEASE_TAG: ${{ steps.release_meta.outputs.tag }}
run: git tag "$RELEASE_TAG"
- name: Push release commit and tag
if: steps.check_tag.outputs.exists != 'true'
env:
RELEASE_TAG: ${{ steps.release_meta.outputs.tag }}
RELEASE_BRANCH: ${{ github.ref_name }}
run: |
git push origin "HEAD:$RELEASE_BRANCH"
git push origin "$RELEASE_TAG"
release-info:
name: Resolve release metadata
needs: [prepare]
if: always() && (github.event_name == 'push' || needs.prepare.result == 'success')
runs-on: ubuntu-latest
outputs:
tag: ${{ steps.meta.outputs.tag }}
version: ${{ steps.meta.outputs.version }}
prerelease: ${{ steps.meta.outputs.prerelease }}
ref: ${{ steps.meta.outputs.ref }}
steps:
- id: meta
env:
EVENT_NAME: ${{ github.event_name }}
PUSH_TAG: ${{ github.ref_name }}
PUSH_REF: ${{ github.ref }}
INPUT_PRERELEASE: ${{ github.event.inputs.prerelease }}
PREPARED_TAG: ${{ needs.prepare.outputs.tag }}
PREPARED_VERSION: ${{ needs.prepare.outputs.version }}
run: |
if [ "$EVENT_NAME" = "workflow_dispatch" ]; then
tag="$PREPARED_TAG"
version="$PREPARED_VERSION"
prerelease="$INPUT_PRERELEASE"
case "$version" in
*-*) prerelease="true" ;;
esac
ref="refs/tags/$tag"
else
tag="$PUSH_TAG"
version="${PUSH_TAG#v}"
case "$version" in
*-*) prerelease="true" ;;
*) prerelease="false" ;;
esac
ref="$PUSH_REF"
fi
echo "tag=$tag" >> "$GITHUB_OUTPUT"
echo "version=$version" >> "$GITHUB_OUTPUT"
echo "prerelease=$prerelease" >> "$GITHUB_OUTPUT"
echo "ref=$ref" >> "$GITHUB_OUTPUT"
create-release:
name: Create GitHub release
needs: [release-info]
if: always() && needs.release-info.result == 'success'
runs-on: ubuntu-latest
outputs:
release_id: ${{ steps.release.outputs.release_id }}
steps:
- id: release
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
GH_REPO: ${{ github.repository }}
RELEASE_TAG: ${{ needs.release-info.outputs.tag }}
RELEASE_VERSION: ${{ needs.release-info.outputs.version }}
RELEASE_PRERELEASE: ${{ needs.release-info.outputs.prerelease }}
run: |
if gh release view "$RELEASE_TAG" >/dev/null 2>&1; then
release_id="$(gh release view "$RELEASE_TAG" --json databaseId --jq '.databaseId')"
else
args=(
release create "$RELEASE_TAG"
--title "OpenGoat v$RELEASE_VERSION"
--generate-notes
--notes "Download the installer for your platform from the assets below."
)
if [ "$RELEASE_PRERELEASE" = "true" ]; then
args+=(--prerelease)
fi
gh "${args[@]}"
release_id="$(gh release view "$RELEASE_TAG" --json databaseId --jq '.databaseId')"
fi
echo "release_id=$release_id" >> "$GITHUB_OUTPUT"
publish:
name: Build and publish desktop bundles
needs: [release-info, create-release]
if: always() && needs.release-info.result == 'success' && needs.create-release.result == 'success'
strategy:
fail-fast: false
matrix:
include:
- os: macos-latest
target: aarch64-apple-darwin
- os: windows-latest
target: x86_64-pc-windows-msvc
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
ref: ${{ needs.release-info.outputs.ref }}
- uses: pnpm/action-setup@v4
- uses: actions/setup-node@v4
with:
node-version: 22
cache: pnpm
- uses: dtolnay/rust-toolchain@stable
with:
targets: ${{ matrix.target }}
- uses: Swatinem/rust-cache@v2
- name: Install dependencies
shell: bash
run: pnpm install --frozen-lockfile
- name: Verify release version sync
shell: bash
run: pnpm release:check-version
- name: Build desktop bundles
env:
CI: true
shell: bash
run: |
args=(--target "${{ matrix.target }}" --ci)
if [ "${{ runner.os }}" = "macOS" ]; then
args+=(--no-sign)
fi
pnpm --dir apps/desktop tauri build "${args[@]}"
- name: Upload desktop bundles to GitHub release
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
RELEASE_TAG: ${{ needs.release-info.outputs.tag }}
RELEASE_VERSION: ${{ needs.release-info.outputs.version }}
TARGET_TRIPLE: ${{ matrix.target }}
shell: bash
run: |
bundle_root="target/$TARGET_TRIPLE/release/bundle"
upload_dir="$RUNNER_TEMP/release-assets"
rm -rf "$upload_dir"
mkdir -p "$upload_dir"
if [ "${{ runner.os }}" = "macOS" ]; then
dmg_path="$(find "$bundle_root/dmg" -type f -name '*.dmg' | head -n1)"
app_path="$(find "$bundle_root/macos" -type d -name '*.app' | head -n1)"
if [ -n "$dmg_path" ]; then
cp "$dmg_path" "$upload_dir/OpenGoat_${RELEASE_VERSION}_darwin_aarch64.dmg"
fi
if [ -n "$app_path" ]; then
app_name="$(basename "$app_path")"
tar -C "$(dirname "$app_path")" -czf "$upload_dir/OpenGoat_${RELEASE_VERSION}_darwin_aarch64.app.tar.gz" "$app_name"
fi
else
msi_path="$(find "$bundle_root" -type f -name '*.msi' | head -n1)"
exe_path="$(find "$bundle_root" -type f -name '*setup*.exe' | head -n1)"
if [ -n "$msi_path" ]; then
cp "$msi_path" "$upload_dir/OpenGoat_${RELEASE_VERSION}_windows_x64.msi"
fi
if [ -n "$exe_path" ]; then
cp "$exe_path" "$upload_dir/OpenGoat_${RELEASE_VERSION}_windows_x64_setup.exe"
fi
fi
mapfile -d '' assets < <(find "$upload_dir" -type f -print0)
if [ "${#assets[@]}" -eq 0 ]; then
echo "::error::No release assets found under $bundle_root"
exit 1
fi
gh release upload "$RELEASE_TAG" "${assets[@]}" --clobber