Skip to content

ci(release): automate Sparkle ZIP, appcast, and channels - #786

Merged
diazdesandi merged 3 commits into
developmentfrom
ci/sparkle-release-workflow
Jul 23, 2026
Merged

ci(release): automate Sparkle ZIP, appcast, and channels#786
diazdesandi merged 3 commits into
developmentfrom
ci/sparkle-release-workflow

Conversation

@diazdesandi

@diazdesandi diazdesandi commented Jul 16, 2026

Copy link
Copy Markdown
Member

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.xml to gh-pages. Infers Sparkle channel from the tag (stable / beta for -rc/-beta / alpha for -alpha/-nightly), with a workflow_dispatch override. Apple sign/notarize stays as-is.

External contributors: before opening a PR for a bug fix or new feature, please make sure there's a corresponding issue in the issue tracker. PRs that fix or change things that haven't been reported/agreed on may be closed without review.

Closes: N/A

PR Type

  • Bug fix
  • CI/CD
  • Documentation
  • Feature
  • Performance improvement
  • Refactor
  • Test addition or update
  • Other (please describe)

Does this PR introduce a breaking change?

  • Yes - if yes, please describe the impact and migration path
  • No

What is the new behavior?

  • Extends the Release workflow to build Thaw_<tag>.zip, run generate_appcast, attach ZIP/deltas to the GitHub Release, then publish appcast.xml to gh-pages when the release is published.
  • Infers Sparkle channel from the tag (stable / beta / alpha), with a workflow_dispatch channel override.
  • Adds independent flags for publishing the GitHub Release vs the appcast (draft dry-runs remain possible).

PR Checklist

  • I've built and run the app locally and verified that it works as expected.
  • I've run swiftformat . to keep the code style consistent.
  • I've added tests for new behavior (if applicable)
  • I've updated documentation as needed

Other information

Maintainer action required before the first Sparkle release run — add this Actions secret:

  • SPARKLE_ED25519_PRIVATE_KEY — EdDSA private key matching SUPublicEDKey in Info.plist (fQ2kWqCLfAPAxQX1rp8gVNoG9hlAV/Gmm7kMBbxFe+A=).

Without that secret, the new Sparkle step fails; DMG notarization is unchanged.

Validation

  • Confirm SPARKLE_ED25519_PRIVATE_KEY is set in repo Actions secrets
  • Dry-run via workflow_dispatch with publish_release unchecked (draft release, no appcast push)
  • Tag or dispatch a prerelease (e.g. x.y.z-rc.N) and verify ZIP + deltas on the release, sparkle:channel = beta in appcast when published
  • Verify https://stonerl.github.io/Thaw/appcast.xml updates only after the release is published (not while draft)
  • Existing installs on Stable/Development still verify signatures against the current public key

Summary by CodeRabbit

  • New Features
    • Added automated Sparkle release packaging with notarized ZIPs, update feeds, and delta updates.
    • Supports stable, beta, alpha, and nightly release channels.
    • Release assets can include DMG, ZIP, and delta archives.
  • Improvements
    • Release tags now automatically determine the appropriate update channel.
    • Manual releases can be prepared as drafts before publication.
    • Appcast publishing can be managed separately from GitHub Release publication.

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.
@coderabbitai

coderabbitai Bot commented Jul 16, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

📝 Walkthrough

Walkthrough

Adds 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 appcast.xml to gh-pages.

Changes

Sparkle release pipeline

Layer / File(s) Summary
Sparkle action packaging and tooling
.github/actions/sparkle-release/action.yml
Defines the action contract, staples and zips the app, and installs the requested Sparkle tools.
Appcast generation and publication
.github/actions/sparkle-release/action.yml
Stages release archives, generates and signs appcast.xml, exports delta paths, and optionally updates gh-pages.
Release workflow orchestration
.github/workflows/release.yml
Adds dispatch controls and tag/channel resolution, collects DMG/ZIP/delta assets, creates the GitHub Release, and conditionally publishes the appcast.

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
Loading

Possibly related PRs

  • stonerl/Thaw#483: Modifies overlapping release workflow tag handling and packaging integration.
  • stonerl/Thaw#513: Modifies the same release workflow’s tag validation and release flow.
  • stonerl/Thaw#530: Provides related modular release packaging and notarization workflow structure.

Suggested labels: chore

Suggested reviewers: stonerl

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Title check ✅ Passed The title is concise and accurately summarizes the main release automation change.
Description check ✅ Passed The description follows the template with the required sections filled in and the change clearly explained.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch ci/sparkle-release-workflow

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

📥 Commits

Reviewing files that changed from the base of the PR and between b01530b and 4713260.

📒 Files selected for processing (2)
  • .github/actions/sparkle-release/action.yml
  • .github/workflows/release.yml

Comment thread .github/actions/sparkle-release/action.yml
Comment thread .github/actions/sparkle-release/action.yml
Comment thread .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.
@github-actions github-actions Bot added cd Related to deployments or release automation ci Related to continuous integration workflows or runners. labels Jul 22, 2026
@stonerl

stonerl commented Jul 22, 2026

Copy link
Copy Markdown
Collaborator

LGTM

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

📥 Commits

Reviewing files that changed from the base of the PR and between e0bc6a4 and 8966e66.

📒 Files selected for processing (1)
  • .github/workflows/release.yml

jobs:
release:
runs-on: macos-26
runs-on: xcode-27

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🩺 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}")
PY

Repository: 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}")
PY

Repository: 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

@diazdesandi
diazdesandi marked this pull request as draft July 23, 2026 03:02
@diazdesandi
diazdesandi marked this pull request as ready for review July 23, 2026 03:02
@diazdesandi
diazdesandi merged commit 73c8d3f into development Jul 23, 2026
1 of 4 checks passed
@diazdesandi
diazdesandi deleted the ci/sparkle-release-workflow branch July 23, 2026 03:04
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

cd Related to deployments or release automation ci Related to continuous integration workflows or runners.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants