Skip to content

publish desktop v2.0.0 #107

publish desktop v2.0.0

publish desktop v2.0.0 #107

name: Publish Desktop App
run-name: "${{ format('publish desktop {0}', github.event_name == 'workflow_dispatch' && inputs.tag || github.ref_name) }}"
# Triggered manually or by publish-executor-package.yml after a CLI release
# lands. Builds Electron distributables for mac/win/linux with the compiled
# executor CLI bundled in `resources/executor/`, then attaches them to the GitHub
# release matching the tag so electron-updater can pick them up.
on:
workflow_dispatch:
inputs:
tag:
description: Git tag to publish (e.g. v1.4.1)
required: true
type: string
permissions:
contents: read
concurrency:
group: publish-desktop-${{ github.ref }}
cancel-in-progress: false
jobs:
build:
permissions:
contents: read
strategy:
fail-fast: false
matrix:
include:
# smoke: run the compiled-sidecar smoke test on legs whose target
# matches the runner (the mac x64 leg cross-compiles on an arm64
# runner, so its binary can't be executed natively there).
- os: macos-latest
arch: arm64
platform: mac
bun-target: bun-darwin-arm64
smoke: true
- os: macos-latest
arch: x64
platform: mac
bun-target: bun-darwin-x64
smoke: false
- os: ubuntu-latest
arch: x64
platform: linux
bun-target: bun-linux-x64
smoke: true
- os: windows-latest
arch: x64
platform: win
bun-target: bun-windows-x64
smoke: true
runs-on: ${{ matrix.os }}
# A healthy leg finishes in ~10 minutes. Without a deadline, a hung step
# wedges the whole publish queue: the concurrency group below queues later
# runs behind this one without cancelling it (v1.5.7's mac leg sat 4 hours
# on a bun install that errored without exiting, holding v1.5.8 pending).
timeout-minutes: 45
steps:
- name: Checkout
uses: actions/checkout@v4
with:
persist-credentials: false
- name: Setup Bun
uses: oven-sh/setup-bun@v2
with:
bun-version: 1.3.11
- name: Validate release tag
env:
RAW_RELEASE_TAG: ${{ inputs.tag }}
run: bun run scripts/validate-release-ref.ts --tag-env RAW_RELEASE_TAG --write-env RELEASE_TAG
- name: Checkout release tag
env:
GH_TOKEN: ${{ secrets.RELEASE_PAT || github.token }}
shell: bash
run: |
auth_remote="https://x-access-token:${GH_TOKEN}@github.com/${GITHUB_REPOSITORY}.git"
git fetch --force --tags "$auth_remote" "refs/tags/$RELEASE_TAG:refs/tags/$RELEASE_TAG"
git checkout --detach "$RELEASE_TAG"
- name: Setup Node
uses: actions/setup-node@v4
with:
node-version: 24
- name: Install dependencies
run: bun install --frozen-lockfile
- name: Build web app
run: bun run --filter @executor-js/local build
- name: Build bundled executor
env:
BUN_TARGET: ${{ matrix.bun-target }}
run: bun ./scripts/build-sidecar.ts
working-directory: apps/desktop
# Gate the release on the compiled binary actually booting. v1.5.0/.1
# shipped local-server binaries that died on launch (missing libsql native
# binding) — a regression dev mode can't catch because `bun run` resolves
# node_modules that `bun build --compile` does not bundle.
- name: Smoke test bundled executor
if: matrix.smoke
run: bun run test:smoke
working-directory: apps/desktop
- name: Build Electron main/preload/renderer
env:
# Crash-report DSN baked into the main bundle (see the define in
# electron.vite.config.ts). Unset (forks, local) → crash reporting
# is compiled out and dumps stay local.
DESKTOP_SENTRY_DSN: ${{ vars.DESKTOP_SENTRY_DSN }}
run: bunx --bun electron-vite build
working-directory: apps/desktop
- name: Stage Apple API key (mac signing + notarization)
if: matrix.platform == 'mac' && env.APPLE_API_KEY != ''
env:
APPLE_API_KEY: ${{ secrets.APPLE_API_KEY }}
run: |
mkdir -p "${RUNNER_TEMP}/private_keys"
printf '%s' "$APPLE_API_KEY" > "${RUNNER_TEMP}/private_keys/AuthKey.p8"
echo "APPLE_API_KEY_PATH=${RUNNER_TEMP}/private_keys/AuthKey.p8" >> "$GITHUB_ENV"
- name: Build desktop distributables
env:
# electron-builder reads GH_TOKEN for the publish step. We use
# --publish never here and attach assets explicitly in the release
# job so we don't fight electron-updater's metadata expectations.
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
# Apple signing + notarization. electron-builder picks these up:
# CSC_LINK / CSC_KEY_PASSWORD → import the Developer ID Application
# cert (.p12, base64) into a temp keychain for codesign
# APPLE_API_KEY (file path) / APPLE_API_KEY_ID / APPLE_API_ISSUER
# → notarytool authentication for the notarization upload
# When the secrets aren't set (forks, local), electron-builder
# silently produces an unsigned build.
CSC_LINK: ${{ secrets.CSC_LINK }}
CSC_KEY_PASSWORD: ${{ secrets.CSC_KEY_PASSWORD }}
APPLE_API_KEY: ${{ env.APPLE_API_KEY_PATH }}
APPLE_API_KEY_ID: ${{ secrets.APPLE_API_KEY_ID }}
APPLE_API_ISSUER: ${{ secrets.APPLE_API_ISSUER }}
run: bunx --bun electron-builder --${{ matrix.platform }} --${{ matrix.arch }} --publish never --config electron-builder.config.ts
working-directory: apps/desktop
# The two mac legs each emit a latest-mac.yml listing only their own
# arch. Rename per-arch here; the release job merges them back into the
# single latest-mac.yml electron-updater clients fetch. Without this,
# merge-multiple in the release job lets one arch clobber the other and
# every Mac gets pointed at whichever leg uploaded last.
- name: Rename mac update manifest per arch
if: matrix.platform == 'mac'
shell: bash
run: mv apps/desktop/dist/latest-mac.yml "apps/desktop/dist/latest-mac-${{ matrix.arch }}.yml"
- name: Upload artifacts
uses: actions/upload-artifact@v4
with:
name: desktop-${{ matrix.platform }}-${{ matrix.arch }}
path: |
apps/desktop/dist/*.dmg
apps/desktop/dist/*.zip
apps/desktop/dist/*.exe
apps/desktop/dist/*.AppImage
apps/desktop/dist/*.deb
apps/desktop/dist/*.rpm
apps/desktop/dist/latest*.yml
if-no-files-found: warn
release:
needs: build
runs-on: ubuntu-latest
permissions:
contents: write
steps:
- name: Checkout validation script
uses: actions/checkout@v4
with:
persist-credentials: false
- name: Setup Bun
uses: oven-sh/setup-bun@v2
with:
bun-version: 1.3.11
- name: Validate release tag
env:
RAW_RELEASE_TAG: ${{ inputs.tag }}
run: bun run scripts/validate-release-ref.ts --tag-env RAW_RELEASE_TAG --write-env RELEASE_TAG
- name: Download all artifacts
uses: actions/download-artifact@v4
with:
path: artifacts
merge-multiple: true
- name: Merge mac update manifests
run: |
set -euo pipefail
bun scripts/merge-latest-mac-yml.ts \
artifacts/latest-mac-x64.yml \
artifacts/latest-mac-arm64.yml \
artifacts/latest-mac.yml
rm artifacts/latest-mac-x64.yml artifacts/latest-mac-arm64.yml
- name: Upload to GitHub Release
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
set -euo pipefail
while IFS= read -r file; do
echo "Uploading: $file"
gh release upload "$RELEASE_TAG" "$file" --repo "$GITHUB_REPOSITORY" --clobber
done < <(find artifacts -type f \
\( -name "*.dmg" -o -name "*.zip" -o -name "*.exe" \
-o -name "*.AppImage" -o -name "*.deb" -o -name "*.rpm" \
-o -name "latest*.yml" \))
# Flip draft → published only after every desktop asset is uploaded —
# this is the atomic point where the new tag becomes "latest".
- name: Promote release (draft → published)
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: gh release edit "$RELEASE_TAG" --draft=false --repo "$GITHUB_REPOSITORY"