Beta Release #2
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: Beta Release | |
| # Manual trigger for beta releases from develop branch | |
| on: | |
| workflow_dispatch: | |
| inputs: | |
| version: | |
| description: 'Beta version (e.g., 2.8.0-beta.1)' | |
| required: true | |
| type: string | |
| dry_run: | |
| description: 'Test build without creating release' | |
| required: false | |
| default: false | |
| type: boolean | |
| jobs: | |
| validate-version: | |
| name: Validate beta version format | |
| runs-on: ubuntu-latest | |
| steps: | |
| - name: Validate version format | |
| run: | | |
| VERSION="${{ github.event.inputs.version }}" | |
| # Check if version matches beta semver pattern | |
| if [[ ! "$VERSION" =~ ^[0-9]+\.[0-9]+\.[0-9]+-(beta|alpha|rc)\.[0-9]+$ ]]; then | |
| echo "::error::Invalid version format: $VERSION" | |
| echo "Version must match pattern: X.Y.Z-beta.N (e.g., 2.8.0-beta.1)" | |
| exit 1 | |
| fi | |
| echo "Valid beta version: $VERSION" | |
| create-tag: | |
| name: Create beta tag | |
| needs: validate-version | |
| runs-on: ubuntu-latest | |
| permissions: | |
| contents: write | |
| outputs: | |
| version: ${{ github.event.inputs.version }} | |
| steps: | |
| - uses: actions/checkout@v4 | |
| with: | |
| ref: develop | |
| - name: Create and push tag | |
| if: ${{ github.event.inputs.dry_run != 'true' }} | |
| run: | | |
| VERSION="${{ github.event.inputs.version }}" | |
| git config user.name "github-actions[bot]" | |
| git config user.email "github-actions[bot]@users.noreply.github.com" | |
| git tag -a "v$VERSION" -m "Beta release v$VERSION" | |
| git push origin "v$VERSION" | |
| echo "Created tag v$VERSION" | |
| - name: Create tag only (dry run) | |
| if: ${{ github.event.inputs.dry_run == 'true' }} | |
| run: | | |
| VERSION="${{ github.event.inputs.version }}" | |
| echo "DRY RUN: Would create tag v$VERSION" | |
| # Intel build on Intel runner for native compilation | |
| build-macos-intel: | |
| needs: create-tag | |
| runs-on: macos-15-intel | |
| outputs: | |
| notarization_id: ${{ steps.notarize.outputs.notarization-id }} | |
| dmg_file: ${{ steps.notarize.outputs.dmg-file }} | |
| steps: | |
| - uses: actions/checkout@v4 | |
| with: | |
| # Use tag for real releases, develop branch for dry runs | |
| ref: ${{ github.event.inputs.dry_run == 'true' && 'develop' || format('v{0}', needs.create-tag.outputs.version) }} | |
| - name: Setup Python | |
| uses: actions/setup-python@v5 | |
| with: | |
| python-version: '3.11' | |
| - name: Setup Node.js and install dependencies | |
| uses: ./.github/actions/setup-node-frontend | |
| - name: Install Rust toolchain (for building native Python packages) | |
| uses: dtolnay/rust-toolchain@stable | |
| - name: Cache pip wheel cache (for compiled packages like real_ladybug) | |
| uses: actions/cache@v4 | |
| with: | |
| path: ~/Library/Caches/pip | |
| key: pip-wheel-${{ runner.os }}-x64-${{ hashFiles('apps/backend/requirements.txt') }} | |
| restore-keys: | | |
| pip-wheel-${{ runner.os }}-x64- | |
| - name: Cache bundled Python | |
| uses: actions/cache@v4 | |
| with: | |
| path: apps/frontend/python-runtime | |
| key: python-bundle-${{ runner.os }}-x64-3.12.8-rust-${{ hashFiles('apps/backend/requirements.txt') }} | |
| restore-keys: | | |
| python-bundle-${{ runner.os }}-x64-3.12.8-rust- | |
| - name: Build application | |
| run: cd apps/frontend && npm run build | |
| env: | |
| SENTRY_DSN: ${{ secrets.SENTRY_DSN }} | |
| SENTRY_TRACES_SAMPLE_RATE: ${{ secrets.SENTRY_TRACES_SAMPLE_RATE }} | |
| SENTRY_PROFILES_SAMPLE_RATE: ${{ secrets.SENTRY_PROFILES_SAMPLE_RATE }} | |
| - name: Package macOS (Intel) | |
| run: | | |
| VERSION="${{ needs.create-tag.outputs.version }}" | |
| cd apps/frontend && npm run package:mac -- --x64 --config.extraMetadata.version="$VERSION" | |
| env: | |
| GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| CSC_LINK: ${{ secrets.MAC_CERTIFICATE }} | |
| CSC_KEY_PASSWORD: ${{ secrets.MAC_CERTIFICATE_PASSWORD }} | |
| SENTRY_DSN: ${{ secrets.SENTRY_DSN }} | |
| SENTRY_TRACES_SAMPLE_RATE: ${{ secrets.SENTRY_TRACES_SAMPLE_RATE }} | |
| SENTRY_PROFILES_SAMPLE_RATE: ${{ secrets.SENTRY_PROFILES_SAMPLE_RATE }} | |
| - name: Submit notarization (async) | |
| id: notarize | |
| uses: ./.github/actions/submit-macos-notarization | |
| with: | |
| apple-id: ${{ secrets.APPLE_ID }} | |
| apple-app-specific-password: ${{ secrets.APPLE_APP_SPECIFIC_PASSWORD }} | |
| apple-team-id: ${{ secrets.APPLE_TEAM_ID }} | |
| - name: Upload artifacts | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: macos-intel-builds | |
| path: | | |
| apps/frontend/dist/*.dmg | |
| apps/frontend/dist/*.zip | |
| apps/frontend/dist/*.yml | |
| # Apple Silicon build on ARM64 runner for native compilation | |
| build-macos-arm64: | |
| needs: create-tag | |
| runs-on: macos-15 | |
| outputs: | |
| notarization_id: ${{ steps.notarize.outputs.notarization-id }} | |
| dmg_file: ${{ steps.notarize.outputs.dmg-file }} | |
| steps: | |
| - uses: actions/checkout@v4 | |
| with: | |
| # Use tag for real releases, develop branch for dry runs | |
| ref: ${{ github.event.inputs.dry_run == 'true' && 'develop' || format('v{0}', needs.create-tag.outputs.version) }} | |
| - name: Setup Python | |
| uses: actions/setup-python@v5 | |
| with: | |
| python-version: '3.11' | |
| - name: Setup Node.js and install dependencies | |
| uses: ./.github/actions/setup-node-frontend | |
| - name: Cache pip wheel cache | |
| uses: actions/cache@v4 | |
| with: | |
| path: ~/Library/Caches/pip | |
| key: pip-wheel-${{ runner.os }}-arm64-${{ hashFiles('apps/backend/requirements.txt') }} | |
| restore-keys: | | |
| pip-wheel-${{ runner.os }}-arm64- | |
| - name: Cache bundled Python | |
| uses: actions/cache@v4 | |
| with: | |
| path: apps/frontend/python-runtime | |
| key: python-bundle-${{ runner.os }}-arm64-3.12.8-${{ hashFiles('apps/backend/requirements.txt') }} | |
| restore-keys: | | |
| python-bundle-${{ runner.os }}-arm64-3.12.8- | |
| - name: Build application | |
| run: cd apps/frontend && npm run build | |
| env: | |
| SENTRY_DSN: ${{ secrets.SENTRY_DSN }} | |
| SENTRY_TRACES_SAMPLE_RATE: ${{ secrets.SENTRY_TRACES_SAMPLE_RATE }} | |
| SENTRY_PROFILES_SAMPLE_RATE: ${{ secrets.SENTRY_PROFILES_SAMPLE_RATE }} | |
| - name: Package macOS (Apple Silicon) | |
| run: | | |
| VERSION="${{ needs.create-tag.outputs.version }}" | |
| cd apps/frontend && npm run package:mac -- --arm64 --config.extraMetadata.version="$VERSION" | |
| env: | |
| GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| CSC_LINK: ${{ secrets.MAC_CERTIFICATE }} | |
| CSC_KEY_PASSWORD: ${{ secrets.MAC_CERTIFICATE_PASSWORD }} | |
| SENTRY_DSN: ${{ secrets.SENTRY_DSN }} | |
| SENTRY_TRACES_SAMPLE_RATE: ${{ secrets.SENTRY_TRACES_SAMPLE_RATE }} | |
| SENTRY_PROFILES_SAMPLE_RATE: ${{ secrets.SENTRY_PROFILES_SAMPLE_RATE }} | |
| - name: Submit notarization (async) | |
| id: notarize | |
| uses: ./.github/actions/submit-macos-notarization | |
| with: | |
| apple-id: ${{ secrets.APPLE_ID }} | |
| apple-app-specific-password: ${{ secrets.APPLE_APP_SPECIFIC_PASSWORD }} | |
| apple-team-id: ${{ secrets.APPLE_TEAM_ID }} | |
| - name: Upload artifacts | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: macos-arm64-builds | |
| path: | | |
| apps/frontend/dist/*.dmg | |
| apps/frontend/dist/*.zip | |
| apps/frontend/dist/*.yml | |
| build-windows: | |
| needs: create-tag | |
| runs-on: windows-latest | |
| permissions: | |
| id-token: write # Required for OIDC authentication with Azure | |
| contents: read | |
| env: | |
| # Job-level env so AZURE_CLIENT_ID is available for step-level if conditions | |
| AZURE_CLIENT_ID: ${{ secrets.AZURE_CLIENT_ID }} | |
| steps: | |
| - uses: actions/checkout@v4 | |
| with: | |
| # Use tag for real releases, develop branch for dry runs | |
| ref: ${{ github.event.inputs.dry_run == 'true' && 'develop' || format('v{0}', needs.create-tag.outputs.version) }} | |
| - name: Setup Python | |
| uses: actions/setup-python@v5 | |
| with: | |
| python-version: '3.11' | |
| - name: Setup Node.js and install dependencies | |
| uses: ./.github/actions/setup-node-frontend | |
| - name: Cache pip wheel cache | |
| uses: actions/cache@v4 | |
| with: | |
| path: ~\AppData\Local\pip\Cache | |
| key: pip-wheel-${{ runner.os }}-x64-${{ hashFiles('apps/backend/requirements.txt') }} | |
| restore-keys: | | |
| pip-wheel-${{ runner.os }}-x64- | |
| - name: Cache bundled Python | |
| uses: actions/cache@v4 | |
| with: | |
| path: apps/frontend/python-runtime | |
| key: python-bundle-${{ runner.os }}-x64-3.12.8-${{ hashFiles('apps/backend/requirements.txt') }} | |
| restore-keys: | | |
| python-bundle-${{ runner.os }}-x64-3.12.8- | |
| - name: Build application | |
| run: cd apps/frontend && npm run build | |
| env: | |
| SENTRY_DSN: ${{ secrets.SENTRY_DSN }} | |
| SENTRY_TRACES_SAMPLE_RATE: ${{ secrets.SENTRY_TRACES_SAMPLE_RATE }} | |
| SENTRY_PROFILES_SAMPLE_RATE: ${{ secrets.SENTRY_PROFILES_SAMPLE_RATE }} | |
| - name: Package Windows | |
| shell: bash | |
| run: | | |
| VERSION="${{ needs.create-tag.outputs.version }}" | |
| cd apps/frontend && npm run package:win -- --config.extraMetadata.version="$VERSION" | |
| env: | |
| GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| # Disable electron-builder's built-in signing (we use Azure Trusted Signing instead) | |
| CSC_IDENTITY_AUTO_DISCOVERY: false | |
| SENTRY_DSN: ${{ secrets.SENTRY_DSN }} | |
| SENTRY_TRACES_SAMPLE_RATE: ${{ secrets.SENTRY_TRACES_SAMPLE_RATE }} | |
| SENTRY_PROFILES_SAMPLE_RATE: ${{ secrets.SENTRY_PROFILES_SAMPLE_RATE }} | |
| - name: Azure Login (OIDC) | |
| if: env.AZURE_CLIENT_ID != '' | |
| uses: azure/login@v2 | |
| with: | |
| client-id: ${{ secrets.AZURE_CLIENT_ID }} | |
| tenant-id: ${{ secrets.AZURE_TENANT_ID }} | |
| subscription-id: ${{ secrets.AZURE_SUBSCRIPTION_ID }} | |
| - name: Sign Windows executable with Azure Trusted Signing | |
| if: env.AZURE_CLIENT_ID != '' | |
| uses: azure/trusted-signing-action@v0.5.11 | |
| with: | |
| endpoint: https://neu.codesigning.azure.net/ | |
| trusted-signing-account-name: ${{ secrets.AZURE_SIGNING_ACCOUNT }} | |
| certificate-profile-name: ${{ secrets.AZURE_CERTIFICATE_PROFILE }} | |
| files-folder: apps/frontend/dist | |
| files-folder-filter: exe | |
| file-digest: SHA256 | |
| timestamp-rfc3161: http://timestamp.acs.microsoft.com | |
| timestamp-digest: SHA256 | |
| - name: Verify Windows executable is signed | |
| if: env.AZURE_CLIENT_ID != '' | |
| shell: pwsh | |
| run: | | |
| cd apps/frontend/dist | |
| $exeFile = Get-ChildItem -Filter "*.exe" | Select-Object -First 1 | |
| if ($exeFile) { | |
| Write-Host "Verifying signature on $($exeFile.Name)..." | |
| $sig = Get-AuthenticodeSignature -FilePath $exeFile.FullName | |
| if ($sig.Status -ne 'Valid') { | |
| Write-Host "::error::Signature verification failed: $($sig.Status)" | |
| Write-Host "::error::Status Message: $($sig.StatusMessage)" | |
| exit 1 | |
| } | |
| Write-Host "✅ Signature verified successfully" | |
| Write-Host " Subject: $($sig.SignerCertificate.Subject)" | |
| Write-Host " Issuer: $($sig.SignerCertificate.Issuer)" | |
| Write-Host " Thumbprint: $($sig.SignerCertificate.Thumbprint)" | |
| } else { | |
| Write-Host "::error::No .exe file found to verify" | |
| exit 1 | |
| } | |
| - name: Regenerate checksums after signing | |
| if: env.AZURE_CLIENT_ID != '' | |
| shell: pwsh | |
| run: | | |
| $ErrorActionPreference = "Stop" | |
| cd apps/frontend/dist | |
| # Find the installer exe (electron-builder names it with "Setup" or just the app name) | |
| # electron-builder produces one installer exe per build | |
| $exeFiles = Get-ChildItem -Filter "*.exe" | |
| if ($exeFiles.Count -eq 0) { | |
| Write-Host "::error::No .exe files found in dist folder" | |
| exit 1 | |
| } | |
| Write-Host "Found $($exeFiles.Count) exe file(s): $($exeFiles.Name -join ', ')" | |
| $ymlFile = "latest.yml" | |
| if (-not (Test-Path $ymlFile)) { | |
| Write-Host "::error::$ymlFile not found - cannot update checksums" | |
| exit 1 | |
| } | |
| $content = Get-Content $ymlFile -Raw | |
| $originalContent = $content | |
| # Process each exe file and update its hash in latest.yml | |
| foreach ($exeFile in $exeFiles) { | |
| Write-Host "Processing $($exeFile.Name)..." | |
| # Compute SHA512 hash and convert to base64 (electron-builder format) | |
| $bytes = [System.IO.File]::ReadAllBytes($exeFile.FullName) | |
| $sha512 = [System.Security.Cryptography.SHA512]::Create() | |
| $hashBytes = $sha512.ComputeHash($bytes) | |
| $hash = [System.Convert]::ToBase64String($hashBytes) | |
| $size = $exeFile.Length | |
| Write-Host " Hash: $hash" | |
| Write-Host " Size: $size" | |
| } | |
| # For electron-builder, latest.yml has a single file entry for the installer | |
| # Update the sha512 and size for the primary exe (first one, typically the installer) | |
| $primaryExe = $exeFiles | Select-Object -First 1 | |
| $bytes = [System.IO.File]::ReadAllBytes($primaryExe.FullName) | |
| $sha512 = [System.Security.Cryptography.SHA512]::Create() | |
| $hashBytes = $sha512.ComputeHash($bytes) | |
| $hash = [System.Convert]::ToBase64String($hashBytes) | |
| $size = $primaryExe.Length | |
| # Update sha512 hash (base64 pattern: alphanumeric, +, /, =) | |
| $content = $content -replace 'sha512: [A-Za-z0-9+/=]+', "sha512: $hash" | |
| # Update size | |
| $content = $content -replace 'size: \d+', "size: $size" | |
| if ($content -eq $originalContent) { | |
| Write-Host "::error::Checksum replacement failed - content unchanged. Check if latest.yml format has changed." | |
| exit 1 | |
| } | |
| Set-Content -Path $ymlFile -Value $content -NoNewline | |
| Write-Host "✅ Updated $ymlFile with new base64 hash and size for $($primaryExe.Name)" | |
| - name: Skip signing notice | |
| if: env.AZURE_CLIENT_ID == '' | |
| run: echo "::warning::Windows signing skipped - AZURE_CLIENT_ID not configured. The .exe will be unsigned." | |
| - name: Upload artifacts | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: windows-builds | |
| path: | | |
| apps/frontend/dist/*.exe | |
| apps/frontend/dist/*.yml | |
| build-linux: | |
| needs: create-tag | |
| runs-on: ubuntu-latest | |
| steps: | |
| - uses: actions/checkout@v4 | |
| with: | |
| # Use tag for real releases, develop branch for dry runs | |
| ref: ${{ github.event.inputs.dry_run == 'true' && 'develop' || format('v{0}', needs.create-tag.outputs.version) }} | |
| - name: Setup Python | |
| uses: actions/setup-python@v5 | |
| with: | |
| python-version: '3.11' | |
| - name: Setup Node.js and install dependencies | |
| uses: ./.github/actions/setup-node-frontend | |
| - name: Setup Flatpak | |
| run: | | |
| set -e | |
| sudo apt-get update | |
| sudo apt-get install -y flatpak flatpak-builder | |
| flatpak remote-add --user --if-not-exists flathub https://flathub.org/repo/flathub.flatpakrepo | |
| flatpak install -y --user flathub org.freedesktop.Platform//25.08 org.freedesktop.Sdk//25.08 | |
| flatpak install -y --user flathub org.electronjs.Electron2.BaseApp//25.08 | |
| - name: Cache pip wheel cache | |
| uses: actions/cache@v4 | |
| with: | |
| path: ~/.cache/pip | |
| key: pip-wheel-${{ runner.os }}-x64-${{ hashFiles('apps/backend/requirements.txt') }} | |
| restore-keys: | | |
| pip-wheel-${{ runner.os }}-x64- | |
| - name: Cache bundled Python | |
| uses: actions/cache@v4 | |
| with: | |
| path: apps/frontend/python-runtime | |
| key: python-bundle-${{ runner.os }}-x64-3.12.8-${{ hashFiles('apps/backend/requirements.txt') }} | |
| restore-keys: | | |
| python-bundle-${{ runner.os }}-x64-3.12.8- | |
| - name: Build application | |
| run: cd apps/frontend && npm run build | |
| env: | |
| SENTRY_DSN: ${{ secrets.SENTRY_DSN }} | |
| SENTRY_TRACES_SAMPLE_RATE: ${{ secrets.SENTRY_TRACES_SAMPLE_RATE }} | |
| SENTRY_PROFILES_SAMPLE_RATE: ${{ secrets.SENTRY_PROFILES_SAMPLE_RATE }} | |
| - name: Package Linux | |
| run: | | |
| VERSION="${{ needs.create-tag.outputs.version }}" | |
| cd apps/frontend && npm run package:linux -- --config.extraMetadata.version="$VERSION" | |
| env: | |
| GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| SENTRY_DSN: ${{ secrets.SENTRY_DSN }} | |
| SENTRY_TRACES_SAMPLE_RATE: ${{ secrets.SENTRY_TRACES_SAMPLE_RATE }} | |
| SENTRY_PROFILES_SAMPLE_RATE: ${{ secrets.SENTRY_PROFILES_SAMPLE_RATE }} | |
| - name: Upload artifacts | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: linux-builds | |
| path: | | |
| apps/frontend/dist/*.AppImage | |
| apps/frontend/dist/*.deb | |
| apps/frontend/dist/*.flatpak | |
| apps/frontend/dist/*.yml | |
| # Finalize macOS notarization (runs in parallel with Windows/Linux builds) | |
| finalize-notarization: | |
| needs: [build-macos-intel, build-macos-arm64] | |
| runs-on: macos-latest | |
| steps: | |
| - uses: actions/checkout@v4 | |
| - name: Download Intel DMG | |
| uses: actions/download-artifact@v4 | |
| with: | |
| name: macos-intel-builds | |
| path: intel | |
| - name: Download ARM64 DMG | |
| uses: actions/download-artifact@v4 | |
| with: | |
| name: macos-arm64-builds | |
| path: arm64 | |
| - name: Wait for notarization and staple | |
| uses: ./.github/actions/finalize-macos-notarization | |
| with: | |
| apple-id: ${{ secrets.APPLE_ID }} | |
| apple-app-specific-password: ${{ secrets.APPLE_APP_SPECIFIC_PASSWORD }} | |
| apple-team-id: ${{ secrets.APPLE_TEAM_ID }} | |
| intel-notarization-id: ${{ needs.build-macos-intel.outputs.notarization_id }} | |
| arm64-notarization-id: ${{ needs.build-macos-arm64.outputs.notarization_id }} | |
| intel-dmg-file: ${{ needs.build-macos-intel.outputs.dmg_file }} | |
| arm64-dmg-file: ${{ needs.build-macos-arm64.outputs.dmg_file }} | |
| - name: Upload stapled Intel DMG | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: macos-intel-stapled | |
| path: intel/*.dmg | |
| - name: Upload stapled ARM64 DMG | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: macos-arm64-stapled | |
| path: arm64/*.dmg | |
| create-release: | |
| needs: [create-tag, finalize-notarization, build-windows, build-linux] | |
| runs-on: ubuntu-latest | |
| if: ${{ github.event.inputs.dry_run != 'true' }} | |
| permissions: | |
| contents: write | |
| steps: | |
| - uses: actions/checkout@v4 | |
| with: | |
| ref: v${{ needs.create-tag.outputs.version }} | |
| fetch-depth: 0 | |
| - name: Download all artifacts | |
| uses: actions/download-artifact@v4 | |
| with: | |
| path: dist | |
| - name: Flatten binary artifacts | |
| run: | | |
| mkdir -p release-assets | |
| # Copy stapled macOS DMGs (from finalize-notarization job) | |
| # Validate that stapled DMGs exist before copying | |
| if ! find dist/macos-intel-stapled dist/macos-arm64-stapled -type f -name "*.dmg" 2>/dev/null | grep -q .; then | |
| echo "::warning::No stapled DMGs found. Using un-stapled DMGs from build artifacts." | |
| find dist/macos-intel-builds dist/macos-arm64-builds -type f -name "*.dmg" -exec cp {} release-assets/ \; 2>/dev/null || true | |
| else | |
| find dist/macos-intel-stapled dist/macos-arm64-stapled -type f -name "*.dmg" -exec cp {} release-assets/ \; 2>/dev/null || true | |
| fi | |
| # Copy other macOS artifacts (zip, yml, blockmap for delta updates) from original build | |
| find dist/macos-intel-builds dist/macos-arm64-builds -type f \( -name "*.zip" -o -name "*.yml" -o -name "*.blockmap" \) -exec cp {} release-assets/ \; 2>/dev/null || true | |
| # Copy Windows and Linux artifacts (including blockmap for delta updates) | |
| find dist/windows-builds dist/linux-builds -type f \( -name "*.exe" -o -name "*.AppImage" -o -name "*.deb" -o -name "*.flatpak" -o -name "*.yml" -o -name "*.blockmap" \) -exec cp {} release-assets/ \; 2>/dev/null || true | |
| # Validate that at least one artifact was copied | |
| artifact_count=$(find release-assets -type f \( -name "*.dmg" -o -name "*.zip" -o -name "*.exe" -o -name "*.AppImage" -o -name "*.deb" -o -name "*.flatpak" \) | wc -l) | |
| if [ "$artifact_count" -eq 0 ]; then | |
| echo "::error::No build artifacts found! Expected .dmg, .zip, .exe, .AppImage, .deb, or .flatpak files." | |
| exit 1 | |
| fi | |
| echo "Found $artifact_count binary artifact(s):" | |
| ls -la release-assets/ | |
| # Merge macOS manifests from Intel and ARM64 builds | |
| # See: https://github.com/electron-userland/electron-builder/issues/5592 | |
| - name: Merge macOS manifests | |
| uses: ./.github/actions/merge-macos-manifests | |
| with: | |
| dist-path: dist | |
| output-path: release-assets | |
| copy-other-manifests: 'true' | |
| - name: Rename and validate beta manifests | |
| run: | | |
| cd release-assets | |
| echo "=== Current manifest files ===" | |
| ls -la *.yml 2>/dev/null || echo "No yml files found yet" | |
| # electron-builder generates latest*.yml files by default | |
| # For beta channel, electron-updater expects beta*.yml files | |
| # Rename: latest.yml -> beta.yml, latest-mac.yml -> beta-mac.yml, latest-linux.yml -> beta-linux.yml | |
| # Windows: latest.yml -> beta.yml | |
| if [ -f "latest.yml" ]; then | |
| echo "Renaming latest.yml -> beta.yml (Windows)" | |
| mv latest.yml beta.yml | |
| fi | |
| # macOS: latest-mac.yml -> beta-mac.yml | |
| if [ -f "latest-mac.yml" ]; then | |
| echo "Renaming latest-mac.yml -> beta-mac.yml (macOS)" | |
| mv latest-mac.yml beta-mac.yml | |
| fi | |
| # Linux: latest-linux.yml -> beta-linux.yml | |
| if [ -f "latest-linux.yml" ]; then | |
| echo "Renaming latest-linux.yml -> beta-linux.yml (Linux)" | |
| mv latest-linux.yml beta-linux.yml | |
| fi | |
| # Linux ARM64: latest-linux-arm64.yml -> beta-linux-arm64.yml (if exists) | |
| if [ -f "latest-linux-arm64.yml" ]; then | |
| echo "Renaming latest-linux-arm64.yml -> beta-linux-arm64.yml (Linux ARM64)" | |
| mv latest-linux-arm64.yml beta-linux-arm64.yml | |
| fi | |
| echo "" | |
| echo "=== Beta manifest files after rename ===" | |
| ls -la *.yml 2>/dev/null || echo "No yml files found" | |
| # Validate required beta manifests exist | |
| missing_manifests="" | |
| if [ ! -f "beta-mac.yml" ]; then | |
| missing_manifests="$missing_manifests beta-mac.yml" | |
| fi | |
| if [ ! -f "beta.yml" ]; then | |
| missing_manifests="$missing_manifests beta.yml" | |
| fi | |
| if [ ! -f "beta-linux.yml" ]; then | |
| missing_manifests="$missing_manifests beta-linux.yml" | |
| fi | |
| if [ -n "$missing_manifests" ]; then | |
| echo "::error::Missing required beta manifests:$missing_manifests" | |
| echo "::error::Auto-update will fail on affected platforms without these files!" | |
| exit 1 | |
| fi | |
| echo "" | |
| echo "All required beta manifests present:" | |
| echo " - beta-mac.yml (macOS)" | |
| echo " - beta.yml (Windows)" | |
| echo " - beta-linux.yml (Linux)" | |
| - name: Generate checksums | |
| run: | | |
| cd release-assets | |
| sha256sum ./* > checksums.sha256 | |
| cat checksums.sha256 | |
| - name: Create Beta Release | |
| uses: softprops/action-gh-release@v2 | |
| with: | |
| tag_name: v${{ needs.create-tag.outputs.version }} | |
| name: v${{ needs.create-tag.outputs.version }} (Beta) | |
| body: | | |
| ## Beta Release v${{ needs.create-tag.outputs.version }} | |
| This is a **beta release** for testing new features. It may contain bugs or incomplete functionality. | |
| ### How to opt-in to beta updates | |
| 1. Open Auto Code | |
| 2. Go to Settings > Updates | |
| 3. Enable "Beta Updates" toggle | |
| ### Reporting Issues | |
| Please report any issues at https://github.com/OBenner/Auto-Coding/issues | |
| --- | |
| **Full Changelog**: https://github.com/${{ github.repository }}/compare/main...v${{ needs.create-tag.outputs.version }} | |
| files: release-assets/* | |
| draft: false | |
| prerelease: true | |
| env: | |
| GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| dry-run-summary: | |
| needs: [create-tag, finalize-notarization, build-windows, build-linux] | |
| runs-on: ubuntu-latest | |
| if: ${{ github.event.inputs.dry_run == 'true' }} | |
| steps: | |
| - uses: actions/checkout@v4 | |
| - name: Download all artifacts | |
| uses: actions/download-artifact@v4 | |
| with: | |
| path: dist | |
| - name: Flatten binary artifacts | |
| run: | | |
| mkdir -p release-assets | |
| # Copy stapled macOS DMGs (from finalize-notarization job) | |
| find dist/macos-intel-stapled dist/macos-arm64-stapled -type f -name "*.dmg" -exec cp {} release-assets/ \; 2>/dev/null || true | |
| # Copy other macOS artifacts (zip, yml, blockmap for delta updates) from original build | |
| find dist/macos-intel-builds dist/macos-arm64-builds -type f \( -name "*.zip" -o -name "*.yml" -o -name "*.blockmap" \) -exec cp {} release-assets/ \; 2>/dev/null || true | |
| # Copy Windows and Linux artifacts (including blockmap for delta updates) | |
| find dist/windows-builds dist/linux-builds -type f \( -name "*.exe" -o -name "*.AppImage" -o -name "*.deb" -o -name "*.flatpak" -o -name "*.yml" -o -name "*.blockmap" \) -exec cp {} release-assets/ \; 2>/dev/null || true | |
| # Merge macOS manifests (same logic as real release) | |
| - name: Merge macOS manifests | |
| uses: ./.github/actions/merge-macos-manifests | |
| with: | |
| dist-path: dist | |
| output-path: release-assets | |
| copy-other-manifests: 'true' | |
| - name: Validate and rename beta manifests | |
| run: | | |
| cd release-assets | |
| # Rename latest*.yml to beta*.yml | |
| [ -f "latest.yml" ] && mv latest.yml beta.yml | |
| [ -f "latest-mac.yml" ] && mv latest-mac.yml beta-mac.yml | |
| [ -f "latest-linux.yml" ] && mv latest-linux.yml beta-linux.yml | |
| [ -f "latest-linux-arm64.yml" ] && mv latest-linux-arm64.yml beta-linux-arm64.yml | |
| # Validate required manifests | |
| missing="" | |
| [ ! -f "beta-mac.yml" ] && missing="$missing beta-mac.yml" | |
| [ ! -f "beta.yml" ] && missing="$missing beta.yml" | |
| [ ! -f "beta-linux.yml" ] && missing="$missing beta-linux.yml" | |
| if [ -n "$missing" ]; then | |
| echo "::warning::DRY RUN: Missing required beta manifests:$missing" | |
| echo "MANIFEST_STATUS=FAILED" >> $GITHUB_ENV | |
| else | |
| echo "MANIFEST_STATUS=PASSED" >> $GITHUB_ENV | |
| # Show merged manifest content for verification | |
| echo "" | |
| echo "=== beta-mac.yml content (should have both architectures) ===" | |
| cat beta-mac.yml | |
| fi | |
| - name: Dry run summary | |
| run: | | |
| echo "## Beta Release Dry Run Complete" >> $GITHUB_STEP_SUMMARY | |
| echo "" >> $GITHUB_STEP_SUMMARY | |
| echo "**Version:** ${{ needs.create-tag.outputs.version }}" >> $GITHUB_STEP_SUMMARY | |
| echo "" >> $GITHUB_STEP_SUMMARY | |
| echo "### Build Artifacts" >> $GITHUB_STEP_SUMMARY | |
| echo "\`\`\`" >> $GITHUB_STEP_SUMMARY | |
| find dist -type f \( -name "*.dmg" -o -name "*.zip" -o -name "*.exe" -o -name "*.AppImage" -o -name "*.deb" -o -name "*.flatpak" \) >> $GITHUB_STEP_SUMMARY | |
| echo "\`\`\`" >> $GITHUB_STEP_SUMMARY | |
| echo "" >> $GITHUB_STEP_SUMMARY | |
| echo "### Update Manifests (Required for Auto-Update)" >> $GITHUB_STEP_SUMMARY | |
| if [ "$MANIFEST_STATUS" = "PASSED" ]; then | |
| echo "All required beta manifests present:" >> $GITHUB_STEP_SUMMARY | |
| echo "- beta-mac.yml (macOS)" >> $GITHUB_STEP_SUMMARY | |
| echo "- beta.yml (Windows)" >> $GITHUB_STEP_SUMMARY | |
| echo "- beta-linux.yml (Linux)" >> $GITHUB_STEP_SUMMARY | |
| else | |
| echo "**WARNING: Missing required manifests! Auto-update will fail.**" >> $GITHUB_STEP_SUMMARY | |
| echo "Check build logs for details." >> $GITHUB_STEP_SUMMARY | |
| fi | |
| echo "" >> $GITHUB_STEP_SUMMARY | |
| echo "To create a real release, run this workflow again with dry_run unchecked." >> $GITHUB_STEP_SUMMARY |