ci: cut over desktop releases to Electron

Repackages Electron app for legacy Tauri updater migration
Stops release and manual DMG workflows from building Tauri
Documents transition flow and cleanup timing
This commit is contained in:
Bohdan Triapitsyn
2026-05-21 16:37:06 +03:00
parent 6cc1afc963
commit 7d98f388c0
3 changed files with 249 additions and 493 deletions
+1 -108
View File
@@ -1,4 +1,4 @@
name: Build macOS DMG (arm64)
name: Build Electron macOS DMG (arm64)
on:
workflow_dispatch:
@@ -16,114 +16,7 @@ on:
required: false
default: ""
env:
CARGO_INCREMENTAL: 0
RUST_BACKTRACE: short
jobs:
build-macos-dmg-arm64:
name: Build DMG (arm64, ${{ inputs.macos_version }})
runs-on: ${{ inputs.macos_version }}
steps:
- name: Checkout
uses: actions/checkout@v4
with:
ref: ${{ inputs.ref || github.ref }}
- name: Setup bun
uses: oven-sh/setup-bun@v2
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: "20"
- name: Install Rust stable
uses: dtolnay/rust-toolchain@stable
with:
targets: aarch64-apple-darwin
- name: Rust cache
uses: swatinem/rust-cache@v2
with:
workspaces: packages/desktop/src-tauri
key: aarch64-apple-darwin
- name: Install dependencies
run: bun install --frozen-lockfile
- name: Install Apple Certificate
env:
APPLE_CERTIFICATE: ${{ secrets.APPLE_CERTIFICATE }}
APPLE_CERTIFICATE_PASSWORD: ${{ secrets.APPLE_CERTIFICATE_PASSWORD }}
run: |
KEYCHAIN_PATH=$RUNNER_TEMP/app-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: Set up notarization credentials
env:
APPLE_ID: ${{ secrets.APPLE_ID }}
APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }}
APPLE_PASSWORD: ${{ secrets.APPLE_PASSWORD }}
run: |
if [ -z "$APPLE_ID" ] || [ -z "$APPLE_TEAM_ID" ] || [ -z "$APPLE_PASSWORD" ]; then
echo "Error: Missing Apple notarization credentials"
exit 1
fi
xcrun notarytool store-credentials "openchamber-notarize" \
--apple-id "$APPLE_ID" \
--team-id "$APPLE_TEAM_ID" \
--password "$APPLE_PASSWORD"
- name: Build UI package
run: bun run --cwd packages/ui build
- name: Build Desktop app (arm64)
run: bun run --cwd packages/desktop build && bun run --cwd packages/desktop tauri build --target aarch64-apple-darwin
env:
TAURI_SIGNING_PRIVATE_KEY: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY }}
TAURI_SIGNING_PRIVATE_KEY_PASSWORD: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY_PASSWORD }}
APPLE_SIGNING_IDENTITY: ${{ secrets.APPLE_SIGNING_IDENTITY }}
APPLE_ID: ${{ secrets.APPLE_ID }}
APPLE_PASSWORD: ${{ secrets.APPLE_PASSWORD }}
APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }}
- name: Prepare DMG artifact
run: |
set -euo pipefail
mkdir -p artifacts
DMG_PATH="packages/desktop/src-tauri/target/aarch64-apple-darwin/release/bundle/dmg/*.dmg"
if ls $DMG_PATH 1> /dev/null 2>&1; then
DMG_FILE=$(ls $DMG_PATH | head -n 1)
DMG_NAME="OpenChamber_${{ inputs.macos_version }}_arm64.dmg"
cp "$DMG_FILE" "artifacts/$DMG_NAME"
else
echo "Error: DMG file not found at $DMG_PATH"
exit 1
fi
- name: Upload DMG artifact
uses: actions/upload-artifact@v4
with:
name: dmg-${{ inputs.macos_version }}-arm64
path: artifacts/*.dmg
retention-days: 7
build-macos-dmg-arm64-electron:
name: Build Electron DMG (arm64, ${{ inputs.macos_version }})
runs-on: ${{ inputs.macos_version }}
+183 -377
View File
@@ -78,299 +78,6 @@ jobs:
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
build-desktop-macos:
needs: create-release
runs-on: macos-26
strategy:
fail-fast: false
matrix:
target: [aarch64-apple-darwin, x86_64-apple-darwin]
include:
- target: aarch64-apple-darwin
arch: aarch64
platform: darwin-aarch64
- target: x86_64-apple-darwin
arch: x86_64
platform: darwin-x86_64
outputs:
version: ${{ needs.create-release.outputs.version }}
steps:
- uses: actions/checkout@v4
- name: Setup bun
uses: oven-sh/setup-bun@v2
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '20'
- name: Install Rust stable
uses: dtolnay/rust-toolchain@stable
with:
targets: ${{ matrix.target }}
- name: Rust cache
uses: swatinem/rust-cache@v2
with:
workspaces: packages/desktop/src-tauri
key: ${{ matrix.target }}
- name: Install dependencies
run: bun install --frozen-lockfile
- name: Install Apple Certificate
env:
APPLE_CERTIFICATE: ${{ secrets.APPLE_CERTIFICATE }}
APPLE_CERTIFICATE_PASSWORD: ${{ secrets.APPLE_CERTIFICATE_PASSWORD }}
run: |
# Create temporary keychain
KEYCHAIN_PATH=$RUNNER_TEMP/app-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"
# Import certificate
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: Set up notarization credentials
env:
APPLE_ID: ${{ secrets.APPLE_ID }}
APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }}
APPLE_PASSWORD: ${{ secrets.APPLE_PASSWORD }}
run: |
# Validate secrets are set
if [ -z "$APPLE_ID" ] || [ -z "$APPLE_TEAM_ID" ] || [ -z "$APPLE_PASSWORD" ]; then
echo "Error: Missing Apple notarization credentials"
exit 1
fi
xcrun notarytool store-credentials "openchamber-notarize" \
--apple-id "$APPLE_ID" \
--team-id "$APPLE_TEAM_ID" \
--password "$APPLE_PASSWORD"
- name: Build UI package
run: bun run --cwd packages/ui build
- name: Build Desktop app
# Note: We use inline commands instead of desktop:build to pass architecture-specific --target flag
# This enables cross-compilation for both arm64 and x86_64 from the same runner
run: |
export TAURI_ENV_TARGET_TRIPLE=${{ matrix.target }}
bun run --cwd packages/desktop build
bun run --cwd packages/desktop tauri build --target ${{ matrix.target }}
env:
TAURI_ENV_TARGET_TRIPLE: ${{ matrix.target }}
TAURI_SIGNING_PRIVATE_KEY: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY }}
TAURI_SIGNING_PRIVATE_KEY_PASSWORD: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY_PASSWORD }}
APPLE_SIGNING_IDENTITY: ${{ secrets.APPLE_SIGNING_IDENTITY }}
APPLE_ID: ${{ secrets.APPLE_ID }}
APPLE_PASSWORD: ${{ secrets.APPLE_PASSWORD }}
APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }}
- name: Verify binary architectures
run: |
set -euo pipefail
BUNDLE_DIR="packages/desktop/src-tauri/target/${{ matrix.target }}/release/bundle/macos"
if [ ! -d "$BUNDLE_DIR" ]; then
echo "❌ Error: bundle directory not found: $BUNDLE_DIR"
exit 1
fi
APP_PATH=$(find "$BUNDLE_DIR" -maxdepth 2 -name "*.app" -print -quit)
if [ -z "$APP_PATH" ]; then
echo "❌ Error: .app bundle not found under $BUNDLE_DIR"
exit 1
fi
echo "🔍 Verifying binary architectures in $APP_PATH"
# Extract raw architecture names (macOS file command reports ARM as "arm64")
MAIN_ARCH_RAW=$(file "$APP_PATH/Contents/MacOS/openchamber-desktop" | grep -oE 'arm64|x86_64|aarch64' | head -1)
SIDEARCH_ARCH_RAW=$(file "$APP_PATH/Contents/MacOS/openchamber-server" | grep -oE 'arm64|x86_64|aarch64' | head -1)
# Normalize architecture names (arm64 -> aarch64 for consistency with Rust/Tauri)
normalize_arch() {
case "$1" in
arm64) echo "aarch64" ;;
aarch64|x86_64) echo "$1" ;;
*) echo "unknown" ;;
esac
}
MAIN_ARCH=$(normalize_arch "$MAIN_ARCH_RAW")
SIDEARCH_ARCH=$(normalize_arch "$SIDEARCH_ARCH_RAW")
EXPECTED_ARCH=$(echo "${{ matrix.target }}" | grep -oE 'aarch64|x86_64' | head -1)
echo " Main: $MAIN_ARCH_RAW → $MAIN_ARCH"
echo " Sidecar: $SIDEARCH_ARCH_RAW → $SIDEARCH_ARCH"
echo " Expected: $EXPECTED_ARCH"
if [ "$MAIN_ARCH" != "$EXPECTED_ARCH" ]; then
echo "❌ ERROR: Main binary architecture mismatch!"
echo " Expected: $EXPECTED_ARCH"
echo " Got: $MAIN_ARCH (raw: $MAIN_ARCH_RAW)"
exit 1
fi
if [ "$SIDEARCH_ARCH" != "$EXPECTED_ARCH" ]; then
echo "❌ ERROR: Sidecar binary architecture mismatch!"
echo " Expected: $EXPECTED_ARCH"
echo " Got: $SIDEARCH_ARCH (raw: $SIDEARCH_ARCH_RAW)"
exit 1
fi
echo "✅ Architecture verification passed: both binaries match $EXPECTED_ARCH"
- name: Verify macOS entitlements
run: |
set -euo pipefail
BUNDLE_DIR="packages/desktop/src-tauri/target/${{ matrix.target }}/release/bundle/macos"
if [ ! -d "$BUNDLE_DIR" ]; then
echo "Error: bundle directory not found: $BUNDLE_DIR"
exit 1
fi
APP_PATH=$(find "$BUNDLE_DIR" -maxdepth 2 -name "*.app" -print -quit)
if [ -z "$APP_PATH" ]; then
echo "Error: .app bundle not found under $BUNDLE_DIR"
echo "Contents:"; ls -la "$BUNDLE_DIR"
exit 1
fi
echo "Verifying app bundle: $APP_PATH"
codesign -vv "$APP_PATH"
ENTITLEMENTS=$(codesign -d --entitlements :- "$APP_PATH" 2>&1 || true)
echo "$ENTITLEMENTS"
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-executable-page-protection \
com.apple.security.cs.disable-library-validation
do
if ! echo "$ENTITLEMENTS" | grep -q "<key>$key</key>"; then
echo "Error: required entitlement missing: $key"
exit 1
fi
done
- name: Prepare release artifacts
run: |
mkdir -p artifacts
VERSION="${{ needs.create-release.outputs.version }}"
# Copy DMG (Tauri names it with the target triple in the path)
DMG_PATH="packages/desktop/src-tauri/target/${{ matrix.target }}/release/bundle/dmg/*.dmg"
if ls $DMG_PATH 1> /dev/null 2>&1; then
DMG_FILE=$(ls $DMG_PATH | head -n 1)
DMG_NAME="OpenChamber_${VERSION}_${{ matrix.platform }}.dmg"
cp "$DMG_FILE" "artifacts/$DMG_NAME"
else
echo "Error: DMG file not found at $DMG_PATH"
exit 1
fi
# Copy tar.gz and signature for updater
TAR_PATH="packages/desktop/src-tauri/target/${{ matrix.target }}/release/bundle/macos/*.tar.gz"
SIG_PATH="packages/desktop/src-tauri/target/${{ matrix.target }}/release/bundle/macos/*.tar.gz.sig"
if ls $TAR_PATH 1> /dev/null 2>&1; then
TAR_FILE=$(ls $TAR_PATH | head -n 1)
TAR_BASE=$(basename "$TAR_FILE")
TAR_NAME="${TAR_BASE%.tar.gz}-${{ matrix.platform }}.tar.gz"
cp "$TAR_FILE" "artifacts/$TAR_NAME"
else
echo "Error: tar.gz file not found at $TAR_PATH"
exit 1
fi
if ls $SIG_PATH 1> /dev/null 2>&1; then
SIG_FILE=$(ls $SIG_PATH | head -n 1)
SIG_BASE=$(basename "$SIG_FILE")
SIG_NAME="${SIG_BASE%.tar.gz.sig}-${{ matrix.platform }}.tar.gz.sig"
cp "$SIG_FILE" "artifacts/$SIG_NAME"
else
echo "Error: signature file not found at $SIG_PATH"
exit 1
fi
echo "Successfully prepared artifacts:"
ls -lh artifacts/
- name: Generate update manifest
run: |
VERSION="${{ needs.create-release.outputs.version }}"
# Find the signature file for this platform
SIG_FILE=$(find artifacts -name "*-${{ matrix.platform }}.tar.gz.sig" | head -1)
if [ -f "$SIG_FILE" ]; then
SIGNATURE=$(cat "$SIG_FILE")
else
SIGNATURE=""
fi
# Find the tar.gz file name for this platform
TAR_FILE=$(find artifacts -name "*-${{ matrix.platform }}.tar.gz" ! -name "*.sig" | head -1)
TAR_NAME=$(basename "$TAR_FILE" 2>/dev/null || echo "OpenChamber-${{ matrix.platform }}.app.tar.gz")
cat > artifacts/latest-${{ matrix.platform }}.json << EOF
{
"version": "${VERSION}",
"notes": "See release notes at https://github.com/${{ github.repository }}/releases/tag/v${VERSION}",
"pub_date": "$(date -u +%Y-%m-%dT%H:%M:%SZ)",
"platforms": {
"${{ matrix.platform }}": {
"signature": "${SIGNATURE}",
"url": "https://github.com/${{ github.repository }}/releases/download/v${VERSION}/${TAR_NAME}"
}
}
}
EOF
echo "Generated latest-${{ matrix.platform }}.json:"
cat artifacts/latest-${{ matrix.platform }}.json
- name: Upload release assets
uses: softprops/action-gh-release@v2
with:
tag_name: v${{ needs.create-release.outputs.version }}
files: |
artifacts/*.dmg
artifacts/*.tar.gz
artifacts/*.tar.gz.sig
artifacts/latest-${{ matrix.platform }}.json
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Upload manifest as artifact
uses: actions/upload-artifact@v4
with:
name: manifest-${{ matrix.platform }}
path: artifacts/latest-${{ matrix.platform }}.json
retention-days: 1
publish-npm:
needs: create-release
runs-on: ubuntu-latest
@@ -411,89 +118,6 @@ jobs:
env:
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
combine-manifests:
needs: [create-release, build-desktop-macos]
runs-on: ubuntu-latest
steps:
- name: Download aarch64 manifest
uses: actions/download-artifact@v4
with:
name: manifest-darwin-aarch64
path: artifacts
- name: Download x86_64 manifest
uses: actions/download-artifact@v4
with:
name: manifest-darwin-x86_64
path: artifacts
- name: Combine manifests
run: |
VERSION="${{ needs.create-release.outputs.version }}"
PUB_DATE="$(date -u +%Y-%m-%dT%H:%M:%SZ)"
REPO="${{ github.repository }}"
# Validate that both manifest files exist and are valid JSON
if [ ! -f artifacts/latest-darwin-aarch64.json ]; then
echo "Error: aarch64 manifest not found"
exit 1
fi
if [ ! -f artifacts/latest-darwin-x86_64.json ]; then
echo "Error: x86_64 manifest not found"
exit 1
fi
# Validate JSON structure
if ! jq empty artifacts/latest-darwin-aarch64.json 2>/dev/null; then
echo "Error: aarch64 manifest is not valid JSON"
exit 1
fi
if ! jq empty artifacts/latest-darwin-x86_64.json 2>/dev/null; then
echo "Error: x86_64 manifest is not valid JSON"
exit 1
fi
# Validate platform data exists in manifests
if ! jq -e '.platforms["darwin-aarch64"]' artifacts/latest-darwin-aarch64.json > /dev/null; then
echo "Error: darwin-aarch64 platform data not found in manifest"
exit 1
fi
if ! jq -e '.platforms["darwin-x86_64"]' artifacts/latest-darwin-x86_64.json > /dev/null; then
echo "Error: darwin-x86_64 platform data not found in manifest"
exit 1
fi
# Use jq to properly combine the manifests
jq -n \
--arg version "$VERSION" \
--arg notes "See release notes at https://github.com/${REPO}/releases/tag/v${VERSION}" \
--arg pub_date "$PUB_DATE" \
--slurpfile aarch64 artifacts/latest-darwin-aarch64.json \
--slurpfile x86_64 artifacts/latest-darwin-x86_64.json \
'{
version: $version,
notes: $notes,
pub_date: $pub_date,
platforms: {
"darwin-aarch64": $aarch64[0].platforms["darwin-aarch64"],
"darwin-x86_64": $x86_64[0].platforms["darwin-x86_64"]
}
}' > artifacts/latest.json
echo "Generated combined latest.json:"
cat artifacts/latest.json
- name: Upload combined manifest
uses: softprops/action-gh-release@v2
with:
tag_name: v${{ needs.create-release.outputs.version }}
files: artifacts/latest.json
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
build-desktop-electron-macos:
needs: create-release
runs-on: macos-26
@@ -617,6 +241,31 @@ jobs:
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Stage signed Electron app for Tauri updater repackage
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
rm -rf electron-app-artifact
mkdir -p electron-app-artifact
cp -R "$APP_PATH" electron-app-artifact/OpenChamber.app
- name: Upload signed Electron app for Tauri updater repackage
uses: actions/upload-artifact@v4
with:
name: electron-app-${{ matrix.arch }}
path: electron-app-artifact/OpenChamber.app
retention-days: 1
- name: Upload per-arch latest-mac.yml for merge
uses: actions/upload-artifact@v4
with:
@@ -624,6 +273,163 @@ jobs:
path: packages/electron/dist/latest-mac.yml
retention-days: 1
repackage-electron-as-tauri-update:
needs: [create-release, build-desktop-electron-macos]
runs-on: macos-26
strategy:
fail-fast: false
matrix:
include:
- arch: arm64
platform: darwin-aarch64
- arch: x64
platform: darwin-x86_64
steps:
- uses: actions/checkout@v4
- name: Download signed Electron app
uses: actions/download-artifact@v4
with:
name: electron-app-${{ matrix.arch }}
path: staged
- name: Install minisign
run: brew install minisign
- name: Tar and sign Electron app as Tauri update payload
env:
TAURI_KEY: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY }}
TAURI_KEY_PASSWORD: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY_PASSWORD }}
VERSION: ${{ needs.create-release.outputs.version }}
run: |
set -euo pipefail
if [ ! -d staged/OpenChamber.app ]; then
echo "Error: staged/OpenChamber.app not found"
ls -la staged
exit 1
fi
cd staged
TARBALL="OpenChamber.app.tar.gz"
tar -czf "$TARBALL" OpenChamber.app
printf '%s\n' "$TAURI_KEY" > ../tauri-signing.key
printf '%s\n' "$TAURI_KEY_PASSWORD" | minisign -S -s ../tauri-signing.key -m "$TARBALL" -W
mv "$TARBALL" "OpenChamber-${VERSION}-${{ matrix.platform }}.app.tar.gz"
mv "${TARBALL}.minisig" "OpenChamber-${VERSION}-${{ matrix.platform }}.app.tar.gz.sig"
- name: Generate Tauri latest platform manifest
env:
VERSION: ${{ needs.create-release.outputs.version }}
REPO: ${{ github.repository }}
run: |
set -euo pipefail
SIG=$(cat staged/OpenChamber-${VERSION}-${{ matrix.platform }}.app.tar.gz.sig)
TAR="OpenChamber-${VERSION}-${{ matrix.platform }}.app.tar.gz"
jq -n \
--arg version "$VERSION" \
--arg notes "OpenChamber has moved to Electron. This update replaces the Tauri shell with the Electron build. Subsequent updates will be delivered via the Electron auto-updater." \
--arg pub_date "$(date -u +%Y-%m-%dT%H:%M:%SZ)" \
--arg platform "${{ matrix.platform }}" \
--arg signature "$SIG" \
--arg url "https://github.com/${REPO}/releases/download/v${VERSION}/${TAR}" \
'{ version: $version, notes: $notes, pub_date: $pub_date, platforms: { ($platform): { signature: $signature, url: $url } } }' \
> staged/latest-${{ matrix.platform }}.json
- name: Upload tarball and signature to release
uses: softprops/action-gh-release@v2
with:
tag_name: v${{ needs.create-release.outputs.version }}
files: |
staged/*.app.tar.gz
staged/*.app.tar.gz.sig
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Upload per-platform Tauri manifest for merge
uses: actions/upload-artifact@v4
with:
name: tauri-manifest-${{ matrix.platform }}
path: staged/latest-${{ matrix.platform }}.json
retention-days: 1
combine-manifests:
needs: [create-release, repackage-electron-as-tauri-update]
runs-on: ubuntu-latest
steps:
- name: Download Tauri updater manifests
uses: actions/download-artifact@v4
with:
pattern: tauri-manifest-*
path: artifacts
merge-multiple: true
- name: Combine manifests
run: |
set -euo pipefail
VERSION="${{ needs.create-release.outputs.version }}"
PUB_DATE="$(date -u +%Y-%m-%dT%H:%M:%SZ)"
if [ ! -f artifacts/latest-darwin-aarch64.json ]; then
echo "Error: aarch64 manifest not found"
exit 1
fi
if [ ! -f artifacts/latest-darwin-x86_64.json ]; then
echo "Error: x86_64 manifest not found"
exit 1
fi
if ! jq empty artifacts/latest-darwin-aarch64.json 2>/dev/null; then
echo "Error: aarch64 manifest is not valid JSON"
exit 1
fi
if ! jq empty artifacts/latest-darwin-x86_64.json 2>/dev/null; then
echo "Error: x86_64 manifest is not valid JSON"
exit 1
fi
if ! jq -e '.platforms["darwin-aarch64"]' artifacts/latest-darwin-aarch64.json > /dev/null; then
echo "Error: darwin-aarch64 platform data not found in manifest"
exit 1
fi
if ! jq -e '.platforms["darwin-x86_64"]' artifacts/latest-darwin-x86_64.json > /dev/null; then
echo "Error: darwin-x86_64 platform data not found in manifest"
exit 1
fi
jq -n \
--arg version "$VERSION" \
--arg notes "OpenChamber has moved to Electron. This update replaces the Tauri shell with the Electron build. Subsequent updates will be delivered via the Electron auto-updater." \
--arg pub_date "$PUB_DATE" \
--slurpfile aarch64 artifacts/latest-darwin-aarch64.json \
--slurpfile x86_64 artifacts/latest-darwin-x86_64.json \
'{
version: $version,
notes: $notes,
pub_date: $pub_date,
platforms: {
"darwin-aarch64": $aarch64[0].platforms["darwin-aarch64"],
"darwin-x86_64": $x86_64[0].platforms["darwin-x86_64"]
}
}' > artifacts/latest.json
cat artifacts/latest.json
- name: Upload combined Tauri updater manifest
uses: softprops/action-gh-release@v2
with:
tag_name: v${{ needs.create-release.outputs.version }}
files: artifacts/latest.json
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
combine-electron-manifests:
needs: [create-release, build-desktop-electron-macos]
runs-on: ubuntu-latest
@@ -657,7 +463,7 @@ jobs:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
finalize-release:
needs: [create-release, build-desktop-macos, build-desktop-electron-macos, publish-npm, combine-manifests, combine-electron-manifests]
needs: [create-release, build-desktop-electron-macos, repackage-electron-as-tauri-update, publish-npm, combine-manifests, combine-electron-manifests]
runs-on: ubuntu-latest
env:
DISCORD_WEBHOOK_URL: ${{ secrets.DISCORD_WEBHOOK_URL }}
+65 -8
View File
@@ -4,6 +4,13 @@
> designed will not be around when the cutover happens — read this file top to
> bottom and execute; do not assume prior context.
> Current status: the release workflow cutover is implemented. Desktop releases
> now build Electron and repackage that Electron `.app` into the old Tauri
> updater format for existing Tauri installs. The next safe engineering step is
> [Step 5 — Remove Tauri-specific code](#step-5--remove-tauri-specific-code),
> but only after the transition release has shipped and lived for at least 2
> weeks with no rollback.
## What this is
OpenChamber historically shipped as a Tauri app. A parallel Electron shell was
@@ -72,11 +79,46 @@ Check all of these before making any release:
5. **`minisign` CLI is available on the macOS runner** (or installable via
brew). Used to sign the Electron tarball with the Tauri key.
## Release workflow changes
## Current release workflow
The file to edit: `.github/workflows/release.yml`.
The release workflow no longer builds a Tauri desktop app. It now does this:
Today it has these jobs (simplified):
```text
create-release
├── build-desktop-electron-macos (Electron .dmg/.zip/blockmap/latest-mac.yml)
├── repackage-electron-as-tauri-update (Electron .app -> Tauri .app.tar.gz/.sig)
├── publish-npm
├── combine-manifests (Tauri latest.json for migration only)
├── combine-electron-manifests (Electron latest-mac.yml)
└── finalize-release
```
The transition works like this:
1. `build-desktop-electron-macos` builds, signs, and notarizes the Electron app.
2. It uploads the signed `OpenChamber.app` as a short-lived Actions artifact.
3. `repackage-electron-as-tauri-update` downloads that Electron `.app`.
4. It packs it into `OpenChamber-<version>-darwin-*.app.tar.gz`.
5. It signs that tarball with the existing Tauri minisign private key.
6. It uploads the tarball and `.sig` to the GitHub release.
7. It generates Tauri-compatible manifests and `combine-manifests` merges them into `latest.json`.
So old Tauri installs still see the update contract they expect:
```text
latest.json -> .app.tar.gz -> .sig
```
But the payload inside the `.app.tar.gz` is Electron, not Tauri. Tauri's updater
only verifies the signature and extracts the bundle over the existing
`/Applications/OpenChamber.app`. After restart, the app is Electron and future
updates use `latest-mac.yml` through `electron-updater`.
## Historical release workflow changes
The file edited for the cutover was `.github/workflows/release.yml`.
Before the cutover it had these jobs (simplified):
```
create-release
@@ -90,6 +132,8 @@ create-release
### Step 1 — Remove the Tauri build
Status: done.
Delete these jobs entirely:
- `build-desktop-macos`
- `combine-manifests`
@@ -99,6 +143,8 @@ list must be updated to drop both.
### Step 2 — Add a repackage job
Status: done.
Insert after `build-desktop-electron-macos`:
```yaml
@@ -201,6 +247,8 @@ repackage-electron-as-tauri-update:
### Step 3 — Re-add the `combine-manifests` job
Status: done.
Bring it back (it was deleted in Step 1) but sourcing artifacts from the
repackage job instead of the old Tauri build. The merging logic is identical
to what the old job did. Minimum job shape:
@@ -232,6 +280,8 @@ combine-manifests:
### Step 4 — Update `finalize-release.needs`
Status: done.
```yaml
finalize-release:
needs: [create-release, build-desktop-electron-macos, repackage-electron-as-tauri-update, publish-npm, combine-manifests, combine-electron-manifests]
@@ -239,8 +289,11 @@ finalize-release:
### Step 5 — Remove Tauri-specific code
After the transition release ships and has been out at least 2 weeks with no
rollback, remove:
Status: next safe refactoring step, after the transition release ships and has
been out at least 2 weeks with no rollback.
Do not do this in the same release as the migration. Once the transition release
has proved stable, remove:
- `packages/desktop/` (entire package — Tauri Rust + UI glue)
- Any `isTauriShell()` branches that are now dead code in
@@ -249,9 +302,13 @@ rollback, remove:
audit each before removing).
- This file (`docs/TAURI_TO_ELECTRON_CUTOVER.md`) — mission accomplished.
Do this in a separate PR. Keep the transition release workflow intact until
the cleanup lands; rolling the cleanup into the transition release itself
makes debugging much harder if the migration misbehaves for a user.
Do this in a separate PR. Keep the transition release workflow intact until the
cleanup lands; rolling the cleanup into the transition release itself makes
debugging much harder if the migration misbehaves for a user.
The manual arm64 macOS DMG workflow has already been changed to build Electron
only, so there should be no GitHub Actions path that accidentally produces a new
Tauri DMG.
## Validation before tagging the transition release