name: Publish Release on: release: types: [published] workflow_dispatch: inputs: tag_name: description: Existing release tag to rebuild and publish required: true type: string permissions: contents: write actions: write concurrency: group: release-${{ github.event.release.tag_name || github.event.inputs.tag_name || github.run_id }} cancel-in-progress: false jobs: prepare-release: name: Prepare release runs-on: ubuntu-latest outputs: tag_name: ${{ steps.prepare.outputs.tag_name }} package_version: ${{ steps.prepare.outputs.package_version }} source_sha: ${{ steps.prepare.outputs.source_sha }} prerelease: ${{ steps.prepare.outputs.prerelease }} steps: - name: Checkout release source uses: actions/checkout@v4 with: ref: ${{ github.event_name == 'release' && github.event.release.tag_name || github.event.inputs.tag_name }} fetch-depth: 0 - name: Validate release tag id: prepare shell: bash run: | set -euo pipefail if [ "${{ github.event_name }}" = "release" ]; then TAG_NAME="${{ github.event.release.tag_name }}" IS_PRERELEASE="${{ github.event.release.prerelease }}" else TAG_NAME="${{ github.event.inputs.tag_name }}" IS_PRERELEASE="false" fi if [ -z "$TAG_NAME" ]; then echo "Release tag is required." exit 1 fi TAG_VERSION="${TAG_NAME#v}" PACKAGE_VERSION=$(node -p "require('./package.json').version") if [ "$PACKAGE_VERSION" != "$TAG_VERSION" ]; then echo "Release tag version ($TAG_VERSION) does not match package.json version ($PACKAGE_VERSION)." exit 1 fi if [ "${{ github.event_name }}" = "workflow_dispatch" ]; then gh release view "$TAG_NAME" --repo "${{ github.repository }}" >/dev/null 2>&1 || \ gh release create "$TAG_NAME" --repo "${{ github.repository }}" --verify-tag --draft --title "$TAG_NAME" fi echo "tag_name=$TAG_NAME" >> "$GITHUB_OUTPUT" echo "package_version=$PACKAGE_VERSION" >> "$GITHUB_OUTPUT" echo "source_sha=$(git rev-parse HEAD)" >> "$GITHUB_OUTPUT" echo "prerelease=$IS_PRERELEASE" >> "$GITHUB_OUTPUT" env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} build-macos-x64: name: Build macOS x64 needs: prepare-release runs-on: macos-15-intel env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} APPLE_ID: ${{ secrets.APPLE_ID }} APPLE_APP_SPECIFIC_PASSWORD: ${{ secrets.APPLE_APP_SPECIFIC_PASSWORD }} APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }} steps: - name: Checkout code uses: actions/checkout@v4 with: ref: ${{ needs.prepare-release.outputs.source_sha }} - name: Setup Node.js uses: actions/setup-node@v4 with: node-version: 22 cache: npm - name: Setup Python uses: actions/setup-python@v5 with: python-version: '3.11' - name: Validate macOS signing secrets shell: bash env: APPLE_SIGNING_CERTIFICATE_P12_BASE64: ${{ secrets.APPLE_SIGNING_CERTIFICATE_P12_BASE64 }} APPLE_SIGNING_CERTIFICATE_PASSWORD: ${{ secrets.APPLE_SIGNING_CERTIFICATE_PASSWORD }} run: | set -euo pipefail for name in APPLE_SIGNING_CERTIFICATE_P12_BASE64 APPLE_SIGNING_CERTIFICATE_PASSWORD APPLE_ID APPLE_APP_SPECIFIC_PASSWORD APPLE_TEAM_ID; do if [ -z "${!name:-}" ]; then echo "Missing required macOS release secret: $name" exit 1 fi done - name: Prepare macOS signing certificate shell: bash env: APPLE_SIGNING_CERTIFICATE_P12_BASE64: ${{ secrets.APPLE_SIGNING_CERTIFICATE_P12_BASE64 }} APPLE_SIGNING_CERTIFICATE_PASSWORD: ${{ secrets.APPLE_SIGNING_CERTIFICATE_PASSWORD }} run: | set -euo pipefail CERT_PATH="$RUNNER_TEMP/apple-signing-cert.p12" echo "$APPLE_SIGNING_CERTIFICATE_P12_BASE64" | base64 --decode > "$CERT_PATH" { echo "CSC_LINK=$CERT_PATH" echo "CSC_KEY_PASSWORD=$APPLE_SIGNING_CERTIFICATE_PASSWORD" } >> "$GITHUB_ENV" - name: Install dependencies run: npm ci --ignore-scripts - name: Install app dependencies run: npx electron-builder install-app-deps - name: Build native macOS helpers run: npm run build:native-helpers - name: Build macOS x64 run: | npx tsc npx vite build npx electron-builder --mac dmg zip --x64 --publish never - name: Upload macOS x64 artifacts uses: actions/upload-artifact@v4 with: name: macos-x64-release path: | release/*.dmg release/*.zip release/*.blockmap release/latest-mac.yml if-no-files-found: error build-macos-arm64: name: Build macOS arm64 needs: prepare-release runs-on: macos-14 env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} APPLE_ID: ${{ secrets.APPLE_ID }} APPLE_APP_SPECIFIC_PASSWORD: ${{ secrets.APPLE_APP_SPECIFIC_PASSWORD }} APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }} steps: - name: Checkout code uses: actions/checkout@v4 with: ref: ${{ needs.prepare-release.outputs.source_sha }} - name: Setup Node.js uses: actions/setup-node@v4 with: node-version: 22 cache: npm - name: Setup Python uses: actions/setup-python@v5 with: python-version: '3.11' - name: Validate macOS signing secrets shell: bash env: APPLE_SIGNING_CERTIFICATE_P12_BASE64: ${{ secrets.APPLE_SIGNING_CERTIFICATE_P12_BASE64 }} APPLE_SIGNING_CERTIFICATE_PASSWORD: ${{ secrets.APPLE_SIGNING_CERTIFICATE_PASSWORD }} run: | set -euo pipefail for name in APPLE_SIGNING_CERTIFICATE_P12_BASE64 APPLE_SIGNING_CERTIFICATE_PASSWORD APPLE_ID APPLE_APP_SPECIFIC_PASSWORD APPLE_TEAM_ID; do if [ -z "${!name:-}" ]; then echo "Missing required macOS release secret: $name" exit 1 fi done - name: Prepare macOS signing certificate shell: bash env: APPLE_SIGNING_CERTIFICATE_P12_BASE64: ${{ secrets.APPLE_SIGNING_CERTIFICATE_P12_BASE64 }} APPLE_SIGNING_CERTIFICATE_PASSWORD: ${{ secrets.APPLE_SIGNING_CERTIFICATE_PASSWORD }} run: | set -euo pipefail CERT_PATH="$RUNNER_TEMP/apple-signing-cert.p12" echo "$APPLE_SIGNING_CERTIFICATE_P12_BASE64" | base64 --decode > "$CERT_PATH" { echo "CSC_LINK=$CERT_PATH" echo "CSC_KEY_PASSWORD=$APPLE_SIGNING_CERTIFICATE_PASSWORD" } >> "$GITHUB_ENV" - name: Install dependencies run: npm ci --ignore-scripts - name: Install app dependencies run: npx electron-builder install-app-deps - name: Build native macOS helpers run: npm run build:native-helpers - name: Build macOS arm64 run: | npx tsc npx vite build npx electron-builder --mac dmg zip --arm64 --publish never - name: Upload macOS arm64 artifacts uses: actions/upload-artifact@v4 with: name: macos-arm64-release path: | release/*.dmg release/*.zip release/*.blockmap release/latest-mac.yml if-no-files-found: error merge-macos-update-metadata: name: Merge macOS update metadata needs: - prepare-release - build-macos-x64 - build-macos-arm64 runs-on: ubuntu-latest steps: - name: Checkout workflow actions uses: actions/checkout@v4 with: sparse-checkout: .github/actions ref: ${{ needs.prepare-release.outputs.source_sha }} - name: Download macOS x64 artifacts uses: actions/download-artifact@v4 with: name: macos-x64-release path: release-assets/macos-x64 - name: Download macOS arm64 artifacts uses: actions/download-artifact@v4 with: name: macos-arm64-release path: release-assets/macos-arm64 - name: Merge latest-mac.yml uses: ./.github/actions/merge-macos-metadata with: x64-directory: release-assets/macos-x64 arm64-directory: release-assets/macos-arm64 output-file: release-assets/merged/latest-mac.yml - name: Upload merged latest-mac.yml artifact uses: actions/upload-artifact@v4 with: name: macos-merged-metadata path: release-assets/merged/latest-mac.yml if-no-files-found: error build-windows-x64: name: Build Windows x64 needs: prepare-release runs-on: windows-latest env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} steps: - name: Checkout code uses: actions/checkout@v4 with: ref: ${{ needs.prepare-release.outputs.source_sha }} - name: Setup Node.js uses: actions/setup-node@v4 with: node-version: 22 cache: npm - name: Setup Python uses: actions/setup-python@v5 with: python-version: '3.11' - name: Configure Windows signing shell: pwsh env: WINDOWS_SIGNING_CERTIFICATE_P12_BASE64: ${{ secrets.WINDOWS_SIGNING_CERTIFICATE_P12_BASE64 }} WINDOWS_SIGNING_CERTIFICATE_PASSWORD: ${{ secrets.WINDOWS_SIGNING_CERTIFICATE_PASSWORD }} run: | if ([string]::IsNullOrWhiteSpace($env:WINDOWS_SIGNING_CERTIFICATE_P12_BASE64) -or [string]::IsNullOrWhiteSpace($env:WINDOWS_SIGNING_CERTIFICATE_PASSWORD)) { Write-Warning 'Windows signing secrets are missing. Building an unsigned installer.' Add-Content -Path $env:GITHUB_ENV -Value 'CSC_IDENTITY_AUTO_DISCOVERY=false' exit 0 } $certPath = Join-Path $env:RUNNER_TEMP 'windows-signing-cert.p12' [IO.File]::WriteAllBytes($certPath, [Convert]::FromBase64String($env:WINDOWS_SIGNING_CERTIFICATE_P12_BASE64)) Add-Content -Path $env:GITHUB_ENV -Value "WIN_CSC_LINK=$certPath" Add-Content -Path $env:GITHUB_ENV -Value "WIN_CSC_KEY_PASSWORD=$env:WINDOWS_SIGNING_CERTIFICATE_PASSWORD" - name: Install dependencies run: npm ci --ignore-scripts - name: Remove stale uiohook build output shell: pwsh run: | if (Test-Path node_modules/uiohook-napi/build) { Remove-Item node_modules/uiohook-napi/build -Recurse -Force } - name: Install app dependencies run: npx electron-builder install-app-deps - name: Build Windows x64 shell: pwsh run: | npm run build:platform-native-helpers npx tsc npx vite build npx electron-builder --win nsis --x64 --publish never - name: Upload Windows x64 artifacts uses: actions/upload-artifact@v4 with: name: windows-x64-release path: | release/*.exe release/*.blockmap release/latest*.yml if-no-files-found: error build-linux-x64: name: Build Linux x64 needs: prepare-release runs-on: ubuntu-latest env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} steps: - name: Checkout code uses: actions/checkout@v4 with: ref: ${{ needs.prepare-release.outputs.source_sha }} - name: Setup Node.js uses: actions/setup-node@v4 with: node-version: 22 cache: npm - name: Setup Python uses: actions/setup-python@v5 with: python-version: '3.11' - name: Install Linux native build dependencies run: | sudo apt-get update sudo apt-get install -y libx11-dev libxt-dev libxtst-dev libxkbfile-dev libxi-dev libxrandr-dev libxinerama-dev - name: Install dependencies run: npm ci --ignore-scripts - name: Remove stale uiohook build output run: rm -rf node_modules/uiohook-napi/build - name: Install app dependencies run: npx electron-builder install-app-deps - name: Build Linux x64 run: | export CSC_IDENTITY_AUTO_DISCOVERY=false npm run build:platform-native-helpers npx tsc npx vite build npx electron-builder --linux AppImage --x64 --publish never - name: Upload Linux x64 artifacts uses: actions/upload-artifact@v4 with: name: linux-x64-release path: | release/*.AppImage release/*.blockmap release/latest-linux.yml if-no-files-found: error publish-release-assets: name: Publish release assets needs: - prepare-release - build-macos-x64 - build-macos-arm64 - merge-macos-update-metadata - build-windows-x64 - build-linux-x64 runs-on: ubuntu-latest steps: - name: Download macOS x64 artifacts uses: actions/download-artifact@v4 with: name: macos-x64-release path: release-assets/macos-x64 - name: Download macOS arm64 artifacts uses: actions/download-artifact@v4 with: name: macos-arm64-release path: release-assets/macos-arm64 - name: Download merged macOS metadata uses: actions/download-artifact@v4 with: name: macos-merged-metadata path: release-assets/macos-merged - name: Download Windows x64 artifacts uses: actions/download-artifact@v4 with: name: windows-x64-release path: release-assets/windows-x64 - name: Download Linux x64 artifacts uses: actions/download-artifact@v4 with: name: linux-x64-release path: release-assets/linux-x64 - name: Upload release assets to GitHub shell: bash run: | set -euo pipefail shopt -s nullglob assets=( release-assets/macos-x64/*.dmg release-assets/macos-x64/*.zip release-assets/macos-x64/*.blockmap release-assets/macos-arm64/*.dmg release-assets/macos-arm64/*.zip release-assets/macos-arm64/*.blockmap release-assets/macos-merged/latest-mac.yml release-assets/windows-x64/*.exe release-assets/windows-x64/*.blockmap release-assets/windows-x64/latest*.yml release-assets/linux-x64/*.AppImage release-assets/linux-x64/*.blockmap release-assets/linux-x64/latest-linux.yml ) if [ ${#assets[@]} -eq 0 ]; then echo "No release assets found to upload." exit 1 fi gh release upload "${{ needs.prepare-release.outputs.tag_name }}" \ "${assets[@]}" \ --repo "${{ github.repository }}" \ --clobber env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} trigger-homebrew-update: name: Trigger Homebrew tap update needs: - prepare-release - publish-release-assets runs-on: ubuntu-latest if: needs.prepare-release.outputs.prerelease != 'true' steps: - name: Dispatch Homebrew tap workflow shell: bash run: | gh workflow run homebrew-tap.yml \ --repo "${{ github.repository }}" \ -f tag="${{ needs.prepare-release.outputs.tag_name }}" env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}