Skip to content

Release

Release #21

Workflow file for this run

name: Release
on:
workflow_dispatch:
defaults:
run:
shell: bash -xeuo pipefail {0}
concurrency:
group: release
cancel-in-progress: false
permissions: {}
env:
DEVELOPMENT_TEAM: ${{ vars.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}" \
EXCLUDED_ARCHS=x86_64
- 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@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
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:
actions: write # required to trigger site deploy workflow
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
- name: Trigger site deploy
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: gh workflow run "Deploy Site" --repo "${GITHUB_REPOSITORY}"
- name: Mint bot token for tap
id: tap-token
uses: actions/create-github-app-token@1b10c78c7865c340bc4f6099eb2f838309f1e8c3 # v3.1.1
with:
client-id: ${{ vars.APP_CLIENT_ID }}
private-key: ${{ secrets.APP_PRIVATE_KEY }}
owner: ${{ github.repository_owner }}
repositories: homebrew-tap
permission-contents: write
permission-pull-requests: write
- name: Bump Homebrew cask
env:
HOMEBREW_GITHUB_API_TOKEN: ${{ steps.tap-token.outputs.token }}
HOMEBREW_NO_AUTO_UPDATE: 1
HOMEBREW_NO_ENV_HINTS: 1
APP_SLUG: ${{ steps.tap-token.outputs.app-slug }}
run: |
BOT_USER="${APP_SLUG}[bot]"
BOT_ID=$(GH_TOKEN="${HOMEBREW_GITHUB_API_TOKEN}" gh api "/users/${BOT_USER}" --jq .id)
export HOMEBREW_GIT_NAME="${BOT_USER}"
export HOMEBREW_GIT_EMAIL="${BOT_ID}+${BOT_USER}@users.noreply.github.com"
brew tap starhaven-io/homebrew-tap
brew bump --cask --no-fork --tap starhaven-io/homebrew-tap --version "${TAG}" macosdb