cc-haha/.github/workflows/release-desktop.yml
程序员阿江(Relakkes) 386a41e606 feat(desktop): enable Electron migration path
Introduce the Electron desktop shell alongside the existing React renderer and local Bun server boundary. The migration keeps the DesktopHost contract explicit across Tauri, Electron, and browser runtimes while adding Electron main/preload services for dialogs, shell, notifications, updates, tray/window lifecycle, terminal, preview WebContentsView, app mode, and release/package validation.

The commit also carries the latest local main desktop command updates, including agent slash entries and hidden-by-default markdown thinking details, so the packaged Electron build matches the current main UX surface.

Constraint: React renderer, local Bun server, REST/WebSocket, and sidecar boundaries must remain reusable during the migration
Constraint: macOS dev packages are ad-hoc signed and cannot prove Developer ID notarization or Gatekeeper release launch
Rejected: Browser-only smoke validation | it cannot exercise native dialogs, keychain prompts, notification behavior, or packaged app startup
Confidence: medium
Scope-risk: broad
Directive: Do not remove Tauri host support until signed Electron release artifacts pass native OS smoke on macOS, Windows, and Linux
Tested: bun run check:desktop
Tested: cd desktop && bun run check:electron
Tested: CSC_IDENTITY_AUTO_DISCOVERY=false bun run electron📦dir
Tested: bun run test:package-smoke --platform macos --package-kind dir --artifacts-dir desktop/build-artifacts/electron
Tested: Computer Use read packaged Electron app window at desktop/build-artifacts/electron/mac-arm64/Claude Code Haha.app
Not-tested: Developer ID signed/notarized Gatekeeper launch
Not-tested: Real OS notification click-to-session action
Not-tested: Windows and Linux packaged app smoke on real hosts
2026-06-01 22:43:16 +08:00

286 lines
9.3 KiB
YAML

name: Release Desktop
on:
push:
tags: ['v*.*.*']
workflow_dispatch:
inputs:
draft:
description: 'Create as draft release'
required: false
default: true
type: boolean
permissions:
contents: write
concurrency:
group: release-desktop-${{ github.ref }}
cancel-in-progress: true
jobs:
quality-preflight:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Setup Bun
uses: oven-sh/setup-bun@v2
with:
bun-version: latest
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: 22
- name: Install root dependencies
run: bun install
- name: Install desktop dependencies
working-directory: desktop
run: bun install
- name: Install adapter dependencies
working-directory: adapters
run: bun install
- name: Verify PR-quality gate before release packaging
run: bun run verify
signing-preflight:
runs-on: ubuntu-latest
steps:
- name: Validate release signing and notarization secrets
shell: bash
env:
CSC_LINK: ${{ secrets.MACOS_CERTIFICATE }}
CSC_KEY_PASSWORD: ${{ secrets.MACOS_CERTIFICATE_PASSWORD }}
APPLE_ID: ${{ secrets.APPLE_ID }}
APPLE_APP_SPECIFIC_PASSWORD: ${{ secrets.APPLE_APP_SPECIFIC_PASSWORD }}
APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }}
WIN_CSC_LINK: ${{ secrets.WINDOWS_CERTIFICATE }}
WIN_CSC_KEY_PASSWORD: ${{ secrets.WINDOWS_CERTIFICATE_PASSWORD }}
run: |
missing=()
[ -n "$CSC_LINK" ] || missing+=("MACOS_CERTIFICATE")
[ -n "$CSC_KEY_PASSWORD" ] || missing+=("MACOS_CERTIFICATE_PASSWORD")
[ -n "$APPLE_ID" ] || missing+=("APPLE_ID")
[ -n "$APPLE_APP_SPECIFIC_PASSWORD" ] || missing+=("APPLE_APP_SPECIFIC_PASSWORD")
[ -n "$APPLE_TEAM_ID" ] || missing+=("APPLE_TEAM_ID")
[ -n "$WIN_CSC_LINK" ] || missing+=("WINDOWS_CERTIFICATE")
[ -n "$WIN_CSC_KEY_PASSWORD" ] || missing+=("WINDOWS_CERTIFICATE_PASSWORD")
if [ "${#missing[@]}" -gt 0 ]; then
printf '::error::Missing required release signing/notarization secrets: %s\n' "${missing[*]}"
exit 1
fi
build:
needs:
- quality-preflight
- signing-preflight
strategy:
fail-fast: false
matrix:
include:
- platform: macos-latest
target_triple: aarch64-apple-darwin
builder_args: --mac dmg zip --arm64
label: macOS-ARM64
smoke_platform: macos
- platform: macos-latest
target_triple: x86_64-apple-darwin
builder_args: --mac dmg zip --x64
label: macOS-x64
smoke_platform: macos
- platform: ubuntu-22.04
target_triple: x86_64-unknown-linux-gnu
builder_args: --linux AppImage deb --x64
label: Linux-x64
smoke_platform: linux
- platform: ubuntu-22.04-arm
target_triple: aarch64-unknown-linux-gnu
builder_args: --linux AppImage deb --arm64
label: Linux-ARM64
smoke_platform: linux
- platform: windows-latest
target_triple: x86_64-pc-windows-msvc
builder_args: --win nsis --x64
label: Windows-x64
smoke_platform: windows
runs-on: ${{ matrix.platform }}
name: Build (${{ matrix.label }})
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Read version
id: version
shell: bash
run: |
VERSION=$(node -p "require('./desktop/package.json').version")
echo "value=$VERSION" >> "$GITHUB_OUTPUT"
- name: Validate tag matches version
if: github.event_name == 'push'
shell: bash
run: |
EXPECTED_TAG="v${{ steps.version.outputs.value }}"
if [ "${GITHUB_REF_NAME}" != "$EXPECTED_TAG" ]; then
echo "::error::Tag ${GITHUB_REF_NAME} does not match app version ${EXPECTED_TAG}"
exit 1
fi
- name: Load release notes
id: release_notes
shell: bash
run: |
NOTES_FILE="release-notes/v${{ steps.version.outputs.value }}.md"
if [ ! -f "$NOTES_FILE" ]; then
echo "::error::Missing release notes file: $NOTES_FILE"
exit 1
fi
{
echo 'body<<__RELEASE_NOTES__'
cat "$NOTES_FILE"
echo
echo '__RELEASE_NOTES__'
} >> "$GITHUB_OUTPUT"
- name: Install Linux dependencies
if: contains(matrix.platform, 'ubuntu')
run: |
sudo apt-get update
sudo apt-get install -y build-essential curl wget file libfuse2
- name: Setup Bun
uses: oven-sh/setup-bun@v2
with:
bun-version: latest
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: 22
- name: Install root dependencies
run: bun install
- name: Install desktop dependencies
working-directory: desktop
run: bun install
- name: Install adapter dependencies
working-directory: adapters
run: bun install
- name: Build sidecars
working-directory: desktop
env:
BUN_INSTALL_CACHE_DIR: ${{ runner.temp }}/bun-install-cache
SIDECAR_TARGET_TRIPLE: ${{ matrix.target_triple }}
run: bun run build:sidecars
- name: Build renderer and Electron bundles
working-directory: desktop
run: |
bun run build
bun run build:electron
- name: Build Electron release artifacts
working-directory: desktop
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
CSC_LINK: ${{ secrets.MACOS_CERTIFICATE }}
CSC_KEY_PASSWORD: ${{ secrets.MACOS_CERTIFICATE_PASSWORD }}
APPLE_ID: ${{ secrets.APPLE_ID }}
APPLE_APP_SPECIFIC_PASSWORD: ${{ secrets.APPLE_APP_SPECIFIC_PASSWORD }}
APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }}
WIN_CSC_LINK: ${{ secrets.WINDOWS_CERTIFICATE }}
WIN_CSC_KEY_PASSWORD: ${{ secrets.WINDOWS_CERTIFICATE_PASSWORD }}
run: bunx electron-builder ${{ matrix.builder_args }} --publish never
- name: Verify packaged app structure
run: bun run test:package-smoke --platform ${{ matrix.smoke_platform }} --package-kind release --artifacts-dir desktop/build-artifacts/electron
- name: Verify macOS launch policy
if: matrix.smoke_platform == 'macos'
run: bun run test:package-smoke --platform macos --package-kind release --artifacts-dir desktop/build-artifacts/electron --require-macos-gatekeeper
- name: Namespace update metadata assets
shell: bash
working-directory: desktop/build-artifacts/electron
run: |
shopt -s nullglob
for file in latest*.yml; do
mv "$file" "${file%.yml}-${{ matrix.label }}.yml"
done
- name: Upload update metadata for merge
uses: actions/upload-artifact@v4
with:
name: desktop-update-metadata-${{ matrix.label }}
path: desktop/build-artifacts/electron/latest*.yml
if-no-files-found: ignore
- name: Upload artifacts
uses: softprops/action-gh-release@v2
with:
tag_name: v${{ steps.version.outputs.value }}
name: Claude Code Haha v${{ steps.version.outputs.value }}
body: ${{ steps.release_notes.outputs.body }}
draft: ${{ github.event_name == 'workflow_dispatch' && inputs.draft || false }}
prerelease: false
files: |
desktop/build-artifacts/electron/*.dmg
desktop/build-artifacts/electron/*.zip
desktop/build-artifacts/electron/*.exe
desktop/build-artifacts/electron/*.AppImage
desktop/build-artifacts/electron/*.deb
desktop/build-artifacts/electron/*.blockmap
desktop/build-artifacts/electron/*.yml
publish-update-metadata:
needs: build
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Read version
id: version
shell: bash
run: |
VERSION=$(node -p "require('./desktop/package.json').version")
echo "value=$VERSION" >> "$GITHUB_OUTPUT"
- name: Setup Bun
uses: oven-sh/setup-bun@v2
with:
bun-version: latest
- name: Install root dependencies
run: bun install
- name: Download update metadata artifacts
uses: actions/download-artifact@v4
with:
pattern: desktop-update-metadata-*
path: artifacts/update-metadata
merge-multiple: true
- name: Merge standard update metadata
run: bun run scripts/release-update-metadata.ts --metadata-dir artifacts/update-metadata --out-dir artifacts/update-metadata-standard
- name: Upload standard update metadata
uses: softprops/action-gh-release@v2
with:
tag_name: v${{ steps.version.outputs.value }}
draft: ${{ github.event_name == 'workflow_dispatch' && inputs.draft || false }}
prerelease: false
files: artifacts/update-metadata-standard/*.yml