Skip to content

Release

Release #25

Workflow file for this run

name: Release
on:
push:
tags:
- 'v*' # Trigger on version tags like v1.0.0
env:
APP_NAME: ScreenRecorder
BUNDLE_ID: com.codeitlikemiley.screenrecorder
jobs:
build-and-release:
runs-on: macos-15
permissions:
contents: write # Needed for creating releases
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Extract version from tag
id: version
run: echo "VERSION=${GITHUB_REF_NAME#v}" >> $GITHUB_OUTPUT
- name: Update version in Info.plist
run: |
sed -i '' "s|<string>1.0.0</string>|<string>${{ steps.version.outputs.VERSION }}</string>|g" Resources/Info.plist
- name: Install Apple certificate
env:
CERTIFICATE_P12: ${{ secrets.CERTIFICATE_P12 }}
CERTIFICATE_PASSWORD: ${{ secrets.CERTIFICATE_PASSWORD }}
KEYCHAIN_PASSWORD: ${{ secrets.KEYCHAIN_PASSWORD }}
run: |
# Create temporary keychain
KEYCHAIN_PATH=$RUNNER_TEMP/app-signing.keychain-db
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 -n "$CERTIFICATE_P12" | base64 --decode -o "$CERTIFICATE_PATH"
security import "$CERTIFICATE_PATH" -P "$CERTIFICATE_PASSWORD" \
-A -t cert -f pkcs12 -k "$KEYCHAIN_PATH"
# Allow codesign to access the keychain
security set-key-partition-list -S apple-tool:,apple:,codesign: \
-s -k "$KEYCHAIN_PASSWORD" "$KEYCHAIN_PATH"
# Set as default and add to search list
security default-keychain -s "$KEYCHAIN_PATH"
security list-keychains -d user -s "$KEYCHAIN_PATH" login.keychain-db
# Verify the identity is available
echo "Available signing identities:"
security find-identity -v -p codesigning "$KEYCHAIN_PATH"
- name: Build release binary
run: |
xcodebuild -scheme "$APP_NAME" -configuration Release \
-destination 'platform=macOS' \
-derivedDataPath .build/xcode-release \
CODE_SIGNING_ALLOWED=NO \
build 2>&1 | grep -E '(error:|warning:|BUILD|Signing)' || true
if [ ! -f ".build/xcode-release/Build/Products/Release/$APP_NAME" ]; then
echo "❌ Build failed"
exit 1
fi
- name: Build CLI and MCP server
run: |
swift build -c release --product sr
swift build -c release --product sr-mcp
# Copy to .build/ for easy access
cp .build/release/sr .build/sr
cp .build/release/sr-mcp .build/sr-mcp
echo "✅ CLI and MCP binaries built"
ls -lh .build/sr .build/sr-mcp
- name: Package .app bundle
run: |
APP_DIR=".build/release/${APP_NAME}.app"
CONTENTS_DIR="${APP_DIR}/Contents"
MACOS_DIR="${CONTENTS_DIR}/MacOS"
RESOURCES_DIR="${CONTENTS_DIR}/Resources"
mkdir -p "${MACOS_DIR}" "${RESOURCES_DIR}"
cp ".build/xcode-release/Build/Products/Release/${APP_NAME}" "${MACOS_DIR}/${APP_NAME}"
# Bundle CLI and MCP inside .app
cp .build/sr "${MACOS_DIR}/sr"
cp .build/sr-mcp "${MACOS_DIR}/sr-mcp"
echo "✅ sr and sr-mcp bundled in .app"
cp Resources/Info.plist "${CONTENTS_DIR}/Info.plist"
[ -f "Resources/AppIcon.icns" ] && cp Resources/AppIcon.icns "${RESOURCES_DIR}/AppIcon.icns"
# Copy SPM resource bundles
for bundle in .build/xcode-release/Build/Products/Release/*.bundle; do
[ -d "$bundle" ] && cp -R "$bundle" "${RESOURCES_DIR}/"
done
- name: Code sign
env:
KEYCHAIN_PASSWORD: ${{ secrets.KEYCHAIN_PASSWORD }}
run: |
KEYCHAIN_PATH=$RUNNER_TEMP/app-signing.keychain-db
# Get the SHA-1 hash of the signing identity from the keychain
IDENTITY_HASH=$(security find-identity -v -p codesigning "$KEYCHAIN_PATH" | grep "Developer ID" | head -1 | awk '{print $2}')
if [ -z "$IDENTITY_HASH" ]; then
echo "❌ No Developer ID identity found in keychain"
exit 1
fi
echo "Signing with identity: $IDENTITY_HASH"
# Sign nested CLI binaries first (no app-specific entitlements)
MACOS_DIR=".build/release/${APP_NAME}.app/Contents/MacOS"
for cli_bin in "$MACOS_DIR/sr" "$MACOS_DIR/sr-mcp"; do
if [ -f "$cli_bin" ]; then
codesign --force --sign "$IDENTITY_HASH" \
--options runtime --timestamp \
"$cli_bin"
echo " ✅ Signed $(basename "$cli_bin")"
fi
done
# Sign the main .app bundle (NO --deep)
codesign --force --sign "$IDENTITY_HASH" \
--options runtime \
--entitlements Resources/ScreenRecorder.entitlements \
--timestamp --generate-entitlement-der \
".build/release/${APP_NAME}.app"
codesign --verify --verbose=2 ".build/release/${APP_NAME}.app"
- name: Notarize
env:
APPLE_ID: ${{ secrets.APPLE_ID }}
APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }}
APP_PASSWORD: ${{ secrets.APP_PASSWORD }}
run: |
ditto -c -k --keepParent ".build/release/${APP_NAME}.app" ".build/${APP_NAME}.zip"
xcrun notarytool submit ".build/${APP_NAME}.zip" \
--apple-id "${APPLE_ID}" \
--team-id "${APPLE_TEAM_ID}" \
--password "${APP_PASSWORD}" \
--wait
xcrun stapler staple ".build/release/${APP_NAME}.app"
- name: Create DMG
run: |
brew install create-dmg || true
DMG_NAME="${APP_NAME}-${{ steps.version.outputs.VERSION }}.dmg"
DMG_STAGING=".build/dmg-staging"
mkdir -p "${DMG_STAGING}"
cp -R ".build/release/${APP_NAME}.app" "${DMG_STAGING}/"
create-dmg \
--volname "Screen Recorder" \
--window-pos 200 120 \
--window-size 600 400 \
--icon-size 100 \
--icon "${APP_NAME}.app" 150 190 \
--app-drop-link 450 190 \
--no-internet-enable \
".build/${DMG_NAME}" \
"${DMG_STAGING}" || true
rm -rf "${DMG_STAGING}"
echo "DMG_NAME=${DMG_NAME}" >> $GITHUB_ENV
- name: Create GitHub Release
uses: softprops/action-gh-release@v2
with:
name: "Screen Recorder v${{ steps.version.outputs.VERSION }}"
draft: false
prerelease: false
generate_release_notes: true
files: |
.build/${{ env.DMG_NAME }}
.build/sr
.build/sr-mcp
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Upload to Cloudflare R2
env:
AWS_ACCESS_KEY_ID: ${{ secrets.R2_ACCESS_KEY_ID }}
AWS_SECRET_ACCESS_KEY: ${{ secrets.R2_SECRET_ACCESS_KEY }}
AWS_DEFAULT_REGION: auto
R2_ACCOUNT_ID: ${{ secrets.R2_ACCOUNT_ID }}
R2_BUCKET: ${{ secrets.R2_BUCKET_NAME }}
run: |
# Skip if R2 credentials are not configured
if [ -z "$AWS_ACCESS_KEY_ID" ]; then
echo "ℹ️ R2 not configured — skipping upload"
exit 0
fi
R2_ENDPOINT="https://${R2_ACCOUNT_ID}.r2.cloudflarestorage.com"
# Install AWS CLI if not available
which aws || brew install awscli
# Upload DMG to R2
aws s3 cp ".build/${DMG_NAME}" \
"s3://${R2_BUCKET}/releases/${DMG_NAME}" \
--endpoint-url "${R2_ENDPOINT}"
# Upload CLI and MCP binaries
aws s3 cp ".build/sr" \
"s3://${R2_BUCKET}/releases/sr" \
--endpoint-url "${R2_ENDPOINT}"
aws s3 cp ".build/sr-mcp" \
"s3://${R2_BUCKET}/releases/sr-mcp" \
--endpoint-url "${R2_ENDPOINT}"
echo "✅ Uploaded DMG, sr, and sr-mcp to R2"
- name: Update Homebrew tap
if: success()
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
VERSION: ${{ steps.version.outputs.VERSION }}
run: |
curl -X POST \
-H "Accept: application/vnd.github+json" \
-H "Authorization: Bearer ${GH_TOKEN}" \
https://api.github.com/repos/codeitlikemiley/homebrew-tap/dispatches \
-d "{\"event_type\":\"update-tap\",\"client_payload\":{\"version\":\"${VERSION}\"}}"
echo "✅ Triggered homebrew-tap update for v${VERSION}"
- name: Cleanup keychain
if: always()
run: security delete-keychain $RUNNER_TEMP/app-signing.keychain-db || true