ci(release): automate Sparkle ZIP, appcast, and channels - #786
Conversation
Extend the tag Release workflow to package a notarized Sparkle ZIP, sign updates with generate_appcast, attach deltas, and publish appcast.xml to gh-pages after the GitHub Release assets exist. Requires repo secret SPARKLE_ED25519_PRIVATE_KEY (EdDSA private key matching SUPublicEDKey). Ask stonerl to add it before first use.
📝 WalkthroughWalkthroughAdds a reusable Sparkle release action and updates the release workflow to resolve channels, generate signed appcast and delta artifacts, upload release assets, and optionally publish ChangesSparkle release pipeline
Estimated code review effort: 4 (Complex) | ~45 minutes Sequence Diagram(s)sequenceDiagram
participant WorkflowDispatch
participant ReleaseWorkflow
participant SparkleReleaseAction
participant GitHubRelease
participant GhPages
WorkflowDispatch->>ReleaseWorkflow: provide tag, channel, and publish flags
ReleaseWorkflow->>ReleaseWorkflow: resolve tag, channel, and prerelease
ReleaseWorkflow->>SparkleReleaseAction: generate ZIP and deltas
SparkleReleaseAction-->>ReleaseWorkflow: return release asset paths
ReleaseWorkflow->>GitHubRelease: upload DMG, ZIP, and delta assets
ReleaseWorkflow->>GhPages: conditionally publish appcast.xml
Possibly related PRs
Suggested labels: Suggested reviewers: 🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 3
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In @.github/actions/sparkle-release/action.yml:
- Around line 63-69: Remove the failure suppression from the xcrun stapler
staple command in the release action so stapling errors fail the workflow;
ensure the zip_name, zip_path, and ditto packaging steps execute only after
successful stapling.
- Around line 121-140: Update the appcast generation flow around
generate_appcast so each archive’s enclosure URL uses the release tag from which
that ZIP was fetched, while the current archive uses TAG. Preserve correct
historical URLs for retained prior releases instead of applying one ${TAG}
prefix to every entry, using post-processing or per-archive URL generation
within the existing archive-fetch logic.
In @.github/workflows/release.yml:
- Around line 63-71: Update the automatic channel detection in the TAG_NAME
conditional so alpha, beta, rc, and nightly markers are recognized only as
documented suffix tokens, including the preceding hyphen and end-of-tag
boundary. Preserve stable as the fallback and avoid substring matches that
classify prefixes such as “alphabet” or unrelated trailing text.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: 1368f2dd-01f3-4236-bac1-cf19e1f8afa7
📒 Files selected for processing (2)
.github/actions/sparkle-release/action.yml.github/workflows/release.yml
Fail stapler staple/validate before packaging, match channel markers as hyphenated suffix tokens, and rewrite appcast enclosure URLs per archive tag.
|
LGTM |
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In @.github/workflows/release.yml:
- Line 36: Update the release workflow’s runs-on value to use the supported
macos-26 label, unless a self-hosted runner is explicitly registered with the
exact xcode-27 label; ensure the release job targets an available runner.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro Plus
Run ID: 1babd2f5-7011-488d-8c07-2c78b840d416
📒 Files selected for processing (1)
.github/workflows/release.yml
| jobs: | ||
| release: | ||
| runs-on: macos-26 | ||
| runs-on: xcode-27 |
There was a problem hiding this comment.
🩺 Stability & Availability | 🟠 Major | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "== workflow existence and relevant lines =="
if git ls-files | grep -Fxq .github/workflows/release.yml; then
nl -ba .github/workflows/release.yml | sed -n '1,80p'
else
echo "release workflow not found"
fi
echo
echo "== runner label occurrences in workflows =="
rg -n "runs-on:\s*xcode-27|self-hosted|runner" .github/workflows || true
echo
echo "== available GitHub-hosted macOS runner images page section =="
# read-only: inspect public documentation HTML for xcode labels
python3 - <<'PY'
import urllib.request, urllib.error
url='https://github.com/actions/runner-images/blob/main/README.md'
try:
with urllib.request.urlopen(url, timeout=20) as r:
data=r.read().decode('utf-8', 'replace')
for needle in ['xcode-27', 'macos-26', 'macos-latest', 'macos-14']:
print(f"{needle}: {needle in data}")
except Exception as e:
print(f"fetch failed: {e}")
PYRepository: stonerl/Thaw
Length of output: 234
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "== workflow file presence and runner lines =="
if git ls-files | grep -Fxq .github/workflows/release.yml; then
awk '{printf "%6d %s\n", NR, $0}' .github/workflows/release.yml | sed -n '1,90p'
else
echo "release workflow not found"
fi
echo
echo "== runner label occurrences in workflows =="
grep -RInE 'runs-on:\s*xcode-27|runs-on:\s*.*self-hosted|runner|macos-26|macos-latest|xcode-27' .github/workflows || true
echo
echo "== available GitHub-hosted macOS runner labels around release runners =="
python3 - <<'PY'
import urllib.request, urllib.error
url='https://github.com/actions/runner-images/blob/main/README.md'
try:
with urllib.request.urlopen(url, timeout=20) as r:
txt=r.read().decode('utf-8', 'replace')
for needle in ['xcode-27', 'macos-26', 'macos-latest', 'macos-14', 'macos-13']:
idx=txt.find(needle)
if idx == -1:
print(f"{needle}: not found")
else:
start=max(0, idx-180)
end=min(len(txt), idx+180)
print(f"{needle}: found")
print(txt[start:end].replace('\n',' '))
except Exception as e:
print(f"fetch failed: {type(e).__name__}: {e}")
PYRepository: stonerl/Thaw
Length of output: 19228
Verify the xcode-27 runner before relying on it in release.yml.
xcode-27 is not a supported GitHub-hosted runner label; other workflows in this repo use macos-26. If release.yml is intended to run on a self-hosted runner, ensure that runner is registered with exactly the xcode-27 label. Otherwise, switch it to macos-26 to avoid the release job being queued without a matching runner.
🧰 Tools
🪛 actionlint (1.7.12)
[error] 36-36: label "xcode-27" is unknown. available labels are "windows-latest", "windows-latest-8-cores", "windows-2025", "windows-2025-vs2026", "windows-2022", "windows-11-arm", "ubuntu-slim", "ubuntu-latest", "ubuntu-latest-4-cores", "ubuntu-latest-8-cores", "ubuntu-latest-16-cores", "ubuntu-24.04", "ubuntu-24.04-arm", "ubuntu-22.04", "ubuntu-22.04-arm", "macos-latest", "macos-latest-xlarge", "macos-latest-large", "macos-26-intel", "macos-26-xlarge", "macos-26-large", "macos-26", "macos-15-intel", "macos-15-xlarge", "macos-15-large", "macos-15", "macos-14-xlarge", "macos-14-large", "macos-14", "self-hosted", "x64", "arm", "arm64", "linux", "macos", "windows". if it is a custom label for self-hosted runner, set list of labels in actionlint.yaml config file
(runner-label)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In @.github/workflows/release.yml at line 36, Update the release workflow’s
runs-on value to use the supported macos-26 label, unless a self-hosted runner
is explicitly registered with the exact xcode-27 label; ensure the release job
targets an available runner.
Source: Linters/SAST tools
Summary
Automates the Sparkle half of Thaw releases: produce a Sparkle ZIP, generate the appcast (EdDSA + deltas), attach assets to the GitHub Release, and publish
appcast.xmltogh-pages. Infers Sparkle channel from the tag (stable/betafor-rc/-beta/alphafor-alpha/-nightly), with aworkflow_dispatchoverride. Apple sign/notarize stays as-is.Closes: N/A
PR Type
Does this PR introduce a breaking change?
What is the new behavior?
Thaw_<tag>.zip, rungenerate_appcast, attach ZIP/deltas to the GitHub Release, then publishappcast.xmltogh-pageswhen the release is published.stable/beta/alpha), with aworkflow_dispatchchannel override.PR Checklist
swiftformat .to keep the code style consistent.Other information
Maintainer action required before the first Sparkle release run — add this Actions secret:
SPARKLE_ED25519_PRIVATE_KEY— EdDSA private key matchingSUPublicEDKeyinInfo.plist(fQ2kWqCLfAPAxQX1rp8gVNoG9hlAV/Gmm7kMBbxFe+A=).Without that secret, the new Sparkle step fails; DMG notarization is unchanged.
Validation
SPARKLE_ED25519_PRIVATE_KEYis set in repo Actions secretsworkflow_dispatchwithpublish_releaseunchecked (draft release, no appcast push)x.y.z-rc.N) and verify ZIP + deltas on the release,sparkle:channel=betain appcast when publishedhttps://stonerl.github.io/Thaw/appcast.xmlupdates only after the release is published (not while draft)Summary by CodeRabbit