agent-orchestrator/.github/workflows/frontend-release.yml

250 lines
11 KiB
YAML

name: Desktop release
# Builds and publishes the Electron desktop app via electron-forge.
# Generates a GitHub Release with installers + update manifests, then uploads
# stable, version-free asset aliases so `ao start`'s constant
# releases/latest/download/<name> URL resolves (spec §8, §11.4).
#
# Each target OS builds on its own runner so the bundled `ao` daemon is compiled
# natively for that platform. build-daemon.mjs keys the binary off the build
# host's platform, so cross-OS packaging (e.g. building the Windows installer on
# macOS) would ship a non-Windows binary named `ao` and the app could not launch
# the daemon (issues #235/#256). The per-OS matrix keeps host == target, and the
# Linux daemon is built on the Linux runner (AgentWrapper/agent-orchestrator#2191).
#
# ⚠️ Until macOS code signing + notarization secrets are configured (see
# frontend/docs/desktop-release.md), published builds are UNSIGNED and will
# NOT auto-update on macOS. The workflow still produces installable artifacts.
#
# ── Cutting a FORK test release (dev loop) ────────────────────────────────────
# Test releases go to harshitsinghbhandari/agent-orchestrator (the fork), never
# to AgentWrapper. To cut one, on the FORK:
# - push a `desktop-v*` tag (e.g. `git push fork desktop-v0.0.0-test1`), or
# - run this workflow via `workflow_dispatch` from the fork's Actions tab.
# AO_RELEASE_REPO MUST be set to the fork for a test run. It is derived
# automatically below from `github.repository`, so a fork run publishes to the
# fork and a run on AgentWrapper publishes to AgentWrapper, with no edit. The
# matching test `ao` binary is built with
# `-ldflags -X ...cli.releaseRepo=harshitsinghbhandari/agent-orchestrator` so it
# fetches from the same fork (spec §6.3).
on:
push:
tags:
- "desktop-v*"
workflow_dispatch:
jobs:
release:
strategy:
fail-fast: false
matrix:
os: [macos-latest, windows-latest, ubuntu-latest]
runs-on: ${{ matrix.os }}
# Gates the signed release behind the `release` environment's required
# reviewers: any write-access user can push a desktop-v* tag, but only
# designated approvers can let the build that consumes the signing secrets
# proceed. Configure reviewers in repo Settings > Environments > release.
environment: release
permissions:
contents: write
defaults:
run:
working-directory: frontend
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 20
cache: npm
cache-dependency-path: frontend/package-lock.json
# The daemon is compiled by build-daemon.mjs during prepackage/premake, so
# the Go toolchain must be present and pinned on every runner.
- uses: actions/setup-go@v5
with:
go-version-file: backend/go.mod
cache-dependency-path: backend/go.sum
- run: npm ci
- name: macOS signing setup
if: startsWith(matrix.os, 'macos')
uses: ./.github/actions/macos-signing-setup
with:
csc-link: ${{ secrets.CSC_LINK }}
csc-key-password: ${{ secrets.CSC_KEY_PASSWORD }}
apple-api-key-base64: ${{ secrets.APPLE_API_KEY_BASE64 }}
apple-api-key-id: ${{ secrets.APPLE_API_KEY_ID }}
apple-api-issuer: ${{ secrets.APPLE_API_ISSUER }}
apple-signing-identity: ${{ secrets.APPLE_SIGNING_IDENTITY }}
- name: Publish
run: npm run publish
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
AO_RELEASE_REPO: ${{ github.repository }}
# macOS signing + notarization env (APPLE_SIGNING_IDENTITY, APPLE_API_KEY,
# APPLE_API_KEY_ID, APPLE_API_ISSUER) is exported by the "macOS signing
# setup" step above via $GITHUB_ENV; the Developer ID cert is in the
# keychain. No-op on non-macOS runners.
# Upload stable, version-free asset aliases for this runner's platform so
# `ao start`'s constant releases/latest/download/<name> URL resolves. The
# forge makers emit versioned, productName-derived filenames; we copy them
# to the space-free names `ao start` fetches and upload to the v<version>
# release the publish step just created, with --clobber (so re-runs replace).
#
# Arch-coverage note: each runner only builds its host arch. macos-latest
# (Apple silicon) produces darwin-arm64; macos-15-intel (Intel x64) produces
# darwin-x64. Both macOS runners run this step (see the `if` below), so the
# arm64 and x64 stable aliases are now both built (spec §8).
# The Linux stable name is undecided (spec §11.3), so we build the deb/rpm
# but intentionally upload no Linux alias yet.
- name: Upload stable asset aliases (macOS)
if: startsWith(matrix.os, 'macos')
shell: bash
run: |
# electron-forge publisher-github creates the release as v<package.json
# version> (PublisherGithub.js: `${tagPrefix ?? 'v'}${version}`), NOT the
# triggering git tag. Target that release, not GITHUB_REF_NAME, or the
# upload hits a non-existent release.
tag="v$(node -p "require('./package.json').version")"
arch="$(uname -m)"
case "$arch" in
arm64) alias_name="agent-orchestrator-darwin-arm64.zip" ;;
x86_64) alias_name="agent-orchestrator-darwin-x64.zip" ;;
*) echo "unsupported macOS arch: $arch" >&2; exit 1 ;;
esac
# maker-zip output: out/make/zip/darwin/<arch>/<dir-basename>-<version>.zip
src="$(ls out/make/zip/darwin/*/*.zip | head -n1)"
if [ -z "$src" ]; then echo "no macOS zip found under out/make/zip/darwin" >&2; exit 1; fi
cp "$src" "$alias_name"
gh release upload "$tag" "$alias_name" --clobber
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Upload stable asset aliases (Windows)
if: matrix.os == 'windows-latest'
shell: bash
run: |
# Forge publishes to v<version> (see the macOS step), not the git tag.
tag="v$(node -p "require('./package.json').version")"
alias_name="agent-orchestrator-win32-x64.exe"
# NSIS (electron-builder) output: out/make/<productName> Setup <version>.exe
src="$(ls "out/make/"*Setup*.exe | head -n1)"
if [ -z "$src" ]; then echo "no NSIS installer found under out/make" >&2; exit 1; fi
cp "$src" "$alias_name"
gh release upload "$tag" "$alias_name" --clobber
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
# Linux: deb/rpm AND an AppImage build on this runner (issue #2191). The
# AppImage is the fetch-and-run artifact for `ao start` (spec §11.3); we
# copy it to the stable, space-free name the bootstrapper fetches and
# upload to the v<version> release. The versioned deb/rpm are still
# published by the forge publisher above for users who want a system package.
- name: Upload stable asset aliases (Linux)
if: matrix.os == 'ubuntu-latest'
shell: bash
run: |
# Forge publishes to v<version> (see the macOS step), not the git tag.
tag="v$(node -p "require('./package.json').version")"
alias_name="agent-orchestrator-linux-x64.AppImage"
# AppImage (electron-builder) output: out/make/<productName>-<version>.AppImage
src="$(ls out/make/*.AppImage | head -n1)"
if [ -z "$src" ]; then echo "no AppImage found under out/make" >&2; exit 1; fi
cp "$src" "$alias_name"
gh release upload "$tag" "$alias_name" --clobber
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
# Dedicated Intel (darwin-x64) build leg. Kept a separate job (not in the
# release matrix) because only a macOS host can build the x64 installer, but
# publish-feed now waits on it (needs: [release, release-intel]) so the x64
# entry is always present in latest-mac.yml and Intel users get auto-updates.
# Runner is macos-15-intel: macos-13 is deprecated/has no capacity (multi-hour
# queues), so this leg uses the supported Intel x64 image.
release-intel:
runs-on: macos-15-intel
environment: release # same approval gate as the matrix release job
permissions:
contents: write
defaults:
run:
working-directory: frontend
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 20
cache: npm
cache-dependency-path: frontend/package-lock.json
- uses: actions/setup-go@v5
with:
go-version-file: backend/go.mod
cache-dependency-path: backend/go.sum
- run: npm ci
- name: macOS signing setup
uses: ./.github/actions/macos-signing-setup
with:
csc-link: ${{ secrets.CSC_LINK }}
csc-key-password: ${{ secrets.CSC_KEY_PASSWORD }}
apple-api-key-base64: ${{ secrets.APPLE_API_KEY_BASE64 }}
apple-api-key-id: ${{ secrets.APPLE_API_KEY_ID }}
apple-api-issuer: ${{ secrets.APPLE_API_ISSUER }}
apple-signing-identity: ${{ secrets.APPLE_SIGNING_IDENTITY }}
- name: Publish
run: npm run publish
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
AO_RELEASE_REPO: ${{ github.repository }}
- name: Upload stable asset aliases (macOS)
shell: bash
run: |
tag="v$(node -p "require('./package.json').version")"
arch="$(uname -m)"
case "$arch" in
arm64) alias_name="agent-orchestrator-darwin-arm64.zip" ;;
x86_64) alias_name="agent-orchestrator-darwin-x64.zip" ;;
*) echo "unsupported macOS arch: $arch" >&2; exit 1 ;;
esac
src="$(ls out/make/zip/darwin/*/*.zip | head -n1)"
if [ -z "$src" ]; then echo "no macOS zip found under out/make/zip/darwin" >&2; exit 1; fi
cp "$src" "$alias_name"
gh release upload "$tag" "$alias_name" --clobber
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
# After every platform has built and uploaded its installers, generate the
# electron-updater feed (latest*.yml + .blockmap sidecars) from the versioned
# assets and upload them to the same release. Runs once, on Linux: it only
# hashes already-built artifacts, so it needs no per-OS runner.
publish-feed:
needs: [release, release-intel]
runs-on: ubuntu-latest
permissions:
contents: write
defaults:
run:
working-directory: frontend
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 20
cache: npm
cache-dependency-path: frontend/package-lock.json
# feed.mjs imports app-builder-lib's blockmap generator, so deps are needed.
- run: npm ci
- name: Generate and upload the latest feed
shell: bash
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
# Forge publishes to v<package.json version> (not the git tag).
tag="v$(node -p "require('./package.json').version")"
mkdir -p dist
gh release download "$tag" --dir dist --clobber
node scripts/feed.mjs dist "${tag#v}" latest
shopt -s nullglob
assets=(dist/latest*.yml dist/*.blockmap)
if [ ${#assets[@]} -eq 0 ]; then echo "no feed assets generated" >&2; exit 1; fi
gh release upload "$tag" "${assets[@]}" --clobber