Skip to content

Release Desktop Multi-OS #19

Release Desktop Multi-OS

Release Desktop Multi-OS #19

name: Release Desktop Multi-OS
on:
push:
tags:
- "v*"
workflow_dispatch:
inputs:
tag:
description: "Tag to build and upload (for example: v1.6.1)"
required: true
type: string
source_ref:
description: "Optional build source ref (branch/commit/tag). Defaults to the release tag when omitted."
required: false
type: string
permissions:
contents: write
env:
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: "true"
GODOT_MIRROR_TAG: "godot-mirror-v4.3-stable"
GODOT_UPSTREAM_BASE_URL: "https://github.com/godotengine/godot/releases/download/4.3-stable"
GODOT_WINDOWS_ARCHIVE_NAME: "Godot_v4.3-stable_win64.exe.zip"
GODOT_LINUX_ARCHIVE_NAME: "Godot_v4.3-stable_linux.x86_64.zip"
GODOT_MACOS_ARCHIVE_NAME: "Godot_v4.3-stable_macos.universal.zip"
jobs:
ensure-release:
name: Ensure GitHub Release Exists
runs-on: ubuntu-latest
outputs:
tag_name: ${{ steps.tag.outputs.tag_name }}
steps:
- name: Checkout repository
uses: actions/checkout@v5
with:
fetch-depth: 0
- name: Resolve tag name
id: tag
shell: bash
run: |
if [ "${{ github.event_name }}" = "workflow_dispatch" ]; then
TAG_NAME="${{ github.event.inputs.tag }}"
else
TAG_NAME="${GITHUB_REF#refs/tags/}"
fi
if [ -z "$TAG_NAME" ]; then
echo "::error::Unable to resolve release tag."
exit 1
fi
echo "tag_name=$TAG_NAME" >> "$GITHUB_OUTPUT"
echo "Resolved tag: $TAG_NAME"
- name: Ensure release record exists
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
shell: bash
run: |
TAG_NAME="${{ steps.tag.outputs.tag_name }}"
if gh release view "$TAG_NAME" >/dev/null 2>&1; then
echo "Release already exists for $TAG_NAME."
else
gh release create "$TAG_NAME" --title "$TAG_NAME" --notes "Automated desktop multi-OS release assets."
fi
ensure-godot-mirror-assets:
name: Ensure Godot Mirror Assets
needs: ensure-release
runs-on: ubuntu-latest
steps:
- name: Ensure Godot mirror release exists
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
shell: bash
run: |
if gh release view "$GODOT_MIRROR_TAG" >/dev/null 2>&1; then
echo "Godot mirror release already exists: $GODOT_MIRROR_TAG"
else
gh release create "$GODOT_MIRROR_TAG" \
--target "$GITHUB_SHA" \
--title "$GODOT_MIRROR_TAG" \
--notes "Project-controlled mirror for Godot desktop bootstrap archives."
fi
- name: Seed Godot mirror assets from upstream when missing
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
shell: bash
run: |
set -euo pipefail
mkdir -p build/godot-mirror
mapfile -t EXISTING_ASSETS < <(gh release view "$GODOT_MIRROR_TAG" --json assets --jq '.assets[].name')
has_asset() {
local needle="$1"
local asset
for asset in "${EXISTING_ASSETS[@]}"; do
if [ "$asset" = "$needle" ]; then
return 0
fi
done
return 1
}
for ASSET_NAME in \
"$GODOT_WINDOWS_ARCHIVE_NAME" \
"$GODOT_LINUX_ARCHIVE_NAME" \
"$GODOT_MACOS_ARCHIVE_NAME"
do
if has_asset "$ASSET_NAME"; then
echo "Mirror asset already present: $ASSET_NAME"
continue
fi
ARCHIVE_PATH="build/godot-mirror/$ASSET_NAME"
curl -fsSL "${GODOT_UPSTREAM_BASE_URL}/${ASSET_NAME}" -o "$ARCHIVE_PATH"
gh release upload "$GODOT_MIRROR_TAG" "$ARCHIVE_PATH" --clobber
echo "Uploaded mirror asset: $ASSET_NAME"
done
build-and-upload:
name: Build and Upload (${{ matrix.platform_label }})
needs: [ensure-release, ensure-godot-mirror-assets]
runs-on: ${{ matrix.runner }}
strategy:
fail-fast: false
matrix:
include:
- runner: windows-latest
platform_label: windows
files: |
src-tauri/target/release/bundle/**/*.exe
src-tauri/target/release/bundle/**/*.msi
- runner: ubuntu-latest
platform_label: linux
files: |
src-tauri/target/release/bundle/**/*.AppImage
src-tauri/target/release/bundle/**/*.deb
- runner: macos-latest
platform_label: macos
files: |
src-tauri/target/release/bundle/**/*.dmg
steps:
- name: Checkout repository
uses: actions/checkout@v5
with:
ref: ${{ github.event_name == 'workflow_dispatch' && github.event.inputs.source_ref != '' && github.event.inputs.source_ref || github.event_name == 'workflow_dispatch' && github.event.inputs.tag || github.ref }}
fetch-depth: 0
lfs: false
- name: Setup Node.js
uses: actions/setup-node@v5
with:
node-version: "20"
cache: "npm"
- name: Setup Rust toolchain
uses: dtolnay/rust-toolchain@stable
- name: Install Linux native dependencies
if: runner.os == 'Linux'
shell: bash
run: |
set -euo pipefail
sudo apt-get update
if apt-cache show libwebkit2gtk-4.1-dev >/dev/null 2>&1; then
WEBKIT_PKG="libwebkit2gtk-4.1-dev"
else
WEBKIT_PKG="libwebkit2gtk-4.0-dev"
fi
if apt-cache show libayatana-appindicator3-dev >/dev/null 2>&1; then
APPINDICATOR_PKG="libayatana-appindicator3-dev"
else
APPINDICATOR_PKG="libappindicator3-dev"
fi
sudo apt-get install -y \
"${WEBKIT_PKG}" \
libgtk-3-dev \
librsvg2-dev \
patchelf \
"${APPINDICATOR_PKG}"
echo "Installed Linux native dependencies with ${WEBKIT_PKG} and ${APPINDICATOR_PKG}."
- name: Install dependencies
run: npm ci
- name: Prepare Godot sidecar binary (Windows)
if: runner.os == 'Windows'
shell: pwsh
run: |
$ErrorActionPreference = "Stop"
New-Item -ItemType Directory -Path "build\godot" -Force | Out-Null
$archive = "build\godot\godot-win64.zip"
$mirrorBaseUrl = "https://github.com/Jacobinwwey/NoteConnection/releases/download/$env:GODOT_MIRROR_TAG"
$mirrorUrl = "$mirrorBaseUrl/$env:GODOT_WINDOWS_ARCHIVE_NAME"
$upstreamUrl = "$env:GODOT_UPSTREAM_BASE_URL/$env:GODOT_WINDOWS_ARCHIVE_NAME"
try {
Invoke-WebRequest -Uri $mirrorUrl -OutFile $archive
Write-Host "Downloaded Godot archive from project mirror: $mirrorUrl"
} catch {
Write-Warning "Project mirror download failed, falling back to upstream: $upstreamUrl"
Invoke-WebRequest -Uri $upstreamUrl -OutFile $archive
}
Expand-Archive -Path $archive -DestinationPath "build\godot\extract" -Force
$godotExe = Get-ChildItem -Path "build\godot\extract" -Filter "Godot_v4.3-stable_win64.exe" -Recurse | Select-Object -First 1
if (-not $godotExe) {
$godotExe = Get-ChildItem -Path "build\godot\extract" -Filter "*.exe" -Recurse | Where-Object { $_.Name -notmatch "_console\.exe$" } | Sort-Object Length -Descending | Select-Object -First 1
}
if (-not $godotExe) {
throw "Failed to locate extracted Godot Windows executable."
}
if ($godotExe.Length -lt 1048576) {
throw "Resolved Godot executable is too small ($($godotExe.Length) bytes): $($godotExe.FullName)"
}
Copy-Item -Path $godotExe.FullName -Destination "src-tauri\bin\godot-x86_64-pc-windows-msvc.exe" -Force
- name: Prepare Godot sidecar binary (Linux)
if: runner.os == 'Linux'
shell: bash
run: |
set -euo pipefail
mkdir -p build/godot
MIRROR_BASE_URL="https://github.com/Jacobinwwey/NoteConnection/releases/download/${GODOT_MIRROR_TAG}"
if ! curl -fsSL "${MIRROR_BASE_URL}/${GODOT_LINUX_ARCHIVE_NAME}" -o build/godot/godot-linux.zip; then
echo "Project mirror download failed, falling back to upstream."
curl -fsSL "${GODOT_UPSTREAM_BASE_URL}/${GODOT_LINUX_ARCHIVE_NAME}" -o build/godot/godot-linux.zip
fi
unzip -q -o build/godot/godot-linux.zip -d build/godot/extract
GODOT_BIN="$(find build/godot/extract -maxdepth 2 -type f -name 'Godot_v4.3-stable_linux.x86_64' | head -n 1)"
if [ -z "${GODOT_BIN}" ]; then
echo "Failed to locate extracted Godot Linux executable."
exit 1
fi
cp "${GODOT_BIN}" src-tauri/bin/godot-x86_64-unknown-linux-gnu
chmod +x src-tauri/bin/godot-x86_64-unknown-linux-gnu
- name: Prepare Godot sidecar binary (macOS)
if: runner.os == 'macOS'
shell: bash
run: |
set -euo pipefail
mkdir -p build/godot
MIRROR_BASE_URL="https://github.com/Jacobinwwey/NoteConnection/releases/download/${GODOT_MIRROR_TAG}"
if ! curl -fsSL "${MIRROR_BASE_URL}/${GODOT_MACOS_ARCHIVE_NAME}" -o build/godot/godot-macos.zip; then
echo "Project mirror download failed, falling back to upstream."
curl -fsSL "${GODOT_UPSTREAM_BASE_URL}/${GODOT_MACOS_ARCHIVE_NAME}" -o build/godot/godot-macos.zip
fi
unzip -q -o build/godot/godot-macos.zip -d build/godot/extract
GODOT_BIN="$(find build/godot/extract -type f -path '*Godot.app/Contents/MacOS/Godot' | head -n 1)"
if [ -z "${GODOT_BIN}" ]; then
echo "Failed to locate extracted Godot macOS executable."
exit 1
fi
ARCH="$(uname -m)"
if [ "${ARCH}" = "arm64" ] || [ "${ARCH}" = "aarch64" ]; then
TARGET_BIN="src-tauri/bin/godot-aarch64-apple-darwin"
else
TARGET_BIN="src-tauri/bin/godot-x86_64-apple-darwin"
fi
cp "${GODOT_BIN}" "${TARGET_BIN}"
chmod +x "${TARGET_BIN}"
- name: Build desktop bundle
run: npm run tauri:build:mini
- name: Upload workflow artifacts
uses: actions/upload-artifact@v4
with:
name: release-${{ needs.ensure-release.outputs.tag_name }}-${{ matrix.platform_label }}
if-no-files-found: error
path: ${{ matrix.files }}
- name: Upload assets to GitHub release
uses: softprops/action-gh-release@v2
with:
tag_name: ${{ needs.ensure-release.outputs.tag_name }}
files: ${{ matrix.files }}
fail_on_unmatched_files: true
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
build-and-upload-android:
name: Build and Upload (android-apk)
needs: ensure-release
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v5
with:
ref: ${{ github.event_name == 'workflow_dispatch' && github.event.inputs.source_ref != '' && github.event.inputs.source_ref || github.event_name == 'workflow_dispatch' && github.event.inputs.tag || github.ref }}
fetch-depth: 0
lfs: false
- name: Setup Node.js
uses: actions/setup-node@v5
with:
node-version: "20"
cache: "npm"
- name: Setup Java 23.0.1
uses: actions/setup-java@v5
with:
distribution: temurin
java-version: "23.0.1"
- name: Setup Rust toolchain
uses: dtolnay/rust-toolchain@stable
- name: Setup Android SDK
uses: android-actions/setup-android@v3
- name: Install Android SDK dependencies
shell: bash
run: |
set -euo pipefail
yes | sdkmanager --licenses >/dev/null 2>&1 || true
sdkmanager \
"platform-tools" \
"platforms;android-36" \
"ndk;27.2.12479018"
- name: Install dependencies
run: npm ci
- name: Build Android universal APK
shell: bash
run: |
set -euo pipefail
NOTE_CONNECTION_TAURI_ANDROID_TARGET=universal npm run tauri:android:build
- name: Upload workflow artifacts
uses: actions/upload-artifact@v4
with:
name: release-${{ needs.ensure-release.outputs.tag_name }}-android-apk
if-no-files-found: error
path: |
src-tauri/gen/android/app/build/outputs/apk/**/*.apk
- name: Upload APK assets to GitHub release
uses: softprops/action-gh-release@v2
with:
tag_name: ${{ needs.ensure-release.outputs.tag_name }}
files: |
src-tauri/gen/android/app/build/outputs/apk/**/*.apk
fail_on_unmatched_files: true
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}