From f8a99bb64bf014b0e727ee078aa6dae6f2c7d89c Mon Sep 17 00:00:00 2001 From: Serhii Bykov Date: Mon, 10 Aug 2026 18:01:44 +0200 Subject: [PATCH] chore(release): remove staging appcast workflow --- .github/workflows/staging-appcast.yml | 126 -------------------------- Docs/RELEASING.md | 83 +---------------- fastlane/Fastfile | 48 ---------- 3 files changed, 3 insertions(+), 254 deletions(-) delete mode 100644 .github/workflows/staging-appcast.yml diff --git a/.github/workflows/staging-appcast.yml b/.github/workflows/staging-appcast.yml deleted file mode 100644 index 674083c..0000000 --- a/.github/workflows/staging-appcast.yml +++ /dev/null @@ -1,126 +0,0 @@ -name: Staging Appcast - -on: - workflow_dispatch: - inputs: - version: - description: "Marketing version to build, for example 0.9.0" - required: true - type: string - build: - description: "Sparkle build number, for example 90" - required: true - type: string - tag: - description: "GitHub prerelease tag, for example v0.9.0-test" - required: true - type: string - feed_url: - description: "Staging appcast URL compiled into the app" - required: false - default: "https://keyty.app/staging/appcast.xml" - type: string - -permissions: - contents: write - -concurrency: - group: staging-appcast - cancel-in-progress: false - -jobs: - staging-appcast: - runs-on: macos-latest - - env: - GH_TOKEN: ${{ github.token }} - KEYTY_RELEASE_TEAM_ID: ${{ vars.KEYTY_RELEASE_TEAM_ID || 'NEVA4MAZBL' }} - STAGING_APPCAST_DOWNLOAD_PREFIX: ${{ vars.STAGING_APPCAST_DOWNLOAD_PREFIX }} - - steps: - - uses: actions/checkout@v4 - - - name: Install Tuist - run: brew install tuist - - - name: Install Ruby dependencies - run: bundle install - - - name: Install DMG packaging dependencies - run: | - python3 -m venv .venv-release - .venv-release/bin/python -m pip install --upgrade pip - .venv-release/bin/python -m pip install -r requirements-release.txt - printf '%s\n' "$PWD/.venv-release/bin" >> "$GITHUB_PATH" - - - name: Prepare release secrets - env: - DEVELOPER_ID_CERTIFICATE_BASE64: ${{ secrets.DEVELOPER_ID_CERTIFICATE_BASE64 }} - NOTARY_KEY_BASE64: ${{ secrets.NOTARY_KEY_BASE64 }} - SPARKLE_ED_KEY_BASE64: ${{ secrets.SPARKLE_ED_KEY_BASE64 }} - run: | - set -euo pipefail - test -n "$DEVELOPER_ID_CERTIFICATE_BASE64" || { echo "Missing DEVELOPER_ID_CERTIFICATE_BASE64 secret" >&2; exit 1; } - test -n "$NOTARY_KEY_BASE64" || { echo "Missing NOTARY_KEY_BASE64 secret" >&2; exit 1; } - test -n "$SPARKLE_ED_KEY_BASE64" || { echo "Missing SPARKLE_ED_KEY_BASE64 secret" >&2; exit 1; } - mkdir -p "$RUNNER_TEMP/keyty-release" - printf '%s' "$DEVELOPER_ID_CERTIFICATE_BASE64" | base64 -D > "$RUNNER_TEMP/keyty-release/developer-id.p12" - printf '%s' "$NOTARY_KEY_BASE64" | base64 -D > "$RUNNER_TEMP/keyty-release/notary-key.p8" - printf '%s' "$SPARKLE_ED_KEY_BASE64" | base64 -D > "$RUNNER_TEMP/keyty-release/sparkle-ed-key" - test -s "$RUNNER_TEMP/keyty-release/developer-id.p12" || { echo "Decoded Developer ID certificate is empty" >&2; exit 1; } - test -s "$RUNNER_TEMP/keyty-release/notary-key.p8" || { echo "Decoded notary key is empty" >&2; exit 1; } - test -s "$RUNNER_TEMP/keyty-release/sparkle-ed-key" || { echo "Decoded Sparkle EdDSA key is empty" >&2; exit 1; } - - - name: Import Developer ID certificate - env: - CERTIFICATE_PASSWORD: ${{ secrets.DEVELOPER_ID_CERTIFICATE_PASSWORD }} - KEYCHAIN_PASSWORD: ${{ secrets.RELEASE_KEYCHAIN_PASSWORD }} - run: | - set -euo pipefail - test -n "$CERTIFICATE_PASSWORD" || { echo "Missing DEVELOPER_ID_CERTIFICATE_PASSWORD secret" >&2; exit 1; } - test -n "$KEYCHAIN_PASSWORD" || { echo "Missing RELEASE_KEYCHAIN_PASSWORD secret" >&2; exit 1; } - openssl pkcs12 \ - -legacy \ - -in "$RUNNER_TEMP/keyty-release/developer-id.p12" \ - -passin "pass:$CERTIFICATE_PASSWORD" \ - -info \ - -noout >/dev/null - security create-keychain -p "$KEYCHAIN_PASSWORD" "$RUNNER_TEMP/keyty-release.keychain-db" - security set-keychain-settings -lut 21600 "$RUNNER_TEMP/keyty-release.keychain-db" - security unlock-keychain -p "$KEYCHAIN_PASSWORD" "$RUNNER_TEMP/keyty-release.keychain-db" - security import "$RUNNER_TEMP/keyty-release/developer-id.p12" \ - -k "$RUNNER_TEMP/keyty-release.keychain-db" \ - -P "$CERTIFICATE_PASSWORD" \ - -T /usr/bin/codesign \ - -T /usr/bin/security - security list-keychains -d user -s "$RUNNER_TEMP/keyty-release.keychain-db" login.keychain-db - security default-keychain -s "$RUNNER_TEMP/keyty-release.keychain-db" - security set-key-partition-list \ - -S apple-tool:,apple:,codesign: \ - -s \ - -k "$KEYCHAIN_PASSWORD" \ - "$RUNNER_TEMP/keyty-release.keychain-db" - - - name: Build staging appcast release - env: - NOTARY_KEY_PATH: ${{ runner.temp }}/keyty-release/notary-key.p8 - NOTARY_KEY_ID: ${{ secrets.NOTARY_KEY_ID }} - NOTARY_ISSUER: ${{ secrets.NOTARY_ISSUER }} - SPARKLE_ED_KEY_FILE: ${{ runner.temp }}/keyty-release/sparkle-ed-key - STAGING_BUILD: ${{ inputs.build }} - STAGING_FEED_URL: ${{ inputs.feed_url }} - STAGING_TAG: ${{ inputs.tag }} - STAGING_VERSION: ${{ inputs.version }} - run: | - bundle exec fastlane appcast_staging \ - version:"$STAGING_VERSION" \ - build:"$STAGING_BUILD" \ - tag:"$STAGING_TAG" \ - feed_url:"$STAGING_FEED_URL" - - - name: Upload staging appcast artifacts - uses: actions/upload-artifact@v4 - with: - name: staging-appcast-artifacts - path: dist/ - if-no-files-found: error diff --git a/Docs/RELEASING.md b/Docs/RELEASING.md index 061e00a..f8580a0 100644 --- a/Docs/RELEASING.md +++ b/Docs/RELEASING.md @@ -58,8 +58,6 @@ Optionally configure these repository variables: | Variable | Description | |---|---| | `APPCAST_DOWNLOAD_PREFIX` | Sparkle enclosure URL prefix. By default, the release lane uses the current GitHub release asset URL, such as `https://github.com/keytyapp/Keyty/releases/download/v0.8.0/` | -| `STAGING_APPCAST_URL` | Feed URL compiled into staging builds, default `https://keyty.app/staging/appcast.xml` | -| `STAGING_APPCAST_DOWNLOAD_PREFIX` | Staging Sparkle enclosure URL prefix. By default, the staging lane uses the current GitHub prerelease asset URL | | `KEYTY_RELEASE_TEAM_ID` | Developer ID team, default `NEVA4MAZBL` | The release workflow uploads generated appcast artifacts as an Actions artifact. @@ -85,9 +83,9 @@ other fetch failures stop the lane before creating the GitHub release. - The stable artifact names support GitHub's latest-release download URLs, for example `https://github.com/keytyapp/Keyty/releases/latest/download/Keyty.dmg`. - Appcast generation reads the zip from an appcast-only directory so the DMG is not scanned as a duplicate update. -- Production and staging appcasts are cumulative. Previous appcast items keep - their original tag-specific GitHub asset URLs, while the new item uses the - current release or prerelease tag. +- The production appcast is cumulative. Previous appcast items keep their + original tag-specific GitHub asset URLs, while the new item uses the current + release tag. - The DMG is built by [`scripts/build-dmg.sh`](../scripts/build-dmg.sh) from the signed app bundle and includes a drag-to-`Applications` layout. - `dmgbuild` is the preferred branded DMG backend because it does not require an interactive Finder session. The packaging script falls back to the older Finder AppleScript backend when `dmgbuild` is unavailable locally. @@ -112,81 +110,6 @@ You can override the version or build number for a one-off test: bundle exec fastlane beta version:0.8.0 build:123 ``` -## Staging Appcast Test - -Use the `Staging Appcast` workflow when you need to test the full Sparkle update -path before publishing the production appcast. It runs the `appcast_staging` -lane, builds a Developer ID signed and notarized app whose `SUFeedURL` points to -`https://keyty.app/staging/appcast.xml`, creates a public GitHub prerelease with -stable `Keyty.zip` and `Keyty.dmg` assets, generates `dist/staging/appcast.xml` -pointing to that prerelease asset URL, and uploads `dist/` as a workflow -artifact. - -Like production, the staging lane first downloads the currently published -`staging/appcast.xml` and preserves its existing items. Delete or overwrite the -staging feed when you intentionally want to reset staging history after testing. - -Run the `Staging Appcast` workflow with these inputs to build and publish an -initial staging version: - -```text -version: 0.9.0 -build: 90 -tag: v0.9.0-test -feed_url: https://keyty.app/staging/appcast.xml -``` - -Download the `staging-appcast-artifacts` workflow artifact and publish -`staging/appcast.xml` through Vercel at: - -```text -https://keyty.app/staging/appcast.xml -``` - -Then install the staging `0.9.0` app from the prerelease. - -Run the `Staging Appcast` workflow again to build and publish the staging -update: - -```text -version: 0.9.1 -build: 91 -tag: v0.9.1-test -feed_url: https://keyty.app/staging/appcast.xml -``` - -Download the updated `staging-appcast-artifacts` workflow artifact and publish -the updated `staging/appcast.xml` through Vercel. Then use Check for Updates in -the installed staging `0.9.0` app. Sparkle should offer the `0.9.1` update from -the GitHub prerelease asset. The updated staging appcast will include both the -`0.9.0` and `0.9.1` staging items until you reset it: - -```text -https://github.com/keytyapp/Keyty/releases/download/v0.9.1-test/Keyty.zip -``` - -After testing, delete the staging prereleases and tags: - -```bash -gh release delete v0.9.0-test --yes -gh release delete v0.9.1-test --yes -git push origin :refs/tags/v0.9.0-test -git push origin :refs/tags/v0.9.1-test -git tag -d v0.9.0-test v0.9.1-test -``` - -Remove or overwrite `https://keyty.app/staging/appcast.xml` before publishing -the production appcast. The production `Release` workflow skips `*-test` tags, -so staging tags such as `v0.9.0-test` cannot accidentally run the production -release job. - -You can also run the lane locally with the same parameters if you want to build -the staging artifacts on a maintainer Mac: - -```bash -bundle exec fastlane appcast_staging version:0.9.0 build:90 tag:v0.9.0-test -``` - ## Release Checklist 1. Update the app version in the Xcode project build settings: diff --git a/fastlane/Fastfile b/fastlane/Fastfile index d3ff7df..3daae01 100644 --- a/fastlane/Fastfile +++ b/fastlane/Fastfile @@ -16,8 +16,6 @@ # NOTARY_APPLE_ID / NOTARY_PASSWORD -> Apple ID app-specific password # SPARKLE_ED_KEY_FILE -> EdDSA private key file # APPCAST_DOWNLOAD_PREFIX -> base URL for enclosures -# STAGING_APPCAST_URL -> feed URL compiled into staging builds -# STAGING_APPCAST_DOWNLOAD_PREFIX -> staging base URL for enclosures # KEYTY_RELEASE_TEAM_ID -> Developer ID team # KEYTY_RELEASE_CODE_SIGN_IDENTITY -> release signing identity # KEYTY_NOTARY_PROFILE -> local notarytool keychain profile @@ -38,8 +36,6 @@ BUILD_DIR = File.join(REPO_ROOT, "build", "release") # gitignored (build/) DIST_DIR = File.join(REPO_ROOT, "dist") # release artifacts + appcast.xml APPCAST_DIST_DIR = File.join(DIST_DIR, "appcast") # production Sparkle feed inputs BETA_DIST_DIR = File.join(DIST_DIR, "beta") # local test distribution artifacts -STAGING_DIST_DIR = File.join(DIST_DIR, "staging") # staging appcast + artifacts -STAGING_APPCAST_DIST_DIR = File.join(STAGING_DIST_DIR, "appcast") APPCAST_FILENAME = "appcast.xml" DMG_SCRIPT = File.join(REPO_ROOT, "scripts", "build-dmg.sh") APP_ENTITLEMENTS = File.join(REPO_ROOT, "Apps", "Keyty", "Sources", "Keyty", "Resources", "Keyty.entitlements") @@ -54,7 +50,6 @@ RELEASE_CODE_SIGN_IDENTITY = ENV["KEYTY_RELEASE_CODE_SIGN_IDENTITY"] || "Develop # unique per release. DOWNLOAD_PREFIX = ENV["APPCAST_DOWNLOAD_PREFIX"].to_s.strip PRODUCTION_APPCAST_URL = "https://keyty.app/appcast.xml" -DEFAULT_STAGING_APPCAST_URL = "https://keyty.app/staging/appcast.xml" platform :mac do desc "Full release: build, sign, notarize, staple, GitHub release, update appcast" @@ -111,38 +106,6 @@ platform :mac do UI.message("ZIP: #{zip}") UI.message("Skipped GitHub release, Sparkle appcast generation, and feed publishing.") end - - desc "Staging appcast test: build, sign, notarize, prerelease, update staging appcast" - lane :appcast_staging do |options| - generate_project - - version = options[:version] || marketing_version - build = (options[:build] || build_number).to_s - tag = (options[:tag] || "v#{version}-test").to_s - feed_url = staging_appcast_url(options) - app = File.join(BUILD_DIR, "#{APP_NAME}.app") - zip = File.join(STAGING_APPCAST_DIST_DIR, "#{APP_NAME}.zip") - dmg = File.join(STAGING_DIST_DIR, "#{APP_NAME}.dmg") - download_prefix = staging_appcast_download_prefix(tag: tag) - UI.header("Building staging #{APP_NAME} #{version} (#{build}) for #{feed_url}") - - build_distribution_artifacts(version: version, build: build, app: app, zip: zip, dmg: dmg, feed_url: feed_url) - - seed_appcast_history(feed_url: feed_url, archive_dir: STAGING_APPCAST_DIST_DIR) - - sh("gh release create #{tag.shellescape} #{zip.shellescape} " \ - "#{dmg.shellescape} --title #{"#{APP_NAME} #{version} Staging".shellescape} " \ - "--generate-notes --prerelease") - - generate_cumulative_appcast( - archive_dir: STAGING_APPCAST_DIST_DIR, - output_path: File.join(STAGING_DIST_DIR, APPCAST_FILENAME), - download_prefix: download_prefix - ) - - UI.success("Built staging appcast test release #{tag}.") - UI.message("Next: publish #{STAGING_DIST_DIR}/appcast.xml to #{feed_url}.") - end end # --- helpers --------------------------------------------------------------- @@ -157,17 +120,6 @@ def appcast_download_prefix(tag:) github_release_download_prefix(tag: tag) end -def staging_appcast_url(options) - (options[:feed_url] || ENV["STAGING_APPCAST_URL"] || DEFAULT_STAGING_APPCAST_URL).to_s.strip -end - -def staging_appcast_download_prefix(tag:) - prefix = ENV["STAGING_APPCAST_DOWNLOAD_PREFIX"].to_s.strip - return trailing_slash(prefix) unless prefix.empty? - - github_release_download_prefix(tag: tag) -end - def github_release_download_prefix(tag:) repository = github_repository return "https://github.com/#{repository}/releases/download/#{tag}/" unless repository.empty?