Add Sparkle auto-updates; move Multi-Verse into themes; UI polish #31
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: Build & Release | |
| on: | |
| push: | |
| branches: [main] | |
| tags: ['v*'] | |
| pull_request: | |
| branches: [main] | |
| # Cancel in-progress runs for the same branch | |
| concurrency: | |
| group: ${{ github.workflow }}-${{ github.ref }} | |
| cancel-in-progress: true | |
| env: | |
| SCHEME: TopPresenter | |
| PRODUCT_NAME: TopPresenter | |
| XCODE_VERSION: '16.0' | |
| jobs: | |
| build: | |
| name: Build & Package | |
| runs-on: macos-15 | |
| timeout-minutes: 30 | |
| outputs: | |
| version: ${{ steps.package.outputs.VERSION }} | |
| prerelease_tag: ${{ steps.package.outputs.PRERELEASE_TAG }} | |
| prerelease_version: ${{ steps.package.outputs.PRERELEASE_VERSION }} | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@v5 | |
| - name: Select Xcode | |
| run: sudo xcode-select -s /Applications/Xcode_${{ env.XCODE_VERSION }}.app/Contents/Developer | |
| - name: Show Xcode version | |
| run: xcodebuild -version | |
| - name: Resolve dependencies | |
| run: xcodebuild -resolvePackageDependencies -scheme "$SCHEME" -project "$PRODUCT_NAME.xcodeproj" | |
| - name: Build | |
| run: | | |
| # Monotonic CFBundleVersion so Sparkle can tell releases apart (the project's | |
| # CURRENT_PROJECT_VERSION is a fixed 1). Ad-hoc code signing ("-") so the app's | |
| # entitlements (sandbox + Sparkle XPC) actually apply and the in-app updater's | |
| # installer works — no Apple certs needed (Gatekeeper still asks for right-click | |
| # → Open, exactly as before). Swap "-" for a Developer ID identity + add | |
| # notarization later for silent updates; see UPDATES.md. | |
| xcodebuild \ | |
| -scheme "$SCHEME" \ | |
| -project "$PRODUCT_NAME.xcodeproj" \ | |
| -configuration Release \ | |
| -derivedDataPath build \ | |
| -destination 'platform=macOS' \ | |
| CODE_SIGN_IDENTITY="-" \ | |
| CODE_SIGN_STYLE=Manual \ | |
| CODE_SIGNING_REQUIRED=NO \ | |
| CODE_SIGNING_ALLOWED=YES \ | |
| DEVELOPMENT_TEAM="" \ | |
| CURRENT_PROJECT_VERSION=${{ github.run_number }} \ | |
| clean build | |
| - name: Run Unit Tests | |
| run: | | |
| xcodebuild \ | |
| -scheme "$SCHEME" \ | |
| -project "$PRODUCT_NAME.xcodeproj" \ | |
| -destination 'platform=macOS' \ | |
| -resultBundlePath TestResults.xcresult \ | |
| CODE_SIGN_IDENTITY="-" \ | |
| CODE_SIGNING_REQUIRED=NO \ | |
| CODE_SIGNING_ALLOWED=NO \ | |
| DEVELOPMENT_TEAM="" \ | |
| test || true | |
| - name: Package app | |
| id: package | |
| run: | | |
| APP_PATH="build/Build/Products/Release/$PRODUCT_NAME.app" | |
| if [ ! -d "$APP_PATH" ]; then | |
| echo "❌ App not found at $APP_PATH" | |
| find build -name "*.app" -type d | |
| exit 1 | |
| fi | |
| # Get version info | |
| VERSION=$(/usr/libexec/PlistBuddy -c "Print :CFBundleShortVersionString" "$APP_PATH/Contents/Info.plist" 2>/dev/null || echo "1.0") | |
| BUILD=$(/usr/libexec/PlistBuddy -c "Print :CFBundleVersion" "$APP_PATH/Contents/Info.plist" 2>/dev/null || echo "1") | |
| # Alpha counter is PER VERSION: bumping MARKETING_VERSION restarts at alpha.1 | |
| git fetch --tags --force --quiet | |
| ALPHA_N=$(( $(git tag -l "v$VERSION-alpha.*" | wc -l | tr -d ' ') + 1 )) | |
| echo "VERSION=$VERSION" >> $GITHUB_ENV | |
| echo "BUILD=$BUILD" >> $GITHUB_ENV | |
| echo "PRERELEASE_VERSION=$VERSION-alpha.${ALPHA_N}" >> $GITHUB_ENV | |
| echo "PRERELEASE_TAG=v$VERSION-alpha.${ALPHA_N}" >> $GITHUB_ENV | |
| echo "VERSION=$VERSION" >> $GITHUB_OUTPUT | |
| echo "PRERELEASE_VERSION=$VERSION-alpha.${ALPHA_N}" >> $GITHUB_OUTPUT | |
| echo "PRERELEASE_TAG=v$VERSION-alpha.${ALPHA_N}" >> $GITHUB_OUTPUT | |
| # Create ZIP | |
| cd "build/Build/Products/Release" | |
| ditto -c -k --keepParent "$PRODUCT_NAME.app" "$PRODUCT_NAME-$VERSION.zip" | |
| mv "$PRODUCT_NAME-$VERSION.zip" "$GITHUB_WORKSPACE/" | |
| # Create DMG | |
| hdiutil create -volname "$PRODUCT_NAME" \ | |
| -srcfolder "$PRODUCT_NAME.app" \ | |
| -ov -format UDZO \ | |
| "$GITHUB_WORKSPACE/$PRODUCT_NAME-$VERSION.dmg" | |
| - name: Upload artifact | |
| uses: actions/upload-artifact@v5 | |
| with: | |
| name: TopPresenter-${{ env.VERSION }} | |
| path: | | |
| TopPresenter-*.zip | |
| TopPresenter-*.dmg | |
| retention-days: 30 | |
| # Unique pre-release on every push to main | |
| pre-release: | |
| name: Pre-release (Alpha) | |
| needs: build | |
| if: github.ref == 'refs/heads/main' && github.event_name == 'push' | |
| runs-on: macos-15 | |
| permissions: | |
| contents: write | |
| env: | |
| # Empty until you add the repo secret — the appcast step then no-ops (CI stays green). | |
| SPARKLE_PRIVATE_KEY: ${{ secrets.SPARKLE_PRIVATE_KEY }} | |
| steps: | |
| - name: Checkout (for appcast scripts) | |
| uses: actions/checkout@v5 | |
| - name: Download artifact | |
| uses: actions/download-artifact@v5 | |
| with: | |
| pattern: TopPresenter-* | |
| merge-multiple: true | |
| - name: Get short SHA | |
| run: echo "SHORT_SHA=${GITHUB_SHA::7}" >> $GITHUB_ENV | |
| - name: Create alpha pre-release | |
| uses: softprops/action-gh-release@v2 | |
| with: | |
| tag_name: ${{ needs.build.outputs.prerelease_tag }} | |
| name: "TopPresenter ${{ needs.build.outputs.prerelease_version }}" | |
| prerelease: true | |
| generate_release_notes: true | |
| body: | | |
| ### ⚡ Automatic Alpha Build | |
| **Version:** `${{ needs.build.outputs.prerelease_version }}` | |
| **Tag:** `${{ needs.build.outputs.prerelease_tag }}` | |
| **Commit:** `${{ github.sha }}` | |
| **Branch:** `main` | |
| **Built:** ${{ github.event.head_commit.timestamp }} | |
| > ⚠️ This is an unsigned build. To open on macOS: | |
| > Right-click the app → Open → Open (bypass Gatekeeper). | |
| > Or run: `xattr -cr TopPresenter.app` | |
| --- | |
| **Commit message:** ${{ github.event.head_commit.message }} | |
| files: | | |
| TopPresenter-*.zip | |
| TopPresenter-*.dmg | |
| - name: Publish appcast (beta channel) | |
| if: ${{ env.SPARKLE_PRIVATE_KEY != '' }} | |
| env: | |
| GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| ZIP: TopPresenter-${{ needs.build.outputs.version }}.zip | |
| DOWNLOAD_URL: https://github.com/${{ github.repository }}/releases/download/${{ needs.build.outputs.prerelease_tag }}/TopPresenter-${{ needs.build.outputs.version }}.zip | |
| SHORT_VERSION: ${{ needs.build.outputs.prerelease_version }} | |
| BUILD_VERSION: ${{ github.run_number }} | |
| CHANNEL: beta | |
| REPO: ${{ github.repository }} | |
| NOTES: ${{ github.event.head_commit.message }} | |
| run: | | |
| chmod +x scripts/publish_appcast.sh | |
| scripts/publish_appcast.sh | |
| # Stable release on final version tags (v1.0.0, v1.1.0, etc.) | |
| release: | |
| name: Stable Release | |
| needs: build | |
| if: startsWith(github.ref, 'refs/tags/v') && !contains(github.ref_name, '-') | |
| runs-on: macos-15 | |
| permissions: | |
| contents: write | |
| env: | |
| SPARKLE_PRIVATE_KEY: ${{ secrets.SPARKLE_PRIVATE_KEY }} | |
| steps: | |
| - name: Checkout (for appcast scripts) | |
| uses: actions/checkout@v5 | |
| - name: Download artifact | |
| uses: actions/download-artifact@v5 | |
| with: | |
| pattern: TopPresenter-* | |
| merge-multiple: true | |
| - name: Create stable release | |
| uses: softprops/action-gh-release@v2 | |
| with: | |
| name: "TopPresenter ${{ github.ref_name }}" | |
| generate_release_notes: true | |
| body: | | |
| ### 🎉 TopPresenter ${{ github.ref_name }} | |
| > ⚠️ This is an unsigned build. To open on macOS: | |
| > Right-click the app → Open → Open (bypass Gatekeeper). | |
| > Or run: `xattr -cr TopPresenter.app` | |
| files: | | |
| TopPresenter-*.zip | |
| TopPresenter-*.dmg | |
| - name: Publish appcast (stable channel) | |
| if: ${{ env.SPARKLE_PRIVATE_KEY != '' }} | |
| env: | |
| GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| ZIP: TopPresenter-${{ needs.build.outputs.version }}.zip | |
| DOWNLOAD_URL: https://github.com/${{ github.repository }}/releases/download/${{ github.ref_name }}/TopPresenter-${{ needs.build.outputs.version }}.zip | |
| SHORT_VERSION: ${{ needs.build.outputs.version }} | |
| BUILD_VERSION: ${{ github.run_number }} | |
| CHANNEL: "" | |
| REPO: ${{ github.repository }} | |
| run: | | |
| chmod +x scripts/publish_appcast.sh | |
| scripts/publish_appcast.sh |