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.
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/TableToolXThe 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: writepermission in its own workflow. mainrequires 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 tagsruleset coversrefs/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
releaseenvironment requires maintainer approval and permits deployments only frommainor tags matchingv*.
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.
Before tagging:
- Move the relevant entries in
docs/CHANGELOG.mdfrom Unreleased to the release version. - Set
MARKETING_VERSIONinproject.ymlto the exact tag version without thev. - 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. - Test save/reopen, malformed-input recovery, undo/redo, clipboard operations, VoiceOver, dark/light appearance, and Sparkle updating from the previous public version.
- 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.0The 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.
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.