Release #16
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: | |
| workflow_dispatch: | |
| defaults: | |
| run: | |
| shell: bash -xeuo pipefail {0} | |
| concurrency: | |
| group: release | |
| cancel-in-progress: false | |
| permissions: {} | |
| env: | |
| DEVELOPMENT_TEAM: ${{ secrets.DEVELOPMENT_TEAM }} | |
| jobs: | |
| build: | |
| name: Build | |
| runs-on: macos-26 | |
| environment: | |
| name: release | |
| deployment: false | |
| outputs: | |
| tag: ${{ steps.version.outputs.tag }} | |
| build_number: ${{ steps.version.outputs.build_number }} | |
| artifact_name: "macOSdb-${{ steps.version.outputs.tag }}.zip" | |
| permissions: | |
| contents: write # required to create tag | |
| attestations: write # required to generate build provenance attestations | |
| id-token: write # required to generate build provenance attestations | |
| env: | |
| TEMPORARY_CERTIFICATE_FILE: "macosdb_developer_id_certificate.p12" | |
| TEMPORARY_KEYCHAIN_FILE: "macosdb_signing.keychain-db" | |
| steps: | |
| - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 | |
| with: | |
| persist-credentials: false | |
| - name: Extract version from Xcode project | |
| id: version | |
| run: | | |
| TAG=$(grep -m1 'MARKETING_VERSION' macOSdb.xcodeproj/project.pbxproj | sed 's/.*= *//;s/ *;.*//') | |
| if [[ -z "${TAG}" ]]; then | |
| echo "::error::Could not extract MARKETING_VERSION from project.pbxproj" | |
| exit 1 | |
| fi | |
| BUILD_NUMBER=$(grep -m1 'CURRENT_PROJECT_VERSION' macOSdb.xcodeproj/project.pbxproj | sed 's/.*= *//;s/ *;.*//') | |
| echo "tag=${TAG}" >> "${GITHUB_OUTPUT}" | |
| echo "build_number=${BUILD_NUMBER}" >> "${GITHUB_OUTPUT}" | |
| echo "TAG=${TAG}" >> "${GITHUB_ENV}" | |
| - name: Create tag | |
| env: | |
| GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| REPOSITORY: ${{ github.repository }} | |
| COMMIT_SHA: ${{ github.sha }} | |
| run: | | |
| gh api "repos/${REPOSITORY}/git/refs" \ | |
| -f "ref=refs/tags/${TAG}" \ | |
| -f "sha=${COMMIT_SHA}" | |
| - name: Create and unlock temporary macOS keychain | |
| run: | | |
| TEMPORARY_KEYCHAIN_PASSWORD="$(openssl rand -base64 20)" | |
| TEMPORARY_KEYCHAIN_PATH="${RUNNER_TEMP}/${TEMPORARY_KEYCHAIN_FILE}" | |
| security create-keychain -p "${TEMPORARY_KEYCHAIN_PASSWORD}" "${TEMPORARY_KEYCHAIN_PATH}" | |
| security set-keychain-settings -lut 21600 "${TEMPORARY_KEYCHAIN_PATH}" | |
| security unlock-keychain -p "${TEMPORARY_KEYCHAIN_PASSWORD}" "${TEMPORARY_KEYCHAIN_PATH}" | |
| - name: Create temporary certificate file | |
| env: | |
| DEVELOPER_ID_CERTIFICATE: ${{ secrets.DEVELOPER_ID_CERTIFICATE }} | |
| run: echo -n "${DEVELOPER_ID_CERTIFICATE}" | | |
| base64 --decode --output="${RUNNER_TEMP}/${TEMPORARY_CERTIFICATE_FILE}" | |
| - name: Import certificate into keychain | |
| env: | |
| DEVELOPER_ID_CERTIFICATE_PASSWORD: ${{ secrets.DEVELOPER_ID_CERTIFICATE_PASSWORD }} | |
| run: security import "${RUNNER_TEMP}/${TEMPORARY_CERTIFICATE_FILE}" | |
| -k "${RUNNER_TEMP}/${TEMPORARY_KEYCHAIN_FILE}" | |
| -P "${DEVELOPER_ID_CERTIFICATE_PASSWORD}" | |
| -t cert -f pkcs12 -A | |
| - name: Clean up temporary certificate file | |
| if: always() | |
| run: rm -f "${RUNNER_TEMP}/${TEMPORARY_CERTIFICATE_FILE}" | |
| - name: Open keychain | |
| run: security list-keychain -d user -s "${RUNNER_TEMP}/${TEMPORARY_KEYCHAIN_FILE}" | |
| - name: Archive | |
| run: | | |
| xcodebuild archive \ | |
| -project macOSdb.xcodeproj \ | |
| -scheme macOSdb \ | |
| -destination 'generic/platform=macOS' \ | |
| -configuration Release \ | |
| -archivePath "${RUNNER_TEMP}/macOSdb.xcarchive" \ | |
| CODE_SIGN_STYLE=Manual \ | |
| CODE_SIGN_IDENTITY="Developer ID Application" \ | |
| DEVELOPMENT_TEAM="${DEVELOPMENT_TEAM}" | |
| - name: Export | |
| run: | | |
| cat > "${RUNNER_TEMP}/ExportOptions.plist" << EOF | |
| <?xml version="1.0" encoding="UTF-8"?> | |
| <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> | |
| <plist version="1.0"> | |
| <dict> | |
| <key>method</key> | |
| <string>developer-id</string> | |
| <key>teamID</key> | |
| <string>${DEVELOPMENT_TEAM}</string> | |
| </dict> | |
| </plist> | |
| EOF | |
| xcodebuild -exportArchive \ | |
| -archivePath "${RUNNER_TEMP}/macOSdb.xcarchive" \ | |
| -exportOptionsPlist "${RUNNER_TEMP}/ExportOptions.plist" \ | |
| -exportPath "${RUNNER_TEMP}/export" | |
| - name: Clean up temporary keychain | |
| if: always() | |
| run: | | |
| if [[ -f "${RUNNER_TEMP}/${TEMPORARY_KEYCHAIN_FILE}" ]]; then | |
| security delete-keychain "${RUNNER_TEMP}/${TEMPORARY_KEYCHAIN_FILE}" | |
| fi | |
| - name: Package | |
| run: | | |
| ditto -c -k --keepParent \ | |
| "${RUNNER_TEMP}/export/macOSdb.app" \ | |
| "${RUNNER_TEMP}/macOSdb-${TAG}.zip" | |
| - name: Generate build provenance | |
| uses: actions/attest@59d89421af93a897026c735860bf21b6eb4f7b26 # v4.1.0 | |
| with: | |
| subject-path: "${{ runner.temp }}/macOSdb-${{ steps.version.outputs.tag }}.zip" | |
| - name: Upload artifact | |
| uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0 | |
| with: | |
| name: "macOSdb-${{ steps.version.outputs.tag }}.zip" | |
| path: "${{ runner.temp }}/macOSdb-${{ steps.version.outputs.tag }}.zip" | |
| release: | |
| name: Release | |
| needs: build | |
| runs-on: macos-26 | |
| environment: | |
| name: release | |
| deployment: false | |
| permissions: | |
| contents: write # required to create GitHub releases | |
| env: | |
| TAG: ${{ needs.build.outputs.tag }} | |
| steps: | |
| - name: Download artifact | |
| uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 | |
| with: | |
| name: "${{ needs.build.outputs.artifact_name }}" | |
| - name: Notarize and staple | |
| env: | |
| APPLE_ID: ${{ secrets.NOTARIZATION_APPLE_ID }} | |
| NOTARIZATION_PASSWORD: ${{ secrets.NOTARIZATION_PASSWORD }} | |
| ARTIFACT_NAME: ${{ needs.build.outputs.artifact_name }} | |
| run: | | |
| xcrun notarytool submit "${ARTIFACT_NAME}" \ | |
| --team-id "${DEVELOPMENT_TEAM}" \ | |
| --apple-id "${APPLE_ID}" \ | |
| --password "${NOTARIZATION_PASSWORD}" \ | |
| --wait | |
| ditto -xk "${ARTIFACT_NAME}" "${RUNNER_TEMP}/staple" | |
| xcrun stapler staple "${RUNNER_TEMP}/staple/macOSdb.app" | |
| ditto -c -k --keepParent \ | |
| "${RUNNER_TEMP}/staple/macOSdb.app" \ | |
| "${ARTIFACT_NAME}" | |
| - name: Download Sparkle | |
| run: | | |
| curl -L -o "${RUNNER_TEMP}/sparkle.tar.xz" \ | |
| "https://github.com/sparkle-project/Sparkle/releases/download/2.8.1/Sparkle-2.8.1.tar.xz" | |
| mkdir -p "${RUNNER_TEMP}/sparkle" | |
| tar xf "${RUNNER_TEMP}/sparkle.tar.xz" -C "${RUNNER_TEMP}/sparkle" | |
| - name: Sign update with Sparkle EdDSA | |
| env: | |
| SPARKLE_KEY: ${{ secrets.SPARKLE_PRIVATE_KEY }} | |
| ARTIFACT_NAME: ${{ needs.build.outputs.artifact_name }} | |
| run: | | |
| SPARKLE_KEY_FILE="${RUNNER_TEMP}/sparkle_private_key" | |
| echo "${SPARKLE_KEY}" > "${SPARKLE_KEY_FILE}" | |
| SIG_OUTPUT=$("${RUNNER_TEMP}/sparkle/bin/sign_update" "${ARTIFACT_NAME}" --ed-key-file "${SPARKLE_KEY_FILE}" 2>&1) | |
| rm -f "${SPARKLE_KEY_FILE}" | |
| echo "SPARKLE_SIG=$(echo "${SIG_OUTPUT}" | grep -o 'sparkle:edSignature="[^"]*"' | cut -d'"' -f2)" >> "${GITHUB_ENV}" | |
| echo "SPARKLE_LENGTH=$(echo "${SIG_OUTPUT}" | grep -o 'length="[^"]*"' | cut -d'"' -f2)" >> "${GITHUB_ENV}" | |
| - name: Create release with generated notes | |
| env: | |
| GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| ARTIFACT_NAME: ${{ needs.build.outputs.artifact_name }} | |
| REPOSITORY: ${{ github.repository }} | |
| run: | | |
| gh release create "${TAG}" \ | |
| --repo "${REPOSITORY}" \ | |
| --title "${TAG}" \ | |
| --generate-notes \ | |
| "${ARTIFACT_NAME}" | |
| gh release view "${TAG}" \ | |
| --repo "${REPOSITORY}" \ | |
| --json body \ | |
| --jq '.body' > "${RUNNER_TEMP}/raw_notes.md" | |
| - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 | |
| with: | |
| ref: main | |
| persist-credentials: false | |
| - name: Format release notes | |
| env: | |
| GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| REPOSITORY: ${{ github.repository }} | |
| run: | | |
| python3 scripts/format-release-notes.py \ | |
| "${RUNNER_TEMP}/raw_notes.md" "${TAG}" \ | |
| > "${RUNNER_TEMP}/formatted_notes.md" | |
| python3 scripts/format-release-notes.py \ | |
| "${RUNNER_TEMP}/raw_notes.md" "${TAG}" --html \ | |
| > "${RUNNER_TEMP}/release_notes.html" | |
| gh release edit "${TAG}" \ | |
| --repo "${REPOSITORY}" \ | |
| --notes-file "${RUNNER_TEMP}/formatted_notes.md" | |
| - name: Update appcast | |
| env: | |
| ARTIFACT_NAME: ${{ needs.build.outputs.artifact_name }} | |
| REPOSITORY: ${{ github.repository }} | |
| BUILD_NUMBER: ${{ needs.build.outputs.build_number }} | |
| run: | | |
| export PUBDATE="$(date -u '+%a, %d %b %Y %H:%M:%S %z')" | |
| export DOWNLOAD_URL="https://github.com/${REPOSITORY}/releases/download/${TAG}/${ARTIFACT_NAME}" | |
| export RELEASE_NOTES_HTML="$(cat "${RUNNER_TEMP}/release_notes.html")" | |
| envsubst < .github/appcast-template.xml > appcast.xml | |
| - name: Push appcast | |
| env: | |
| GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| REPOSITORY: ${{ github.repository }} | |
| run: | | |
| git config user.name "github-actions[bot]" | |
| git config user.email "41898282+github-actions[bot]@users.noreply.github.com" | |
| git remote set-url origin "https://x-access-token:${GH_TOKEN}@github.com/${REPOSITORY}.git" | |
| cp appcast.xml "${RUNNER_TEMP}/appcast.xml" | |
| git fetch origin appcast | |
| git checkout appcast | |
| cp "${RUNNER_TEMP}/appcast.xml" appcast.xml | |
| git add appcast.xml | |
| git diff --cached --quiet && exit 0 | |
| git commit -m "Update appcast for ${TAG}" | |
| git push origin appcast |