Skip to content

chore(release): 0.2.24 — auto-update works + Windows CI unblocked #36

chore(release): 0.2.24 — auto-update works + Windows CI unblocked

chore(release): 0.2.24 — auto-update works + Windows CI unblocked #36

Workflow file for this run

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 }}