From 72485d44ffed77c01efaf6a4eb4a26e1861a71e9 Mon Sep 17 00:00:00 2001 From: Josh Knapp Date: Fri, 28 Aug 2026 18:53:24 -0700 Subject: [PATCH] Build each platform on its own native runner Splits the release workflow into linux, windows and macos jobs feeding a single publish job. The publish step is separate so parallel builds cannot race to create the release and fail on a duplicate tag. Windows now builds natively, so signAndEditExecutable no longer has to be disabled. That flag was suppressing rcedit, which is what embeds the icon and version resources -- the v1.0.0 exe shipped with the stock Electron icon as a result. Also adds an NSIS installer alongside the portable zip. macOS gains a dmg target, which cannot be produced off macOS. Builds stay unsigned pending a Developer ID, with CSC_IDENTITY_AUTO_DISCOVERY disabled so electron-builder builds unsigned instead of failing on a real Mac. Nothing cross-builds now; all three runners must be registered before tagging, since a job with no matching runner queues silently. Co-Authored-By: Claude Opus 5 (1M context) --- .gitea/workflows/release.yml | 141 +++++++++++++++++++++++++++++------ desktop-client/package.json | 18 +++-- 2 files changed, 133 insertions(+), 26 deletions(-) diff --git a/.gitea/workflows/release.yml b/.gitea/workflows/release.yml index 686b721..8b170ee 100644 --- a/.gitea/workflows/release.yml +++ b/.gitea/workflows/release.yml @@ -1,8 +1,19 @@ -name: Build AppImage +name: Build release artifacts -# Builds the Linux AppImage on a version tag and attaches it to a Gitea release. -# Tag must match the version in desktop-client/package.json, since electron-builder -# names the artifact from package.json (not from the tag). +# Builds each platform natively on a v* tag and publishes one Gitea release. +# +# Runners required. None are registered yet, and a job with no matching runner +# queues silently rather than failing, so register all three before tagging: +# ubuntu-latest Linux AppImage +# windows-latest Windows NSIS installer + portable zip +# macos-arm64 macOS dmg + zip, both architectures (M-series builds both) +# +# Nothing cross-builds any more: dmg requires macOS, and the Windows exe needs +# rcedit on Windows to get its icon and version resources. +# +# All output is unsigned. Once certificates exist: for macOS drop mac.identity +# from package.json and remove CSC_IDENTITY_AUTO_DISCOVERY; for Windows add the +# cert secrets. Until then users hit Gatekeeper and SmartScreen warnings. on: push: tags: @@ -10,18 +21,19 @@ on: workflow_dispatch: jobs: - appimage: + linux: runs-on: ubuntu-latest defaults: run: working-directory: desktop-client steps: - uses: actions/checkout@v4 - - uses: actions/setup-node@v4 with: node-version: '22' + # Runs only here rather than in all three jobs: it is a repo-wide check, + # and this job uses bash, which is not guaranteed on the Windows runner. - name: Verify tag matches package.json version if: startsWith(github.ref, 'refs/tags/v') run: | @@ -29,7 +41,7 @@ jobs: tag_version="${GITHUB_REF_NAME#v}" if [ "$pkg_version" != "$tag_version" ]; then echo "Tag $GITHUB_REF_NAME does not match package.json version $pkg_version." >&2 - echo "Bump package.json before tagging, or the AppImage will be misnamed." >&2 + echo "Bump package.json before tagging, or artifacts will be misnamed." >&2 exit 1 fi @@ -42,31 +54,118 @@ jobs: - name: Build AppImage run: npx electron-builder --linux AppImage --publish never - - name: Attach AppImage to release - if: startsWith(github.ref, 'refs/tags/v') + - uses: actions/upload-artifact@v3 + with: + name: linux + path: desktop-client/dist/*.AppImage + if-no-files-found: error + + windows: + runs-on: windows-latest + defaults: + run: + working-directory: desktop-client + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-node@v4 + with: + node-version: '22' + + - name: Install dependencies + run: npm ci + + - name: Run tests + run: npm test + + - name: Build installer and portable zip + run: npx electron-builder --win nsis zip --publish never + + - uses: actions/upload-artifact@v3 + with: + name: windows + path: | + desktop-client/dist/*.exe + desktop-client/dist/*-win-*.zip + if-no-files-found: error + + macos: + runs-on: macos-arm64 + defaults: + run: + working-directory: desktop-client + env: + # No Developer ID yet: stop electron-builder auto-discovering an identity, + # which otherwise fails the build on a real Mac rather than building unsigned. + CSC_IDENTITY_AUTO_DISCOVERY: 'false' + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-node@v4 + with: + node-version: '22' + + - name: Install dependencies + run: npm ci + + - name: Run tests + run: npm test + + - name: Build dmg and zip (both architectures) + run: npx electron-builder --mac --x64 --arm64 --publish never + + - uses: actions/upload-artifact@v3 + with: + name: macos + path: | + desktop-client/dist/*.dmg + desktop-client/dist/*-mac-*.zip + if-no-files-found: error + + publish: + # Separate job so only one process touches the release: parallel build jobs + # would race to create it and one would fail on the duplicate tag. + needs: [linux, windows, macos] + if: startsWith(github.ref, 'refs/tags/v') + runs-on: ubuntu-latest + steps: + - uses: actions/download-artifact@v3 + with: + path: artifacts + + - name: Create release and attach artifacts env: TOKEN: ${{ secrets.RELEASE_TOKEN }} API: ${{ github.server_url }}/api/v1/repos/${{ github.repository }} run: | set -euo pipefail - artifact=$(ls dist/*.AppImage | head -1) - echo "Publishing $artifact for $GITHUB_REF_NAME" + if [ -z "${TOKEN:-}" ]; then + echo "RELEASE_TOKEN secret is not set." >&2 + exit 1 + fi - # Reuse the release if the tag already has one, otherwise create it. release_id=$(curl -sf -H "Authorization: token $TOKEN" \ - "$API/releases/tags/$GITHUB_REF_NAME" | node -p \ - "JSON.parse(require('fs').readFileSync(0,'utf8')).id" 2>/dev/null || true) + "$API/releases/tags/$GITHUB_REF_NAME" \ + | node -p "JSON.parse(require('fs').readFileSync(0,'utf8')).id" 2>/dev/null || true) if [ -z "${release_id:-}" ]; then release_id=$(curl -sf -X POST -H "Authorization: token $TOKEN" \ -H "Content-Type: application/json" \ -d "{\"tag_name\":\"$GITHUB_REF_NAME\",\"name\":\"$GITHUB_REF_NAME\"}" \ - "$API/releases" | node -p \ - "JSON.parse(require('fs').readFileSync(0,'utf8')).id") + "$API/releases" \ + | node -p "JSON.parse(require('fs').readFileSync(0,'utf8')).id") fi - curl -sf -X POST -H "Authorization: token $TOKEN" \ - -F "attachment=@$artifact" \ - "$API/releases/$release_id/assets?name=$(basename "$artifact")" \ - -o /dev/null - echo "Attached $(basename "$artifact") to release $release_id" + found=0 + for f in $(find artifacts -type f \( -name '*.AppImage' -o -name '*.zip' -o -name '*.dmg' -o -name '*.exe' \)); do + name=$(basename "$f") + echo "Attaching $name" + curl -sf -X POST -H "Authorization: token $TOKEN" \ + -F "attachment=@$f" \ + "$API/releases/$release_id/assets?name=$name" -o /dev/null + found=$((found + 1)) + done + + if [ "$found" -eq 0 ]; then + echo "No artifacts found to attach." >&2 + exit 1 + fi + echo "Attached $found artifacts to release $release_id" diff --git a/desktop-client/package.json b/desktop-client/package.json index c09369e..0156af2 100644 --- a/desktop-client/package.json +++ b/desktop-client/package.json @@ -25,10 +25,11 @@ "main": "index.js" }, "win": { - "target": "zip", - "sign": false, - "signAndEditExecutable": false, - "certificateSubjectName": null + "target": [ + "nsis", + "zip" + ], + "icon": "build/icon.png" }, "linux": { "target": [ @@ -42,10 +43,17 @@ "artifactName": "${productName}-${version}-${os}-${arch}.${ext}", "mac": { "target": [ + "dmg", "zip" ], "icon": "build/icon.png", - "category": "public.app-category.music" + "category": "public.app-category.music", + "identity": null + }, + "nsis": { + "oneClick": false, + "allowToChangeInstallationDirectory": true, + "perMachine": false } }, "dependencies": { -- 2.52.0