diff --git a/.github/workflows/prerelease.yml b/.github/workflows/prerelease.yml new file mode 100644 index 0000000..085179f --- /dev/null +++ b/.github/workflows/prerelease.yml @@ -0,0 +1,168 @@ +name: Build Pre-release + +on: + release: + types: [prereleased] + workflow_dispatch: + inputs: + version: + description: 'Version number (e.g., 1.0.0-beta.1)' + required: true + type: string + +permissions: + contents: write + +env: + APP_NAME: BetterCapture + SCHEME: BetterCapture + XCODE_VERSION: '26.0' + +jobs: + build: + runs-on: macos-15 + timeout-minutes: 20 + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Select Xcode version + run: sudo xcode-select -s /Applications/Xcode_${{ env.XCODE_VERSION }}.app + + - name: Show Xcode version + run: xcodebuild -version + + - name: Import signing certificate + env: + APPLE_CERTIFICATE_BASE64: ${{ secrets.APPLE_CERTIFICATE_BASE64 }} + 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) + + # Create keychain + 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 + CERTIFICATE_PATH=$RUNNER_TEMP/certificate.p12 + echo "$APPLE_CERTIFICATE_BASE64" | base64 --decode -o "$CERTIFICATE_PATH" + security import "$CERTIFICATE_PATH" -P "$APPLE_CERTIFICATE_PASSWORD" -A -t cert -f pkcs12 -k "$KEYCHAIN_PATH" + security set-key-partition-list -S apple-tool:,apple: -k "$KEYCHAIN_PASSWORD" "$KEYCHAIN_PATH" + security list-keychain -d user -s "$KEYCHAIN_PATH" + + # Clean up certificate file + rm "$CERTIFICATE_PATH" + + - name: Determine version + id: version + run: | + if [ "${{ github.event_name }}" == "release" ]; then + VERSION="${{ github.event.release.tag_name }}" + # Remove 'v' prefix if present + VERSION="${VERSION#v}" + else + VERSION="${{ inputs.version }}" + fi + echo "version=$VERSION" >> $GITHUB_OUTPUT + echo "Building pre-release version: $VERSION" + + - name: Update version in project + run: | + VERSION=${{ steps.version.outputs.version }} + # Extract major.minor for MARKETING_VERSION (strip pre-release suffix) + MARKETING_VERSION=$(echo "$VERSION" | grep -oE '^[0-9]+\.[0-9]+') + agvtool new-marketing-version "$MARKETING_VERSION" + agvtool new-version -all 1 + + - name: Build application + run: | + xcodebuild archive \ + -project "${{ env.APP_NAME }}.xcodeproj" \ + -scheme "${{ env.SCHEME }}" \ + -configuration Release \ + -archivePath "$RUNNER_TEMP/${{ env.APP_NAME }}.xcarchive" \ + -destination "generic/platform=macOS" \ + DEVELOPMENT_TEAM="${{ secrets.APPLE_TEAM_ID }}" \ + CODE_SIGN_IDENTITY="Developer ID Application" \ + CODE_SIGN_STYLE=Manual \ + OTHER_CODE_SIGN_FLAGS="--timestamp --options=runtime" \ + | xcbeautify --renderer github-actions || true + + - name: Export application + run: | + # Create export options plist + cat > "$RUNNER_TEMP/ExportOptions.plist" << EOF + + + + + method + developer-id + teamID + ${{ secrets.APPLE_TEAM_ID }} + signingStyle + manual + signingCertificate + Developer ID Application + + + EOF + + xcodebuild -exportArchive \ + -archivePath "$RUNNER_TEMP/${{ env.APP_NAME }}.xcarchive" \ + -exportPath "$RUNNER_TEMP/export" \ + -exportOptionsPlist "$RUNNER_TEMP/ExportOptions.plist" + + - name: Create DMG + run: | + APP_PATH="$RUNNER_TEMP/export/${{ env.APP_NAME }}.app" + DMG_PATH="$RUNNER_TEMP/${{ env.APP_NAME }}-${{ steps.version.outputs.version }}-arm64.dmg" + + # Create temporary directory for DMG contents + DMG_TEMP="$RUNNER_TEMP/dmg-contents" + mkdir -p "$DMG_TEMP" + + # Copy app to temporary directory + cp -R "$APP_PATH" "$DMG_TEMP/" + + # Create symbolic link to Applications + ln -s /Applications "$DMG_TEMP/Applications" + + # Create DMG + hdiutil create -volname "${{ env.APP_NAME }}" \ + -srcfolder "$DMG_TEMP" \ + -ov -format UDZO \ + "$DMG_PATH" + + # Clean up + rm -rf "$DMG_TEMP" + + echo "DMG_PATH=$DMG_PATH" >> $GITHUB_ENV + + - name: Upload DMG artifact + uses: actions/upload-artifact@v4 + with: + name: ${{ env.APP_NAME }}-${{ steps.version.outputs.version }}-arm64.dmg + path: ${{ env.DMG_PATH }} + if-no-files-found: error + + - name: Upload to pre-release + if: github.event_name == 'release' + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + gh release upload "${{ github.event.release.tag_name }}" \ + "${{ env.DMG_PATH }}" \ + --clobber + + - name: Clean up keychain + if: always() + run: | + KEYCHAIN_PATH=$RUNNER_TEMP/app-signing.keychain-db + if [ -f "$KEYCHAIN_PATH" ]; then + security delete-keychain "$KEYCHAIN_PATH" + fi diff --git a/.github/workflows/pull-request.yml b/.github/workflows/pull-request.yml new file mode 100644 index 0000000..0b58f47 --- /dev/null +++ b/.github/workflows/pull-request.yml @@ -0,0 +1,64 @@ +name: Pull Request + +on: + pull_request: + branches: [main] + paths: + - '**.swift' + - '**.xcodeproj/**' + - '.github/workflows/pull-request.yml' + +env: + APP_NAME: BetterCapture + SCHEME: BetterCapture + XCODE_VERSION: '26.0' + +jobs: + build: + name: Build + runs-on: macos-15 + timeout-minutes: 15 + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Select Xcode version + run: sudo xcode-select -s /Applications/Xcode_${{ env.XCODE_VERSION }}.app + + - name: Show Xcode version + run: xcodebuild -version + + - name: Build application + run: | + xcodebuild build \ + -project "${{ env.APP_NAME }}.xcodeproj" \ + -scheme "${{ env.SCHEME }}" \ + -configuration Debug \ + -destination "platform=macOS" \ + CODE_SIGN_IDENTITY="-" \ + CODE_SIGNING_REQUIRED=NO \ + | xcbeautify --renderer github-actions || true + + test: + name: Test + runs-on: macos-15 + timeout-minutes: 15 + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Select Xcode version + run: sudo xcode-select -s /Applications/Xcode_${{ env.XCODE_VERSION }}.app + + - name: Run tests + run: | + xcodebuild test \ + -project "${{ env.APP_NAME }}.xcodeproj" \ + -scheme "${{ env.SCHEME }}" \ + -configuration Debug \ + -destination "platform=macOS" \ + CODE_SIGN_IDENTITY="-" \ + CODE_SIGNING_REQUIRED=NO \ + | xcbeautify --renderer github-actions || true diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000..f24da31 --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,216 @@ +name: Build and Release + +on: + release: + types: [published] + workflow_dispatch: + inputs: + version: + description: "Version number (e.g., 1.0.0)" + required: true + type: string + +permissions: + contents: write + +env: + APP_NAME: BetterCapture + SCHEME: BetterCapture + XCODE_VERSION: "26.0" + +jobs: + build: + runs-on: macos-15 + timeout-minutes: 30 + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Select Xcode version + run: sudo xcode-select -s /Applications/Xcode_${{ env.XCODE_VERSION }}.app + + - name: Show Xcode version + run: xcodebuild -version + + - name: Import signing certificate + env: + APPLE_CERTIFICATE_BASE64: ${{ secrets.APPLE_CERTIFICATE_BASE64 }} + 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) + + # Create keychain + 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 + CERTIFICATE_PATH=$RUNNER_TEMP/certificate.p12 + echo "$APPLE_CERTIFICATE_BASE64" | base64 --decode -o "$CERTIFICATE_PATH" + security import "$CERTIFICATE_PATH" -P "$APPLE_CERTIFICATE_PASSWORD" -A -t cert -f pkcs12 -k "$KEYCHAIN_PATH" + security set-key-partition-list -S apple-tool:,apple: -k "$KEYCHAIN_PASSWORD" "$KEYCHAIN_PATH" + security list-keychain -d user -s "$KEYCHAIN_PATH" + + # Clean up certificate file + rm "$CERTIFICATE_PATH" + + - name: Determine version + id: version + run: | + if [ "${{ github.event_name }}" == "release" ]; then + VERSION="${{ github.event.release.tag_name }}" + # Remove 'v' prefix if present + VERSION="${VERSION#v}" + else + VERSION="${{ inputs.version }}" + fi + echo "version=$VERSION" >> $GITHUB_OUTPUT + echo "Building version: $VERSION" + + - name: Update version in project + run: | + VERSION=${{ steps.version.outputs.version }} + # Extract major.minor for MARKETING_VERSION + MARKETING_VERSION=$(echo "$VERSION" | grep -oE '^[0-9]+\.[0-9]+') + # Use full version for display if it has patch + agvtool new-marketing-version "$MARKETING_VERSION" + agvtool new-version -all 1 + + - name: Build application + run: | + xcodebuild archive \ + -project "${{ env.APP_NAME }}.xcodeproj" \ + -scheme "${{ env.SCHEME }}" \ + -configuration Release \ + -archivePath "$RUNNER_TEMP/${{ env.APP_NAME }}.xcarchive" \ + -destination "generic/platform=macOS" \ + DEVELOPMENT_TEAM="${{ secrets.APPLE_TEAM_ID }}" \ + CODE_SIGN_IDENTITY="Developer ID Application" \ + CODE_SIGN_STYLE=Manual \ + OTHER_CODE_SIGN_FLAGS="--timestamp --options=runtime" \ + | xcbeautify --renderer github-actions || true + + - name: Export application + run: | + # Create export options plist + cat > "$RUNNER_TEMP/ExportOptions.plist" << EOF + + + + + method + developer-id + teamID + ${{ secrets.APPLE_TEAM_ID }} + signingStyle + manual + signingCertificate + Developer ID Application + + + EOF + + xcodebuild -exportArchive \ + -archivePath "$RUNNER_TEMP/${{ env.APP_NAME }}.xcarchive" \ + -exportPath "$RUNNER_TEMP/export" \ + -exportOptionsPlist "$RUNNER_TEMP/ExportOptions.plist" + + - name: Notarize application + env: + APPLE_ID: ${{ secrets.APPLE_ID }} + APPLE_ID_PASSWORD: ${{ secrets.APPLE_ID_PASSWORD }} + APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }} + run: | + APP_PATH="$RUNNER_TEMP/export/${{ env.APP_NAME }}.app" + + # Create zip for notarization + ditto -c -k --keepParent "$APP_PATH" "$RUNNER_TEMP/${{ env.APP_NAME }}-notarize.zip" + + # Submit for notarization + xcrun notarytool submit "$RUNNER_TEMP/${{ env.APP_NAME }}-notarize.zip" \ + --apple-id "$APPLE_ID" \ + --password "$APPLE_ID_PASSWORD" \ + --team-id "$APPLE_TEAM_ID" \ + --wait + + # Staple the notarization ticket + xcrun stapler staple "$APP_PATH" + + # Clean up + rm "$RUNNER_TEMP/${{ env.APP_NAME }}-notarize.zip" + + - name: Create DMG + run: | + APP_PATH="$RUNNER_TEMP/export/${{ env.APP_NAME }}.app" + DMG_PATH="$RUNNER_TEMP/${{ env.APP_NAME }}-${{ steps.version.outputs.version }}-arm64.dmg" + + # Create temporary directory for DMG contents + DMG_TEMP="$RUNNER_TEMP/dmg-contents" + mkdir -p "$DMG_TEMP" + + # Copy app to temporary directory + cp -R "$APP_PATH" "$DMG_TEMP/" + + # Create symbolic link to Applications + ln -s /Applications "$DMG_TEMP/Applications" + + # Create DMG + hdiutil create -volname "${{ env.APP_NAME }}" \ + -srcfolder "$DMG_TEMP" \ + -ov -format UDZO \ + "$DMG_PATH" + + # Clean up + rm -rf "$DMG_TEMP" + + echo "DMG_PATH=$DMG_PATH" >> $GITHUB_ENV + + - name: Upload DMG artifact + uses: actions/upload-artifact@v4 + with: + name: ${{ env.APP_NAME }}-${{ steps.version.outputs.version }}-arm64.dmg + path: ${{ env.DMG_PATH }} + if-no-files-found: error + + - name: Upload to release + if: github.event_name == 'release' + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + gh release upload "${{ github.event.release.tag_name }}" \ + "${{ env.DMG_PATH }}" \ + --clobber + + - name: Calculate SHA256 and update Homebrew cask + if: github.event_name == 'release' + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + # Calculate SHA256 + SHA256=$(shasum -a 256 "${{ env.DMG_PATH }}" | awk '{print $1}') + VERSION=${{ steps.version.outputs.version }} + + echo "SHA256: $SHA256" + echo "Version: $VERSION" + + # Update cask file + sed -i '' "s/version \".*\"/version \"$VERSION\"/" Casks/bettercapture.rb + sed -i '' "s/sha256 .*/sha256 \"$SHA256\"/" Casks/bettercapture.rb + + # Commit and push the updated cask + git config user.name "github-actions[bot]" + git config user.email "github-actions[bot]@users.noreply.github.com" + git add Casks/bettercapture.rb + git commit -m "chore(brew): update cask to v$VERSION" || echo "No changes to commit" + git push origin HEAD:main + + - name: Clean up keychain + if: always() + run: | + KEYCHAIN_PATH=$RUNNER_TEMP/app-signing.keychain-db + if [ -f "$KEYCHAIN_PATH" ]; then + security delete-keychain "$KEYCHAIN_PATH" + fi diff --git a/Casks/bettercapture.rb b/Casks/bettercapture.rb new file mode 100644 index 0000000..a551777 --- /dev/null +++ b/Casks/bettercapture.rb @@ -0,0 +1,20 @@ +cask "bettercapture" do + version "1.0.0" + sha256 :no_check # Updated automatically by release workflow + + url "https://github.com/jsattler/BetterCapture/releases/download/v#{version}/BetterCapture-#{version}-arm64.dmg" + name "BetterCapture" + desc "The macOS screen recorder you deserve - always free and open source" + homepage "https://github.com/jsattler/BetterCapture" + + depends_on macos: ">= :sequoia" + depends_on arch: :arm64 + + app "BetterCapture.app" + + zap trash: [ + "~/Library/Application Support/BetterCapture", + "~/Library/Caches/com.sattlerjoshua.BetterCapture", + "~/Library/Preferences/com.sattlerjoshua.BetterCapture.plist", + ] +end diff --git a/README.md b/README.md index 5f440a7..f5a7c4d 100644 --- a/README.md +++ b/README.md @@ -27,7 +27,8 @@ ### Homebrew ```bash -brew install bettercapture +brew tap jsattler/bettercapture https://github.com/jsattler/BetterCapture +brew install --cask bettercapture ``` ### Direct Download