Skip to content

Latest commit

 

History

History
134 lines (106 loc) · 7.1 KB

File metadata and controls

134 lines (106 loc) · 7.1 KB

Releasing Table Tool X

Releases are created only from an annotated semantic-version tag. The Release GitHub Actions environment must be protected and should require a maintainer approval.

This fork preserves Jakob Egger's tags through v1.2.1 (build 8) and links to the corresponding upstream release history. Table Tool X continues the same public version sequence at v1.3.0 (build 9); historical tags must never be deleted, recreated, or moved.

One-time repository setup

Configure these GitHub Actions secrets:

Secret Value
DEVELOPER_ID_P12 Base64-encoded Developer ID Application certificate and private key
DEVELOPER_ID_PASSWORD Password used when exporting the .p12
APPLE_TEAM_ID Ten-character Apple Developer team ID
APPLE_ID Apple ID used by notarytool
APPLE_APP_PASSWORD App-specific password for that Apple ID
SPARKLE_ED_PRIVATE_KEY Private Ed25519 key generated by Sparkle's generate_keys tool

The Sparkle private key must also remain in a maintainer's macOS Keychain under the account com.leanderrj.TableToolX. The matching public key is committed as SUPublicEDKey; rotating it requires a signed transition release, not a normal plist edit.

After approving the macOS Keychain access prompt, install that existing private key directly into GitHub without printing it or writing an export file:

security find-generic-password \
  -a com.leanderrj.TableToolX \
  -s https://sparkle-project.org \
  -w | gh secret set SPARKLE_ED_PRIVATE_KEY --repo leanderrj/TableToolX

The configured release environment requires approval and permits only main (manual candidate runs) or tags matching v* (publication). Do not put signing material in repository variables, files, artifacts, or logs.

The repository settings are part of the release boundary:

  • Actions may use GitHub-owned actions only, every action must be pinned to a full commit SHA, and the default workflow token permission is read-only. Swift CodeQL uploads results through the narrowly scoped security-events: write permission in its own workflow.
  • main requires the macOS 14 / Xcode 16.2 and macOS 15 / Xcode 16.4 checks, a linear history, resolved review conversations, and blocks force-pushes and deletion.
  • The active Protect release tags ruleset covers refs/tags/v* and blocks deletion or non-fast-forward updates. Repository administrators retain the recovery bypass.
  • Repository-level immutable releases are enabled. Publishing permanently locks each new release's assets and tag and makes GitHub issue signed release attestations.
  • The protected release environment requires maintainer approval and permits deployments only from main or tags matching v*.

Compatibility CI deliberately uses macOS 14/Xcode 16.2 and macOS 15/Xcode 16.4. Signing, notarization, and Homebrew auditing use GitHub's current macOS 26 image so Apple's distribution tools and Homebrew's supported Xcode remain current while the produced app still targets macOS 14.

The workflow derives the Ed25519 public key from SPARKLE_ED_PRIVATE_KEY and compares it with the app's committed SUPublicEDKey before any build or notarization work. A mismatched or legacy-format secret fails closed instead of publishing an appcast that clients cannot verify. It also verifies that the imported Developer ID identity belongs to APPLE_TEAM_ID, creates secret-derived files with owner-only permissions, and removes the temporary certificate and keychain in an always-run cleanup step.

Release gates

Before tagging:

  1. Move the relevant entries in docs/CHANGELOG.md from Unreleased to the release version.
  2. Set MARKETING_VERSION in project.yml to the exact tag version without the v.
  3. Run xcodegen generate, the Swift and Xcode test suites, and the opt-in large-file performance test. Release metadata validation is enforced directly by the workflow.
  4. Test save/reopen, malformed-input recovery, undo/redo, clipboard operations, VoiceOver, dark/light appearance, and Sparkle updating from the previous public version.
  5. Merge only when CI is green and no known data-integrity or release-blocking issue remains.

The version heading may be ## 1.3.0, ## 1.3.0 - YYYY-MM-DD, or the equivalent bracketed Keep a Changelog form. Tag publication extracts that section for both the GitHub Release body and Sparkle's embedded release notes and fails if it is missing or empty. Manual candidates use the current Unreleased section.

Before creating a tag, run the Release workflow manually with the intended version. A manual run executes the complete Developer ID, notarization, stapling, universal-binary, Sparkle, checksum, Homebrew, and provenance path, then retains the notarized candidate as a seven-day workflow artifact. It never creates a GitHub Release. Install that candidate on a clean Intel or Apple Silicon Mac for the final acceptance pass.

Create and push an annotated tag:

git tag -a v1.3.0 -m "Table Tool X 1.3.0"
git push origin v1.3.0

The tag workflow fails before publishing if any credential is absent or if the tag and project version differ. It also rejects lightweight tags and tags whose commit is not on main. It then tests, archives with Hardened Runtime, exports with Developer ID, notarizes and staples the app, builds a ZIP and DMG, generates an Ed25519-signed Sparkle appcast, produces checksums and a Homebrew cask pinned to the release ZIP, stages that cask in an ephemeral tap for Homebrew style and offline audit checks, attests the artifacts, verifies every uploaded asset, and finally publishes the draft as an immutable release. Before publication the workflow compares GitHub's stored SHA-256 digest for every draft asset; afterward it verifies GitHub's immutable release and asset attestations. Publication explicitly marks the new release as Latest because the GitHub fork retains Jakob's historical Table Tool tags. The cask update workflow repeats the audit with network checks after the assets have public URLs. The release job invokes that workflow explicitly with workflow_dispatch: GitHub intentionally suppresses ordinary release events caused by GITHUB_TOKEN. A manually published stable release can still use the workflow's release trigger, and repeated dispatches stop when the version's automation branch exists.

The stable appcast URL is:

https://github.com/leanderrj/TableToolX/releases/latest/download/appcast.xml

After publication, a second workflow audits the generated Homebrew cask and opens a pull request updating Casks/table-tool-x.rb.

Verification and rollback

On a clean Mac, verify the downloaded artifacts before announcing the release:

shasum -a 256 -c SHA256SUMS
spctl --assess --type execute --verbose=2 "/Applications/Table Tool X.app"
xcrun stapler validate "/Applications/Table Tool X.app"

Also install the previous version and confirm that Check for Updates… downloads and installs the new ZIP. If a bad release is published, mark it as a prerelease and publish a new patch version. Never replace a signed asset in place: Sparkle signatures, GitHub attestations, Homebrew hashes, and user trust all depend on release assets being immutable.