Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
126 changes: 0 additions & 126 deletions .github/workflows/staging-appcast.yml

This file was deleted.

83 changes: 3 additions & 80 deletions Docs/RELEASING.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -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.

Expand All @@ -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:
Expand Down
48 changes: 0 additions & 48 deletions fastlane/Fastfile
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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")
Expand All @@ -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"
Expand Down Expand Up @@ -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 ---------------------------------------------------------------
Expand All @@ -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?
Expand Down
Loading