name: Desktop Release Build Smoke on: workflow_dispatch: inputs: repository: description: Repository to checkout, for example openchamber/openchamber or daveotero/openchamber required: false default: openchamber/openchamber type: string ref: description: Git ref to build (branch, tag, or sha) required: true default: feat/windows-desktop-app type: string build_macos: description: Build signed/notarized macOS Electron artifacts required: false default: true type: boolean build_windows: description: Build Windows Electron installer artifacts required: false default: true type: boolean build_linux: description: Build Linux Electron AppImage artifacts required: false default: true type: boolean retention_days: description: Artifact retention days required: false default: "7" type: choice options: - "1" - "3" - "7" - "14" permissions: contents: read jobs: build-macos-electron: if: ${{ inputs.build_macos }} name: Build macOS Electron (${{ matrix.arch }}) runs-on: macos-26 strategy: fail-fast: false matrix: include: - target: aarch64-apple-darwin arch: arm64 platform: darwin-aarch64 - target: x86_64-apple-darwin arch: x64 platform: darwin-x86_64 steps: - name: Checkout selected ref uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 with: repository: ${{ inputs.repository || github.repository }} ref: ${{ inputs.ref || github.ref }} - name: Setup bun uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # v2.2.0 - name: Setup Node.js uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0 with: node-version: '22' - name: Install dependencies run: bun install --frozen-lockfile - name: Get bundled OpenCode CLI version id: opencode_cli_version run: | VERSION=$(node -p "require('./package.json').dependencies['@opencode-ai/sdk']") echo "version=$VERSION" >> "$GITHUB_OUTPUT" - name: Cache bundled OpenCode CLI artifact uses: actions/cache@0400d5f644dc74513175e3cd8d07132dd4860809 # v4.2.4 with: path: packages/electron/.cache/opencode-cli key: opencode-cli-${{ runner.os }}-${{ matrix.arch }}-${{ steps.opencode_cli_version.outputs.version }} restore-keys: | opencode-cli-${{ runner.os }}-${{ matrix.arch }}- - name: Install Apple Certificate env: APPLE_CERTIFICATE: ${{ secrets.APPLE_CERTIFICATE }} APPLE_CERTIFICATE_PASSWORD: ${{ secrets.APPLE_CERTIFICATE_PASSWORD }} run: | KEYCHAIN_PATH=$RUNNER_TEMP/electron-signing.keychain-db KEYCHAIN_PASSWORD=$(openssl rand -base64 32) security create-keychain -p "$KEYCHAIN_PASSWORD" "$KEYCHAIN_PATH" security set-keychain-settings -lut 21600 "$KEYCHAIN_PATH" security unlock-keychain -p "$KEYCHAIN_PASSWORD" "$KEYCHAIN_PATH" echo "$APPLE_CERTIFICATE" | base64 --decode > $RUNNER_TEMP/certificate.p12 security import $RUNNER_TEMP/certificate.p12 \ -P "$APPLE_CERTIFICATE_PASSWORD" \ -A -t cert -f pkcs12 \ -k "$KEYCHAIN_PATH" security list-keychain -d user -s "$KEYCHAIN_PATH" security set-key-partition-list -S apple-tool:,apple:,codesign: \ -s -k "$KEYCHAIN_PASSWORD" "$KEYCHAIN_PATH" - name: Build Electron app working-directory: packages/electron env: APPLE_ID: ${{ secrets.APPLE_ID }} APPLE_APP_SPECIFIC_PASSWORD: ${{ secrets.APPLE_PASSWORD }} APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }} ELECTRON_BUILDER_ARCH: ${{ matrix.arch }} run: | bun run build:web-assets bun run prepare:opencode-cli bun run verify:opencode-cli bun run bundle:main # npmRebuild=false in package.json, so electron-builder won't # recompile native deps on its own. Rebuild against the target # Electron ABI before packaging, matching the release workflow. bun run rebuild:native bunx electron-builder --mac --${{ matrix.arch }} --publish=never bun run verify:opencode-cli:packaged - name: Verify signature + entitlements + notarization run: | set -euo pipefail APP_DIR="packages/electron/dist/mac" [ -d "packages/electron/dist/mac-arm64" ] && APP_DIR="packages/electron/dist/mac-arm64" APP_PATH=$(find "$APP_DIR" -maxdepth 2 -name "*.app" -print -quit) if [ -z "$APP_PATH" ]; then echo "Error: .app not found under packages/electron/dist/mac*" ls -la packages/electron/dist/ exit 1 fi echo "Verifying $APP_PATH" codesign -vv --deep --strict "$APP_PATH" CS_INFO=$(codesign -dv --verbose=4 "$APP_PATH" 2>&1) echo "$CS_INFO" if ! echo "$CS_INFO" | grep -q "flags=.*runtime"; then echo "Error: hardened runtime flag missing" exit 1 fi xcrun stapler validate "$APP_PATH" ENTITLEMENTS=$(codesign -d --entitlements :- "$APP_PATH" 2>&1 || true) if echo "$ENTITLEMENTS" | grep -q "com.apple.security.app-sandbox"; then echo "Error: app sandbox entitlement is present" exit 1 fi for key in \ com.apple.security.cs.allow-jit \ com.apple.security.cs.allow-unsigned-executable-memory \ com.apple.security.cs.disable-library-validation do if ! echo "$ENTITLEMENTS" | grep -q "$key"; then echo "Error: required entitlement missing: $key" exit 1 fi done - name: Upload macOS installable artifacts uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 with: name: desktop-release-smoke-macos-${{ matrix.arch }} path: | packages/electron/dist/*.dmg packages/electron/dist/*.zip packages/electron/dist/*.blockmap packages/electron/dist/latest-mac.yml if-no-files-found: error retention-days: ${{ fromJSON(inputs.retention_days) }} build-windows-electron: if: ${{ inputs.build_windows }} name: Build Windows Electron (${{ matrix.arch }}) # Match the production release workflow. windows-latest currently resolves # to a runner with Visual Studio 18, which this Electron/node-gyp stack does # not detect correctly. runs-on: windows-2022 strategy: fail-fast: false matrix: include: - arch: x64 target: x86_64-pc-windows-msvc platform: win32-x64 - arch: arm64 target: aarch64-pc-windows-msvc platform: win32-arm64 steps: - name: Checkout selected ref uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 with: repository: ${{ inputs.repository || github.repository }} ref: ${{ inputs.ref || github.ref }} - name: Setup bun uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # v2.2.0 - name: Setup Node.js uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0 with: node-version: '22' - name: Install dependencies run: bun install --frozen-lockfile - name: Get bundled OpenCode CLI version id: opencode_cli_version shell: bash run: | VERSION=$(node -p "require('./package.json').dependencies['@opencode-ai/sdk']") echo "version=$VERSION" >> "$GITHUB_OUTPUT" - name: Cache bundled OpenCode CLI artifact uses: actions/cache@0400d5f644dc74513175e3cd8d07132dd4860809 # v4.2.4 with: path: packages/electron/.cache/opencode-cli key: opencode-cli-${{ runner.os }}-${{ matrix.arch }}-${{ steps.opencode_cli_version.outputs.version }} restore-keys: | opencode-cli-${{ runner.os }}-${{ matrix.arch }}- - name: Build web assets working-directory: packages/electron run: bun run build:web-assets - name: Prepare bundled OpenCode CLI working-directory: packages/electron shell: bash run: | bun run prepare:opencode-cli bun run verify:opencode-cli - name: Bundle main process working-directory: packages/electron run: bun run bundle:main - name: Rebuild native modules working-directory: packages/electron shell: bash env: # Cross-compile for ARM64 target from x64 runner. ELECTRON_BUILDER_ARCH: ${{ matrix.arch }} # npmRebuild=false in package.json, so electron-builder won't # recompile native deps on its own. Rebuild against the target # Electron ABI before packaging, matching the release workflow. run: node ./scripts/rebuild-native.mjs - name: Build Windows app working-directory: packages/electron shell: bash run: | node ./scripts/package.mjs --win --${{ matrix.arch }} --publish=never bun run verify:opencode-cli:packaged - name: Upload Windows installable artifacts uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 with: name: desktop-release-smoke-windows-${{ matrix.arch }} path: | packages/electron/dist/*.exe packages/electron/dist/*.blockmap packages/electron/dist/latest.yml if-no-files-found: error retention-days: ${{ fromJSON(inputs.retention_days) }} build-linux-electron: if: ${{ inputs.build_linux }} name: Build Linux Electron (${{ matrix.arch }}) strategy: fail-fast: false matrix: include: - runner: ubuntu-24.04 arch: x64 host_arch: x86_64 artifact_arch: x86_64 manifest: latest-linux.yml - runner: ubuntu-24.04-arm arch: arm64 host_arch: aarch64 artifact_arch: arm64 manifest: latest-linux-arm64.yml runs-on: ${{ matrix.runner }} steps: - name: Checkout selected ref uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 with: repository: ${{ inputs.repository || github.repository }} ref: ${{ inputs.ref || github.ref }} - name: Setup bun uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # v2.2.0 - name: Setup Node.js uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0 with: node-version: '22' - name: Verify native Linux architecture env: EXPECTED_HOST_ARCH: ${{ matrix.host_arch }} OPENCHAMBER_TARGET_ARCH: ${{ matrix.arch }} run: | set -euo pipefail test "$(uname -m)" = "$EXPECTED_HOST_ARCH" test "$(node -p 'process.arch')" = "$OPENCHAMBER_TARGET_ARCH" - name: Install dependencies run: bun install --frozen-lockfile - name: Get build versions id: versions shell: bash run: | echo "opencode_cli=$(node -p "require('./package.json').dependencies['@opencode-ai/sdk']")" >> "$GITHUB_OUTPUT" echo "app=$(node -p "require('./packages/electron/package.json').version")" >> "$GITHUB_OUTPUT" - name: Cache bundled OpenCode CLI artifact uses: actions/cache@0400d5f644dc74513175e3cd8d07132dd4860809 # v4.2.4 with: path: packages/electron/.cache/opencode-cli key: opencode-cli-${{ runner.os }}-${{ matrix.arch }}-${{ steps.versions.outputs.opencode_cli }} restore-keys: | opencode-cli-${{ runner.os }}-${{ matrix.arch }}- - name: Run focused Electron release tests working-directory: packages/electron run: | bun run test:architecture bun run test:updater - name: Build and package Linux AppImage working-directory: packages/electron env: OPENCHAMBER_TARGET_ARCH: ${{ matrix.arch }} run: | set -euo pipefail bun run build:web-assets bun run prepare:opencode-cli bun run verify:opencode-cli bun run bundle:main bun run rebuild:native node ./scripts/package.mjs --linux --${{ matrix.arch }} --publish=never bun run verify:opencode-cli:packaged bun run verify:linux-appimage - name: Validate Linux update manifest working-directory: packages/electron env: VERSION: ${{ steps.versions.outputs.app }} ARTIFACT_ARCH: ${{ matrix.artifact_arch }} MANIFEST: ${{ matrix.manifest }} run: | set -euo pipefail APPIMAGE="dist/OpenChamber-${VERSION}-linux-${ARTIFACT_ARCH}.AppImage" node ./scripts/verify-update-manifest.mjs "dist/${MANIFEST}" "$APPIMAGE" "$VERSION" - name: Upload Linux installable artifacts uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 with: name: desktop-release-smoke-linux-${{ matrix.arch }} path: | packages/electron/dist/OpenChamber-${{ steps.versions.outputs.app }}-linux-${{ matrix.artifact_arch }}.AppImage packages/electron/dist/${{ matrix.manifest }} if-no-files-found: error retention-days: ${{ fromJSON(inputs.retention_days) }}