Center app icon, use --build-system xcode --arch arm64 in CI #25
Workflow file for this run
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: Release | |
| on: | |
| push: | |
| tags: | |
| - 'v*' | |
| permissions: | |
| contents: write | |
| env: | |
| APP_NAME: Macuake | |
| BUNDLE_ID: com.maquake.app | |
| jobs: | |
| build-and-release: | |
| runs-on: macos-15 | |
| steps: | |
| - uses: actions/checkout@v4 | |
| with: | |
| submodules: recursive | |
| - name: Extract version from tag | |
| id: version | |
| run: | | |
| VERSION="${GITHUB_REF_NAME#v}" | |
| echo "version=$VERSION" >> "$GITHUB_OUTPUT" | |
| echo "Building version $VERSION" | |
| - name: Check available secrets | |
| id: secrets | |
| run: | | |
| echo "has_signing=${{ secrets.CERTIFICATE_P12 != '' }}" >> "$GITHUB_OUTPUT" | |
| echo "has_notarize=${{ secrets.APPLE_ID != '' }}" >> "$GITHUB_OUTPUT" | |
| echo "has_sparkle=${{ secrets.SPARKLE_PRIVATE_KEY != '' }}" >> "$GITHUB_OUTPUT" | |
| echo "has_tap=${{ secrets.HOMEBREW_TAP_TOKEN != '' }}" >> "$GITHUB_OUTPUT" | |
| - name: Download pre-built GhosttyKit | |
| env: | |
| GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| run: ./scripts/download-ghosttykit.sh | |
| - name: Build release binary | |
| run: swift build --build-system xcode -c release --arch arm64 | |
| - name: Prepare app bundle | |
| run: | | |
| APP_BUNDLE="build/${{ env.APP_NAME }}.app" | |
| # Create bundle structure | |
| mkdir -p "$APP_BUNDLE/Contents/MacOS" | |
| mkdir -p "$APP_BUNDLE/Contents/Frameworks" | |
| mkdir -p "$APP_BUNDLE/Contents/Resources" | |
| # Update version in Info.plist | |
| /usr/libexec/PlistBuddy -c "Set :CFBundleShortVersionString ${{ steps.version.outputs.version }}" \ | |
| MaQuake/Resources/Info.plist | |
| /usr/libexec/PlistBuddy -c "Set :CFBundleVersion ${{ github.run_number }}" \ | |
| MaQuake/Resources/Info.plist | |
| # Copy binary (--build-system xcode outputs to .build/apple/Products/Release/) | |
| cp .build/apple/Products/Release/${{ env.APP_NAME }} "$APP_BUNDLE/Contents/MacOS/${{ env.APP_NAME }}" | |
| # Copy Info.plist and icon | |
| cp MaQuake/Resources/Info.plist "$APP_BUNDLE/Contents/Info.plist" | |
| cp MaQuake/Resources/maquake.icns "$APP_BUNDLE/Contents/Resources/" 2>/dev/null || true | |
| # Copy SPM resource bundles to Contents/Resources/ | |
| # --build-system xcode generates accessor that checks Bundle.main.resourceURL | |
| for bundle in .build/apple/Products/Release/*.bundle; do | |
| [ -d "$bundle" ] || continue | |
| [ -f "$bundle/Info.plist" ] || [ -f "$bundle/Contents/Info.plist" ] || continue | |
| name="$(basename "$bundle")" | |
| cp -R "$bundle" "$APP_BUNDLE/Contents/Resources/$name" | |
| done | |
| # Embed Sparkle.framework | |
| SPARKLE_SRC=$(find .build -path "*/macos-arm64*/Sparkle.framework" -type d | head -1) | |
| [ -z "$SPARKLE_SRC" ] && SPARKLE_SRC=".build/artifacts/sparkle/Sparkle/Sparkle.xcframework/macos-arm64_x86_64/Sparkle.framework" | |
| cp -R "$SPARKLE_SRC" "$APP_BUNDLE/Contents/Frameworks/Sparkle.framework" | |
| # Add rpath for embedded frameworks | |
| install_name_tool -add_rpath "@executable_path/../Frameworks" \ | |
| "$APP_BUNDLE/Contents/MacOS/${{ env.APP_NAME }}" 2>/dev/null || true | |
| - name: Import signing certificate | |
| if: steps.secrets.outputs.has_signing == 'true' | |
| env: | |
| CERTIFICATE_P12: ${{ secrets.CERTIFICATE_P12 }} | |
| CERTIFICATE_PASSWORD: ${{ secrets.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 "$CERTIFICATE_P12" | base64 --decode > "$RUNNER_TEMP/certificate.p12" | |
| security import "$RUNNER_TEMP/certificate.p12" \ | |
| -k "$KEYCHAIN_PATH" \ | |
| -P "$CERTIFICATE_PASSWORD" \ | |
| -T /usr/bin/codesign | |
| rm "$RUNNER_TEMP/certificate.p12" | |
| security set-key-partition-list -S apple-tool:,apple:,codesign: \ | |
| -s -k "$KEYCHAIN_PASSWORD" "$KEYCHAIN_PATH" | |
| security list-keychains -d user -s "$KEYCHAIN_PATH" login.keychain | |
| - name: Sign app bundle (inside-out) | |
| if: steps.secrets.outputs.has_signing == 'true' | |
| run: | | |
| APP_BUNDLE="build/${{ env.APP_NAME }}.app" | |
| IDENTITY="${{ secrets.SIGNING_IDENTITY }}" | |
| ENTITLEMENTS="MaQuake/Resources/MaQuake.entitlements" | |
| # Sign resource bundles in Resources/ | |
| for bundle in "$APP_BUNDLE"/Contents/Resources/*.bundle; do | |
| [ -d "$bundle" ] || continue | |
| codesign --force --sign "$IDENTITY" "$bundle" | |
| done | |
| # Sign Sparkle XPC services and helpers (inside-out) | |
| codesign --force --sign "$IDENTITY" --options runtime \ | |
| "$APP_BUNDLE/Contents/Frameworks/Sparkle.framework/Versions/B/XPCServices/Installer.xpc" | |
| codesign --force --sign "$IDENTITY" --options runtime \ | |
| "$APP_BUNDLE/Contents/Frameworks/Sparkle.framework/Versions/B/XPCServices/Downloader.xpc" | |
| codesign --force --sign "$IDENTITY" --options runtime \ | |
| "$APP_BUNDLE/Contents/Frameworks/Sparkle.framework/Versions/B/Autoupdate" | |
| codesign --force --sign "$IDENTITY" --options runtime \ | |
| "$APP_BUNDLE/Contents/Frameworks/Sparkle.framework/Versions/B/Updater.app" | |
| codesign --force --sign "$IDENTITY" --options runtime \ | |
| "$APP_BUNDLE/Contents/Frameworks/Sparkle.framework" | |
| # Sign main app bundle | |
| codesign --force --sign "$IDENTITY" \ | |
| --options runtime \ | |
| --entitlements "$ENTITLEMENTS" \ | |
| "$APP_BUNDLE" | |
| codesign --verify --deep --strict "$APP_BUNDLE" | |
| - name: Ad-hoc sign (when no certificate) | |
| if: steps.secrets.outputs.has_signing != 'true' | |
| run: | | |
| codesign --force --deep --sign - "build/${{ env.APP_NAME }}.app" | |
| - name: Create DMG | |
| run: | | |
| hdiutil create -volname "${{ env.APP_NAME }}" \ | |
| -srcfolder "build/${{ env.APP_NAME }}.app" \ | |
| -ov -format UDZO \ | |
| "build/${{ env.APP_NAME }}.dmg" | |
| - name: Sign DMG | |
| if: steps.secrets.outputs.has_signing == 'true' | |
| run: | | |
| codesign --force --sign "${{ secrets.SIGNING_IDENTITY }}" \ | |
| "build/${{ env.APP_NAME }}.dmg" | |
| - name: Notarize DMG | |
| if: steps.secrets.outputs.has_notarize == 'true' | |
| env: | |
| APPLE_ID: ${{ secrets.APPLE_ID }} | |
| APPLE_PASSWORD: ${{ secrets.APPLE_APP_PASSWORD }} | |
| APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }} | |
| run: | | |
| xcrun notarytool submit "build/${{ env.APP_NAME }}.dmg" \ | |
| --apple-id "$APPLE_ID" \ | |
| --password "$APPLE_PASSWORD" \ | |
| --team-id "$APPLE_TEAM_ID" \ | |
| --wait | |
| xcrun stapler staple "build/${{ env.APP_NAME }}.dmg" | |
| - name: Sign update with Sparkle | |
| if: steps.secrets.outputs.has_sparkle == 'true' | |
| env: | |
| SPARKLE_KEY: ${{ secrets.SPARKLE_PRIVATE_KEY }} | |
| run: | | |
| SPARKLE_BIN=".build/artifacts/sparkle/Sparkle/bin" | |
| echo "$SPARKLE_KEY" > "$RUNNER_TEMP/sparkle_key" | |
| "$SPARKLE_BIN/sign_update" "build/${{ env.APP_NAME }}.dmg" -f "$RUNNER_TEMP/sparkle_key" | |
| rm "$RUNNER_TEMP/sparkle_key" | |
| - name: Generate appcast | |
| if: steps.secrets.outputs.has_sparkle == 'true' | |
| env: | |
| SPARKLE_KEY: ${{ secrets.SPARKLE_PRIVATE_KEY }} | |
| run: | | |
| SPARKLE_BIN=".build/artifacts/sparkle/Sparkle/bin" | |
| echo "$SPARKLE_KEY" > "$RUNNER_TEMP/sparkle_key" | |
| mkdir -p /tmp/appcast_dir | |
| cp "build/${{ env.APP_NAME }}.dmg" /tmp/appcast_dir/ | |
| "$SPARKLE_BIN/generate_appcast" \ | |
| --ed-key-file "$RUNNER_TEMP/sparkle_key" \ | |
| --download-url-prefix "https://github.com/${{ github.repository }}/releases/download/${{ github.ref_name }}/" \ | |
| /tmp/appcast_dir | |
| rm "$RUNNER_TEMP/sparkle_key" | |
| # Fix title to lowercase branding | |
| sed -i '' 's|<title>Macuake</title>|<title>macuake</title>|' /tmp/appcast_dir/appcast.xml | |
| cp /tmp/appcast_dir/appcast.xml build/appcast.xml | |
| - name: Create GitHub Release | |
| uses: softprops/action-gh-release@v2 | |
| with: | |
| files: | | |
| build/${{ env.APP_NAME }}.dmg | |
| build/appcast.xml | |
| fail_on_unmatched_files: false | |
| generate_release_notes: true | |
| draft: false | |
| prerelease: ${{ contains(github.ref_name, 'beta') || contains(github.ref_name, 'rc') }} | |
| - name: Update Homebrew cask | |
| if: steps.secrets.outputs.has_tap == 'true' && !contains(github.ref_name, 'beta') && !contains(github.ref_name, 'rc') | |
| env: | |
| GH_TOKEN: ${{ secrets.HOMEBREW_TAP_TOKEN }} | |
| run: | | |
| VERSION="${{ steps.version.outputs.version }}" | |
| SHA=$(shasum -a 256 "build/${{ env.APP_NAME }}.dmg" | awk '{print $1}') | |
| git clone "https://x-access-token:${GH_TOKEN}@github.com/menemy/homebrew-macuake.git" /tmp/tap | |
| cd /tmp/tap | |
| python3 -c " | |
| v, s = '$VERSION', '$SHA' | |
| print(f'''cask \"macuake\" do | |
| version \"{v}\" | |
| sha256 \"{s}\" | |
| url \"https://github.com/menemy/macuake/releases/download/v#{{version}}/Macuake.dmg\" | |
| name \"Macuake\" | |
| desc \"Drop-down Quake-style terminal for macOS powered by Ghostty\" | |
| homepage \"https://macuake.com\" | |
| depends_on macos: \">= :sonoma\" | |
| app \"Macuake.app\" | |
| zap trash: [ | |
| \"~/Library/Preferences/com.macuake.terminal.plist\", | |
| \"~/Library/Application Support/macuake\", | |
| ] | |
| end''') | |
| " | sed 's/^ //' > Casks/macuake.rb | |
| git config user.name "github-actions[bot]" | |
| git config user.email "41898282+github-actions[bot]@users.noreply.github.com" | |
| git add Casks/macuake.rb | |
| if ! git diff --cached --quiet; then | |
| git commit -m "Update macuake to $VERSION" | |
| git push | |
| fi |