Skip to content

publish desktop v1.6.7 #144

publish desktop v1.6.7

publish desktop v1.6.7 #144

Workflow file for this run

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
dry_run:
description: Build all distributables but do not touch the GitHub release
required: false
default: false
type: boolean
permissions:
contents: read
concurrency:
group: publish-desktop-${{ github.ref }}
cancel-in-progress: false
# CI is not a user. Without this, executor's own product telemetry reports to the
# production PostHog project from every e2e shard: the analytics layer
# (integration_added, execution_completed, artifact_*) and the
# integrations-registry fetch, which lands as a `hit` event. Each shard is a
# fresh container, so it mints a fresh anonymous id and arrives on a fresh
# runner IP — inflating machine and user counts by roughly an order of
# magnitude. Both opt-outs are the packages' documented CI/test hooks.
env:
DO_NOT_TRACK: "1"
EXECUTOR_DISABLE_ANALYTICS: "1"
EXECUTOR_DISABLE_INTEGRATIONS_FETCH: "1"
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: blacksmith-6vcpu-macos-latest
arch: arm64
platform: mac
bun-target: bun-darwin-arm64
smoke: true
- os: blacksmith-6vcpu-macos-latest
arch: x64
platform: mac
bun-target: bun-darwin-x64
smoke: false
- os: blacksmith-4vcpu-ubuntu-2404
arch: x64
platform: linux
bun-target: bun-linux-x64
smoke: true
- os: blacksmith-8vcpu-windows-2025
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: Cache Bun package cache
uses: actions/cache@v4
with:
path: ~/.bun/install/cache
key: bun-cache-${{ runner.os }}-${{ runner.arch }}-${{ hashFiles('bun.lock') }}
restore-keys: bun-cache-${{ runner.os }}-${{ runner.arch }}-
# Electron's postinstall downloads a ~120MB runtime zip into this cache,
# and electron-builder pulls its packaging tools (nsis, winCodeSign,
# AppImage) into a sibling cache on first use. Both are keyed only by
# version, so a lockfile-keyed cache stays warm across most releases.
- name: Cache Electron downloads
uses: actions/cache@v4
with:
path: |
~/Library/Caches/electron
~/Library/Caches/electron-builder
~/.cache/electron
~/.cache/electron-builder
~/AppData/Local/electron/Cache
~/AppData/Local/electron-builder/Cache
key: electron-cache-${{ runner.os }}-${{ runner.arch }}-${{ hashFiles('bun.lock') }}
restore-keys: electron-cache-${{ runner.os }}-${{ runner.arch }}-
- name: Install dependencies
run: bun install --frozen-lockfile
# apps/desktop/build/ holds committed static inputs (icon, entitlements)
# that no module imports, so a sweep can delete them without breaking a
# build or a test. v1.6.1 was tagged without them: both mac legs ran ~8
# minutes, then died inside codesign with "build/entitlements.mac.plist:
# cannot read entitlement data" against an arbitrary locale.pak, while
# the missing icon only ever produced a "default Electron icon is used"
# notice. Check the tag's own tree here so the failure names the cause.
- name: Check desktop build resources
run: bun ./scripts/check-build-resources.ts
working-directory: apps/desktop
- 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 / CSC_KEY_PASSWORD MUST stay scoped to the mac legs.
# Despite the name used here, CSC_LINK is electron-builder's
# cross-platform certificate variable: WinPackager falls back to it
# whenever WIN_CSC_LINK is unset, via
# platformPackager.getCscLink("WIN_CSC_LINK"). Handing the Apple
# Developer ID .p12 to the windows leg made electron-builder read its
# subject and write `publisherName: Developer ID Application: ...`
# into app-update.yml, so every Windows client rejected the update it
# had just downloaded with "New version is not signed by the
# application owner". Empty string is the documented "no certificate"
# value — WindowsSignToolManager treats `cscLink === ""` exactly like
# unset. The APPLE_* vars below need no such guard: they are only
# read by notarytool on darwin and are inert elsewhere.
CSC_LINK: ${{ matrix.platform == 'mac' && secrets.CSC_LINK || '' }}
CSC_KEY_PASSWORD: ${{ matrix.platform == 'mac' && 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/*.blockmap
apps/desktop/dist/latest*.yml
if-no-files-found: warn
release:
needs: build
# dry_run builds and uploads workflow artifacts but never touches the
# GitHub release — used to rehearse workflow changes against a real tag.
if: ${{ !inputs.dry_run }}
runs-on: blacksmith-4vcpu-ubuntu-2404
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
# Parallel uploads; xargs exits non-zero if any single upload fails,
# which keeps the promote step below from running on a partial set.
find artifacts -type f \
\( -name "*.dmg" -o -name "*.zip" -o -name "*.exe" \
-o -name "*.AppImage" -o -name "*.deb" -o -name "*.rpm" \
-o -name "*.blockmap" \
-o -name "latest*.yml" \) -print0 \
| xargs -0 -n1 -P8 -I{} sh -c \
'echo "Uploading: $1"; exec gh release upload "$RELEASE_TAG" "$1" --repo "$GITHUB_REPOSITORY" --clobber' _ {}
# 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"