perf(ui): throttle continuous hover events in uptime bar #33
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 DMG + Appcast | |
| on: | |
| push: | |
| tags: | |
| - "v*" | |
| permissions: | |
| contents: write | |
| pages: write | |
| id-token: write | |
| jobs: | |
| release: | |
| runs-on: macos-latest | |
| steps: | |
| - name: Check out repository | |
| uses: actions/checkout@v4 | |
| with: | |
| fetch-depth: 0 | |
| - name: Configure GitHub Pages | |
| uses: actions/configure-pages@v5 | |
| - name: Install Tuist | |
| run: brew install tuist | |
| - name: Install Tuist dependencies | |
| run: TUIST_SKIP_UPDATE_CHECK=1 tuist install | |
| - name: Validate Sparkle secrets | |
| env: | |
| SPARKLE_PRIVATE_ED_KEY: ${{ secrets.SPARKLE_PRIVATE_ED_KEY }} | |
| SPARKLE_PUBLIC_ED_KEY: ${{ secrets.SPARKLE_PUBLIC_ED_KEY }} | |
| run: | | |
| if [ -z "$SPARKLE_PRIVATE_ED_KEY" ] || [ -z "$SPARKLE_PUBLIC_ED_KEY" ]; then | |
| echo "SPARKLE_PRIVATE_ED_KEY and SPARKLE_PUBLIC_ED_KEY must be configured." | |
| exit 1 | |
| fi | |
| - name: Validate release metadata inputs | |
| run: | | |
| VERSION="${GITHUB_REF_NAME#v}" | |
| if [ -z "$VERSION" ]; then | |
| echo "Release version could not be derived from tag ${GITHUB_REF_NAME}." | |
| exit 1 | |
| fi | |
| if [ -z "${GITHUB_RUN_NUMBER}" ]; then | |
| echo "GITHUB_RUN_NUMBER must be available for release builds." | |
| exit 1 | |
| fi | |
| - name: Import Apple signing certificate | |
| env: | |
| APPLE_CERTIFICATE_P12_BASE64: ${{ secrets.APPLE_CERTIFICATE_P12_BASE64 }} | |
| APPLE_CERTIFICATE_PASSWORD: ${{ secrets.APPLE_CERTIFICATE_PASSWORD }} | |
| APPLE_SIGNING_IDENTITY: ${{ secrets.APPLE_SIGNING_IDENTITY }} | |
| if: ${{ env.APPLE_CERTIFICATE_P12_BASE64 != '' && env.APPLE_CERTIFICATE_PASSWORD != '' && env.APPLE_SIGNING_IDENTITY != '' }} | |
| run: | | |
| CERT_PATH="$RUNNER_TEMP/signing-certificate.p12" | |
| KEYCHAIN_PATH="$RUNNER_TEMP/build.keychain-db" | |
| KEYCHAIN_PASSWORD="$(uuidgen)" | |
| echo "$APPLE_CERTIFICATE_P12_BASE64" | base64 -D > "$CERT_PATH" | |
| security create-keychain -p "$KEYCHAIN_PASSWORD" "$KEYCHAIN_PATH" | |
| security set-keychain-settings -lut 21600 "$KEYCHAIN_PATH" | |
| security unlock-keychain -p "$KEYCHAIN_PASSWORD" "$KEYCHAIN_PATH" | |
| security import "$CERT_PATH" -k "$KEYCHAIN_PATH" -P "$APPLE_CERTIFICATE_PASSWORD" -f pkcs12 -A -T /usr/bin/codesign -T /usr/bin/security | |
| security set-key-partition-list -S apple-tool:,apple:,codesign: -s -k "$KEYCHAIN_PASSWORD" "$KEYCHAIN_PATH" | |
| security list-keychains -d user -s "$KEYCHAIN_PATH" | |
| security default-keychain -d user -s "$KEYCHAIN_PATH" | |
| - name: Package DMG | |
| id: package | |
| env: | |
| CODESIGN_IDENTITY: ${{ secrets.APPLE_SIGNING_IDENTITY }} | |
| APPLE_ID: ${{ secrets.APPLE_ID }} | |
| APPLE_APP_SPECIFIC_PASSWORD: ${{ secrets.APPLE_APP_SPECIFIC_PASSWORD }} | |
| APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }} | |
| MENU_STATUS_BUILD: ${{ github.run_number }} | |
| MENU_STATUS_FEED_URL: https://${{ github.repository_owner }}.github.io/${{ github.event.repository.name }}/appcast.xml | |
| MENU_STATUS_PUBLIC_ED_KEY: ${{ secrets.SPARKLE_PUBLIC_ED_KEY }} | |
| run: | | |
| ./package-app.sh "${GITHUB_REF_NAME#v}" | |
| - name: Detect release channel | |
| id: channel | |
| run: | | |
| python3 Scripts/release-appcast.py classify-version "${GITHUB_REF_NAME#v}" --github-output "$GITHUB_OUTPUT" | |
| - name: Upload GitHub release asset | |
| uses: softprops/action-gh-release@v2 | |
| with: | |
| files: ${{ steps.package.outputs.artifact_path }} | |
| generate_release_notes: true | |
| fail_on_unmatched_files: true | |
| prerelease: ${{ steps.channel.outputs.prerelease }} | |
| - name: Generate appcast.xml | |
| env: | |
| SPARKLE_PRIVATE_ED_KEY: ${{ secrets.SPARKLE_PRIVATE_ED_KEY }} | |
| GH_TOKEN: ${{ github.token }} | |
| run: | | |
| ARTIFACT_PATH="${{ steps.package.outputs.artifact_path }}" | |
| ARTIFACT_NAME="${{ steps.package.outputs.artifact_name }}" | |
| SPARKLE_BIN="Tuist/.build/artifacts/sparkle/Sparkle/bin" | |
| APPCAST_WORK_DIR="$RUNNER_TEMP/appcast" | |
| PAGES_DIR="$RUNNER_TEMP/pages" | |
| ASSET_TAGS="$RUNNER_TEMP/asset-tags.tsv" | |
| mkdir -p "$APPCAST_WORK_DIR" "$PAGES_DIR" | |
| : > "$ASSET_TAGS" | |
| # Include only the latest stable release and the latest pre-release in the appcast. | |
| # Sparkle's client picks the highest-version visible item — older releases add no value | |
| # and some pre-Sparkle-workflow DMGs share CFBundleVersion which would break generate_appcast. | |
| gh release list --repo "$GITHUB_REPOSITORY" --limit 100 \ | |
| --json tagName,isPrerelease,createdAt > "$RUNNER_TEMP/releases.json" | |
| python3 - "$RUNNER_TEMP/releases.json" "$GITHUB_REF_NAME" > "$RUNNER_TEMP/tags.txt" <<'PYEOF' | |
| import json, sys | |
| releases = json.load(open(sys.argv[1])) | |
| current_tag = sys.argv[2] | |
| releases.sort(key=lambda r: r["createdAt"], reverse=True) | |
| latest_stable = next((r["tagName"] for r in releases if not r["isPrerelease"]), None) | |
| latest_beta = next((r["tagName"] for r in releases if r["isPrerelease"]), None) | |
| tags = {t for t in (latest_stable, latest_beta, current_tag) if t} | |
| for t in sorted(tags): | |
| print(t) | |
| PYEOF | |
| while read -r tag; do | |
| [ -z "$tag" ] && continue | |
| if [ "$tag" = "$GITHUB_REF_NAME" ]; then | |
| printf '%s\t%s\n' "$ARTIFACT_NAME" "$tag" >> "$ASSET_TAGS" | |
| continue | |
| fi | |
| assets="$(gh release view "$tag" \ | |
| --repo "$GITHUB_REPOSITORY" \ | |
| --json assets \ | |
| --jq '.assets[].name | select(endswith(".dmg"))')" | |
| if [ -z "$assets" ]; then | |
| echo "No DMG asset found for release $tag." | |
| exit 1 | |
| fi | |
| gh release download "$tag" \ | |
| --repo "$GITHUB_REPOSITORY" \ | |
| --dir "$APPCAST_WORK_DIR" \ | |
| --pattern "*.dmg" \ | |
| --skip-existing | |
| while IFS= read -r asset; do | |
| [ -z "$asset" ] && continue | |
| if [ ! -f "$APPCAST_WORK_DIR/$asset" ]; then | |
| echo "Expected DMG asset $asset from release $tag was not downloaded." | |
| exit 1 | |
| fi | |
| printf '%s\t%s\n' "$asset" "$tag" >> "$ASSET_TAGS" | |
| done <<< "$assets" | |
| done < "$RUNNER_TEMP/tags.txt" | |
| # Always include the freshly built artifact — the release-asset upload above | |
| # may not be visible yet when this step runs. | |
| cp -f "$ARTIFACT_PATH" "$APPCAST_WORK_DIR/$ARTIFACT_NAME" | |
| printf '%s\t%s\n' "$ARTIFACT_NAME" "$GITHUB_REF_NAME" >> "$ASSET_TAGS" | |
| # generate_appcast emits one <item> per .dmg, computing Sparkle EdDSA signatures | |
| # from binary contents. The --download-url-prefix below is a placeholder — the | |
| # Python post-process step rewrites each enclosure URL to the correct per-tag | |
| # GitHub Releases path. --maximum-deltas 0 disables binary delta generation: | |
| # our DMGs are ~4MB so deltas aren't worth the workflow complexity of uploading | |
| # .delta files to the matching release and rewriting their URLs. | |
| echo "$SPARKLE_PRIVATE_ED_KEY" | "$SPARKLE_BIN/generate_appcast" \ | |
| --ed-key-file - \ | |
| --maximum-deltas 0 \ | |
| --download-url-prefix "https://github.com/${GITHUB_REPOSITORY}/releases/download/__placeholder__/" \ | |
| "$APPCAST_WORK_DIR" | |
| # Post-process: tag allowed pre-release items with <sparkle:channel>beta</sparkle:channel>, | |
| # rewrite each enclosure URL using the actual release tag that provided the asset, | |
| # and assert no placeholder or delta entries remain. | |
| python3 Scripts/release-appcast.py postprocess \ | |
| --appcast "$APPCAST_WORK_DIR/appcast.xml" \ | |
| --repo "$GITHUB_REPOSITORY" \ | |
| --asset-tags "$ASSET_TAGS" | |
| cp "$APPCAST_WORK_DIR/appcast.xml" "$PAGES_DIR/appcast.xml" | |
| - name: Upload Pages artifact | |
| uses: actions/upload-pages-artifact@v3 | |
| with: | |
| path: ${{ runner.temp }}/pages | |
| deploy-appcast: | |
| needs: release | |
| runs-on: ubuntu-latest | |
| environment: | |
| name: github-pages | |
| url: ${{ steps.deployment.outputs.page_url }} | |
| steps: | |
| - name: Deploy GitHub Pages | |
| id: deployment | |
| uses: actions/deploy-pages@v4 |