chore(release): 0.2.24 — auto-update works + Windows CI unblocked #36
Workflow file for this run
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: Build & Release | |
| on: | |
| push: | |
| tags: | |
| - 'v*' | |
| workflow_dispatch: | |
| permissions: | |
| contents: write | |
| jobs: | |
| build: | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| include: | |
| - os: windows-2022 | |
| target: win | |
| daemon_rid: win-x64 | |
| npm_script: dist | |
| upload_glob: | | |
| src/frontend/release/*.exe | |
| src/frontend/release/*.blockmap | |
| src/frontend/release/latest.yml | |
| - os: macos-14 | |
| target: mac | |
| daemon_rid: osx-arm64 | |
| npm_script: dist:mac | |
| upload_glob: | | |
| src/frontend/release/*.dmg | |
| src/frontend/release/*.zip | |
| src/frontend/release/latest-mac.yml | |
| - os: ubuntu-24.04 | |
| target: linux | |
| daemon_rid: linux-x64 | |
| npm_script: dist:linux | |
| upload_glob: | | |
| src/frontend/release/*.AppImage | |
| src/frontend/release/latest-linux.yml | |
| runs-on: ${{ matrix.os }} | |
| # Full release build (daemon publish + frontend bundle + installer pack) | |
| # runs ~10m per matrix leg. Bumped to 90m because macOS notarization | |
| # via @electron/notarize can occasionally hang 30-40 min when Apple's | |
| # notary backend queue is saturated — v0.2.15 was cancelled at 40m | |
| # after notarytool had been polling for 38 min straight. Win + Linux | |
| # legs still finish in <10m so this only extends worst-case mac. | |
| timeout-minutes: 90 | |
| steps: | |
| - uses: actions/checkout@v6 | |
| - name: Setup .NET 9 | |
| uses: actions/setup-dotnet@v5 | |
| with: | |
| dotnet-version: '9.0.x' | |
| - name: Setup Node.js | |
| uses: actions/setup-node@v6 | |
| with: | |
| node-version: '22' | |
| - name: Generate macOS .icns icon | |
| if: matrix.target == 'mac' | |
| run: | | |
| if [ ! -f src/frontend/build/icon.icns ]; then | |
| sips -s format icns src/frontend/build/icon.png --out src/frontend/build/icon.icns | |
| fi | |
| shell: bash | |
| - name: Build daemon | |
| # SENTRY_DSN env flows into the published binary's runtime env via | |
| # electron-builder's extraResources bundle; no DSN literal is | |
| # written into the C#. Daemon reads SENTRY_DSN at start-up. | |
| env: | |
| SENTRY_DSN: ${{ secrets.SENTRY_DSN_BACKEND }} | |
| run: dotnet publish src/daemon/NKS.WebDevConsole.Daemon/NKS.WebDevConsole.Daemon.csproj -c Release -r ${{ matrix.daemon_rid }} --self-contained -o src/frontend/resources/daemon | |
| - name: Build solution (plugins + tests) | |
| run: dotnet build WebDevConsole.sln -c Release | |
| - name: Install frontend dependencies | |
| working-directory: src/frontend | |
| run: npm ci | |
| - name: Decode Apple API key for notarization | |
| if: matrix.target == 'mac' && env.APPLE_API_KEY_B64 != '' | |
| env: | |
| APPLE_API_KEY_B64: ${{ secrets.APPLE_API_KEY_B64 }} | |
| run: | | |
| mkdir -p ~/.appstoreconnect/private_keys | |
| echo "$APPLE_API_KEY_B64" | base64 --decode > ~/.appstoreconnect/private_keys/AuthKey_${{ secrets.APPLE_API_KEY_ID }}.p8 | |
| chmod 600 ~/.appstoreconnect/private_keys/AuthKey_${{ secrets.APPLE_API_KEY_ID }}.p8 | |
| echo "APPLE_API_KEY=$HOME/.appstoreconnect/private_keys/AuthKey_${{ secrets.APPLE_API_KEY_ID }}.p8" >> $GITHUB_ENV | |
| shell: bash | |
| - name: Build Electron app | |
| working-directory: src/frontend | |
| run: npm run ${{ matrix.npm_script }} | |
| env: | |
| # Sentry DSNs are set from GitHub Secrets (never in git). Vite | |
| # inlines these at build time via import.meta.env — the daemon | |
| # reads its own from SENTRY_DSN at launch (Electron passes it | |
| # through to spawn()'s env). Leaving a secret blank disables | |
| # Sentry for that surface without a code change. | |
| SENTRY_DSN: ${{ secrets.SENTRY_DSN_FRONTEND }} | |
| SENTRY_DSN_FRONTEND: ${{ secrets.SENTRY_DSN_FRONTEND }} | |
| SENTRY_DSN_BACKEND: ${{ secrets.SENTRY_DSN_BACKEND }} | |
| SENTRY_ENVIRONMENT: production | |
| GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| # stage-plugins.mjs reads GITHUB_TOKEN to auth GitHub API calls | |
| # when fetching plugin release zips. Unauthenticated is 60/hr per | |
| # IP — macOS runners share NAT and burn through that fast, which | |
| # caused v0.2.5's DMG to ship without any plugins. 5000/hr when | |
| # authenticated covers it. | |
| GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| # Skip auto-publish from electron-builder; we publish via softprops/action-gh-release below. | |
| EP_PRE_RELEASE: 'false' | |
| # macOS code-signing: Developer ID Application cert (Jakub Cerny, | |
| # Team DG3SLRLF7A) issued 2026-04-23, expires 2031-04-24. CSC_LINK | |
| # is a base64-encoded .p12 set via `gh secret set`; electron-builder | |
| # decodes + installs into a temp keychain on the mac runner. | |
| # Windows/Linux legs see the secrets too but ignore them (no mac | |
| # identity match). Notarization NOT enabled yet — needs | |
| # APPLE_APP_SPECIFIC_PASSWORD from appleid.apple.com; today's | |
| # signing is enough to unlock Touch ID in osascript admin dialogs. | |
| CSC_LINK: ${{ secrets.CSC_LINK }} | |
| CSC_KEY_PASSWORD: ${{ secrets.CSC_KEY_PASSWORD }} | |
| APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }} | |
| # Notarization via App Store Connect API key (no app-specific | |
| # password required). APPLE_API_KEY path is exported by the | |
| # "Decode Apple API key" step above, only on mac target. | |
| APPLE_API_KEY_ID: ${{ secrets.APPLE_API_KEY_ID }} | |
| APPLE_API_ISSUER: ${{ secrets.APPLE_API_ISSUER }} | |
| - name: Verify release artifacts | |
| run: node scripts/verify-electron-release.mjs --dir src/frontend/release --target ${{ matrix.target }} | |
| shell: bash | |
| # Skipped on CI: daemon app.manifest is requireAdministrator so the | |
| # packaged Electron app triggers a UAC prompt at launch. Headless | |
| # GitHub Actions runners can't dismiss UAC — the bootstrap stalls | |
| # and the step times out. Developers run this manually on elevated | |
| # shells before shipping. Release tag workflow still builds the | |
| # signed installer, which is what ships to users. | |
| # - name: Smoke packaged Electron runtime | |
| # if: matrix.target == 'win' | |
| # working-directory: src/frontend | |
| # run: npm run smoke:packaged:win | |
| - name: Upload artifacts | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: release-${{ matrix.target }} | |
| path: src/frontend/release/** | |
| # Upload release assets per-platform. Each matrix job publishes only | |
| # the extensions it actually produces so the "Pattern does not match | |
| # any files" log noise goes away. generate_release_notes is gated | |
| # to the Windows job (first in the matrix) so the 3 parallel jobs | |
| # don't race each other on the same API call — which produced | |
| # HttpError: Server Error on the v0.2.0 tag push. | |
| - name: Publish to GitHub Release | |
| if: startsWith(github.ref, 'refs/tags/v') | |
| uses: softprops/action-gh-release@v3 | |
| with: | |
| fail_on_unmatched_files: false | |
| draft: false | |
| prerelease: false | |
| generate_release_notes: ${{ matrix.target == 'win' }} | |
| files: ${{ matrix.upload_glob }} | |
| env: | |
| GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} |