docs: changelog for v0.7.3-beta.6 #36
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 | |
| # Tag a version to ship it: `git tag v0.7.0 && git push origin v0.7.0`. | |
| # A tag with a pre-release suffix (e.g. v0.7.0-beta.1) publishes to the Early Access channel; a plain | |
| # tag (v0.7.0) publishes to everyone. The one-time secret/Pages setup is documented in the | |
| # release-swift skill under .agents/skills/release-swift/. | |
| on: | |
| push: | |
| tags: ["v*"] | |
| workflow_dispatch: | |
| inputs: | |
| tag: | |
| description: "Tag to build (e.g. v0.7.0)" | |
| required: true | |
| type: string | |
| permissions: | |
| contents: write | |
| # Serialize releases: each run reads appcast.xml from gh-pages, regenerates it with generate_appcast, | |
| # and force-publishes the branch. Without this, two tags pushed close together could race and drop a | |
| # feed entry. | |
| concurrency: | |
| group: release-appcast | |
| cancel-in-progress: false | |
| jobs: | |
| release: | |
| runs-on: macos-26 | |
| steps: | |
| - uses: actions/checkout@v7 | |
| with: | |
| # Build the requested tag. On a tag push `inputs.tag` is empty and checkout defaults to the | |
| # triggering tag; on workflow_dispatch this pins the tag instead of the default branch. | |
| ref: ${{ inputs.tag }} | |
| fetch-depth: 0 # full history so the CFBundleVersion (commit count) is correct | |
| persist-credentials: true | |
| - name: Select Xcode | |
| # The app icon is committed prebuilt (see script/compile_icon.sh) because no actool on GitHub's | |
| # runners can compile its Liquid Glass `.icon` (Apple regression FB20183399), so the default | |
| # runner Xcode is fine for the build/sign/notarize steps. | |
| run: sudo xcode-select -s /Applications/Xcode.app | |
| - name: Resolve tag, version, and channel | |
| id: meta | |
| run: | | |
| TAG="${{ inputs.tag }}" | |
| [ -z "$TAG" ] && TAG="${GITHUB_REF#refs/tags/}" | |
| VERSION="${TAG#v}" | |
| if [[ "$TAG" == *-* ]]; then CHANNEL=beta; PRERELEASE=true; else CHANNEL=stable; PRERELEASE=false; fi | |
| { | |
| echo "tag=$TAG" | |
| echo "version=$VERSION" | |
| echo "channel=$CHANNEL" | |
| echo "prerelease=$PRERELEASE" | |
| } >> "$GITHUB_OUTPUT" | |
| # Imports the Developer ID cert into a temporary keychain and adds it to the search list (cleaned up | |
| # automatically in a post step). Replaces a hand-rolled `security create-keychain`/`import` block. | |
| - name: Import Developer ID certificate | |
| uses: apple-actions/import-codesign-certs@v7 | |
| with: | |
| # Developer ID Application cert (.p12, base64) used to sign the app and DMG. | |
| p12-file-base64: ${{ secrets.APPLE_CERTIFICATE }} | |
| p12-password: ${{ secrets.APPLE_CERTIFICATE_PASSWORD }} | |
| - name: Resolve signing identity | |
| run: | | |
| # The cert is now in the search list; codesign needs the identity's display name. | |
| IDENTITY="$(security find-identity -v -p codesigning | awk -F'"' '/Developer ID Application/ {print $2; exit}')" | |
| [ -n "$IDENTITY" ] || { echo "No Developer ID Application identity found in the imported cert" >&2; exit 1; } | |
| echo "CODESIGN_IDENTITY=$IDENTITY" >> "$GITHUB_ENV" | |
| - name: Write Sparkle private key | |
| env: | |
| SPARKLE_PRIVATE_KEY: ${{ secrets.SPARKLE_PRIVATE_KEY }} | |
| run: | | |
| printf '%s' "$SPARKLE_PRIVATE_KEY" > "$RUNNER_TEMP/sparkle_ed_private_key" | |
| echo "SPARKLE_PRIVATE_KEY_FILE=$RUNNER_TEMP/sparkle_ed_private_key" >> "$GITHUB_ENV" | |
| - name: Carry forward the existing appcast | |
| run: | | |
| # generate_appcast works on a folder: it reads the existing appcast.xml there and the new DMG, | |
| # then writes a merged appcast. "feed/" is that folder. Seed it with the live appcast so prior | |
| # versions and the OTHER channel's items are preserved (verified: items whose archive isn't in | |
| # the folder keep their original enclosure URLs untouched). | |
| mkdir -p feed | |
| # Distinguish "no gh-pages yet" (first release -> fresh feed) from "couldn't read it" | |
| # (transient failure). Only start fresh when the branch genuinely doesn't exist; otherwise | |
| # abort so a network blip can't replace the live feed's full history with a single item. | |
| set +e | |
| git ls-remote --exit-code --heads origin gh-pages >/dev/null 2>&1 | |
| rc=$? | |
| set -e | |
| if [ "$rc" -eq 0 ]; then | |
| git fetch origin gh-pages | |
| git show origin/gh-pages:appcast.xml > feed/appcast.xml \ | |
| || { echo "gh-pages exists but appcast.xml is unreadable — aborting to protect the feed." >&2; exit 1; } | |
| echo "Loaded existing appcast from gh-pages." | |
| elif [ "$rc" -eq 2 ]; then | |
| echo "No gh-pages branch yet — starting a fresh feed." | |
| else | |
| echo "Could not reach origin to check for gh-pages — aborting to protect the feed." >&2 | |
| exit 1 | |
| fi | |
| - name: Build, sign, and notarize the DMG | |
| env: | |
| SPARKLE_PUBLIC_KEY: ${{ secrets.SPARKLE_PUBLIC_KEY }} | |
| # Apple ID + app-specific password + team ID for notarytool. | |
| NOTARY_APPLE_ID: ${{ secrets.APPLE_ID }} | |
| NOTARY_APP_PASSWORD: ${{ secrets.APPLE_PASSWORD }} | |
| NOTARY_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }} | |
| OPENUSAGE_VERSION: ${{ steps.meta.outputs.version }} | |
| run: ./script/release.sh | |
| # Upload the dSYM produced by release.sh so PostHog can symbolicate crash reports (the in-app | |
| # crash autocapture is anonymous and honors the Share-Anonymous-Usage opt-out; see docs/privacy.md). | |
| # posthog-cli is a CI-only build tool (npm), never shipped in the app bundle. Skips with a loud | |
| # warning when POSTHOG_CLI_API_KEY is unset (e.g. forks), so a missing key never blocks a release; | |
| # when the key IS set, an upload failure fails the step so we never silently ship unsymbolicated. | |
| - name: Upload dSYMs to PostHog | |
| env: | |
| POSTHOG_CLI_API_KEY: ${{ secrets.POSTHOG_CLI_API_KEY }} | |
| POSTHOG_CLI_PROJECT_ID: ${{ secrets.POSTHOG_CLI_PROJECT_ID }} | |
| # US region, matching TelemetryConfig.host in the app. | |
| POSTHOG_CLI_HOST: https://us.i.posthog.com | |
| VERSION: ${{ steps.meta.outputs.version }} | |
| run: | | |
| if [ -z "${POSTHOG_CLI_API_KEY}" ]; then | |
| echo "::warning::POSTHOG_CLI_API_KEY not set — skipping dSYM upload; crashes for $VERSION will NOT symbolicate." | |
| exit 0 | |
| fi | |
| [ -d dist/dSYMs ] || { echo "dist/dSYMs missing — release.sh did not produce a dSYM." >&2; exit 1; } | |
| npm install -g @posthog/cli | |
| # Symbolication matches a crash report to its symbols by the binary's Mach-O UUID (carried in | |
| # the dSYM), so `--directory` is all that's required — no per-release flags to keep in sync. | |
| posthog-cli dsym upload --directory dist/dSYMs | |
| - name: Publish DMG to GitHub Releases | |
| uses: softprops/action-gh-release@v3 | |
| with: | |
| tag_name: ${{ steps.meta.outputs.tag }} | |
| name: OpenUsage ${{ steps.meta.outputs.version }} | |
| prerelease: ${{ steps.meta.outputs.prerelease }} | |
| files: dist/OpenUsage-${{ steps.meta.outputs.version }}.dmg | |
| - name: Preserve Tauri updater manifest on stable releases | |
| if: steps.meta.outputs.channel == 'stable' | |
| env: | |
| GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| TAG: ${{ steps.meta.outputs.tag }} | |
| run: | | |
| # Older Tauri builds fetch releases/latest/download/latest.json. Every stable Swift release | |
| # becomes GitHub "Latest", so carry the final Tauri manifest forward on each stable release. | |
| tmpdir="$(mktemp -d)" | |
| gh release download v0.6.28 --pattern latest.json --dir "$tmpdir" | |
| gh release upload "$TAG" "$tmpdir/latest.json" --clobber | |
| - name: Generate appcast with Sparkle | |
| env: | |
| DMG: OpenUsage-${{ steps.meta.outputs.version }}.dmg | |
| CHANNEL: ${{ steps.meta.outputs.channel }} | |
| # Per-tag GitHub release asset base. generate_appcast applies this prefix only to the new DMG | |
| # in feed/; preserved items keep their own (older-tag) URLs. | |
| DOWNLOAD_PREFIX: https://github.com/${{ github.repository }}/releases/download/${{ steps.meta.outputs.tag }}/ | |
| run: | | |
| # Locate generate_appcast from the Sparkle SwiftPM artifact that release.sh already fetched. | |
| GA="$(find .build/artifacts -type f -name generate_appcast 2>/dev/null | head -n1)" | |
| [ -x "$GA" ] || { echo "generate_appcast not found under .build/artifacts" >&2; exit 1; } | |
| # How many feed items existed before (to confirm nothing is dropped). 0 on first release. | |
| # Sparkle normally keeps only a few versions per branch; this release feed is intentionally | |
| # append-only so older installs and the other channel's items remain available. | |
| before=0 | |
| [ -f feed/appcast.xml ] && before=$(grep -c "<item>" feed/appcast.xml || true) | |
| cp "dist/$DMG" "feed/$DMG" | |
| # Pre-release tags publish to the Early Access (beta) channel; plain tags publish to everyone. | |
| channel_arg=() | |
| [ "$CHANNEL" = "beta" ] && channel_arg=(--channel beta) | |
| # The key file is base64 (it can contain '/'); pass it via stdin so it's never an argv/glob. | |
| cat "$SPARKLE_PRIVATE_KEY_FILE" | "$GA" \ | |
| --ed-key-file - \ | |
| --download-url-prefix "$DOWNLOAD_PREFIX" \ | |
| --maximum-versions 0 \ | |
| "${channel_arg[@]}" \ | |
| feed | |
| # Fail loudly if the merge went wrong. The new DMG's enclosure must exist AND carry an EdDSA | |
| # signature on the same line (generate_appcast silently omits it if SUPublicEDKey doesn't match | |
| # the signing key), and the item count must not shrink (a dropped preserved/other-channel entry). | |
| after=$(grep -c "<item>" feed/appcast.xml || true) | |
| grep -Eq "$DMG\"[^>]*sparkle:edSignature" feed/appcast.xml \ | |
| || { echo "New item for $DMG is missing or unsigned (SUPublicEDKey/private-key mismatch?)." >&2; exit 1; } | |
| [ "$after" -ge "$before" ] || { echo "Appcast item count dropped ($before -> $after) — aborting." >&2; exit 1; } | |
| echo "Appcast OK: $before -> $after items." | |
| - name: Assemble Pages directory | |
| run: | | |
| mkdir -p public | |
| cp feed/appcast.xml public/appcast.xml | |
| - name: Publish appcast to GitHub Pages | |
| uses: peaceiris/actions-gh-pages@v4 | |
| with: | |
| github_token: ${{ secrets.GITHUB_TOKEN }} | |
| publish_branch: gh-pages | |
| publish_dir: ./public | |
| # gh-pages also hosts pricing_supplement.json (see pricing-supplement.yml). Without | |
| # keep_files, publishing the appcast would delete it from the branch. | |
| keep_files: true |