Add /release-nightly skill and fix release skills (#85)

- New /release-nightly: end-to-end version bump + local build + release
- Fix /release-local: source secrets directly, use signing hash to avoid
  keychain ambiguity, correct create-dmg --codesign flag, export
  SPARKLE_PRIVATE_KEY for appcast generation
- Add `say` notification on completion/failure to all release skills
This commit is contained in:
Lawrence Chen 2026-02-18 20:14:17 -08:00 committed by GitHub
parent 9e3f5830a8
commit fb85b690d5
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 244 additions and 19 deletions

View file

@ -1,25 +1,32 @@
# Release Local
Build, sign, notarize, and upload a release locally (no GitHub Actions). Requires direnv to be loaded with Apple signing secrets from `~/.secrets/cmuxterm.env`.
Build, sign, notarize, and upload a release locally (no GitHub Actions). Secrets are in `~/.secrets/cmuxterm.env`.
## Secrets
Source secrets directly (do NOT rely on direnv):
```bash
source ~/.secrets/cmuxterm.env && export SPARKLE_PRIVATE_KEY
```
## Signing Identity
The Manaflow signing identity exists in both login and system keychains. Always use the SHA-1 hash to avoid ambiguity:
```
SIGN_HASH="A050CC7E193C8221BDBA204E731B046CDCCC1B30"
```
Use `$SIGN_HASH` instead of `$APPLE_SIGNING_IDENTITY` for all codesign and create-dmg commands.
## Pre-flight Checks
Before starting, verify all required tools and secrets are available. Run these checks and **stop immediately** if any fail:
```bash
# Required env vars (loaded via direnv from ~/.secrets/cmuxterm.env)
for var in APPLE_SIGNING_IDENTITY APPLE_ID APPLE_APP_SPECIFIC_PASSWORD APPLE_TEAM_ID SPARKLE_PRIVATE_KEY; do
if [ -z "${!var:-}" ]; then echo "MISSING: $var — run 'direnv allow' or check ~/.secrets/cmuxterm.env"; exit 1; fi
done
# Required tools
source ~/.secrets/cmuxterm.env
for tool in zig xcodebuild create-dmg xcrun codesign ditto gh; do
command -v "$tool" >/dev/null || { echo "MISSING: $tool"; exit 1; }
done
# Signing identity exists in keychain
security find-identity -v -p codesigning | grep -q "$APPLE_SIGNING_IDENTITY" || { echo "Signing identity not in keychain"; exit 1; }
echo "All pre-flight checks passed"
```
@ -69,11 +76,12 @@ Sign the embedded CLI binary first, then deep-sign the entire app bundle:
```bash
APP_PATH="build/Build/Products/Release/cmux.app"
ENTITLEMENTS="cmux.entitlements"
SIGN_HASH="A050CC7E193C8221BDBA204E731B046CDCCC1B30"
CLI_PATH="$APP_PATH/Contents/Resources/bin/cmux"
if [ -f "$CLI_PATH" ]; then
/usr/bin/codesign --force --options runtime --timestamp --sign "$APPLE_SIGNING_IDENTITY" --entitlements "$ENTITLEMENTS" "$CLI_PATH"
/usr/bin/codesign --force --options runtime --timestamp --sign "$SIGN_HASH" --entitlements "$ENTITLEMENTS" "$CLI_PATH"
fi
/usr/bin/codesign --force --options runtime --timestamp --sign "$APPLE_SIGNING_IDENTITY" --entitlements "$ENTITLEMENTS" --deep "$APP_PATH"
/usr/bin/codesign --force --options runtime --timestamp --sign "$SIGN_HASH" --entitlements "$ENTITLEMENTS" --deep "$APP_PATH"
/usr/bin/codesign --verify --deep --strict --verbose=2 "$APP_PATH"
```
@ -98,8 +106,9 @@ xcrun notarytool log <submission-id> --apple-id "$APPLE_ID" --team-id "$APPLE_TE
```bash
APP_PATH="build/Build/Products/Release/cmux.app"
create-dmg --identity="$APPLE_SIGNING_IDENTITY" "$APP_PATH" ./
mv ./cmux*.dmg cmux-macos.dmg
SIGN_HASH="A050CC7E193C8221BDBA204E731B046CDCCC1B30"
rm -f cmux-macos.dmg
create-dmg --codesign "$SIGN_HASH" cmux-macos.dmg "$APP_PATH"
xcrun notarytool submit cmux-macos.dmg --apple-id "$APPLE_ID" --team-id "$APPLE_TEAM_ID" --password "$APPLE_APP_SPECIFIC_PASSWORD" --wait
xcrun stapler staple cmux-macos.dmg
xcrun stapler validate cmux-macos.dmg
@ -107,7 +116,10 @@ xcrun stapler validate cmux-macos.dmg
### 8. Generate Sparkle appcast
`SPARKLE_PRIVATE_KEY` must be exported (not just sourced):
```bash
source ~/.secrets/cmuxterm.env && export SPARKLE_PRIVATE_KEY
./scripts/sparkle_generate_appcast.sh cmux-macos.dmg "$TAG" appcast.xml
```
@ -119,10 +131,24 @@ gh release upload "$TAG" cmux-macos.dmg appcast.xml --clobber
Verify the release: `gh release view "$TAG"`
### 10. Cleanup
### 10. Cleanup and notify
```bash
rm -rf build/
rm -rf build/ cmux-macos.dmg appcast.xml
```
## Completion
When the release is fully done (DMG uploaded, release verified), always run:
```bash
say "cmux release complete"
```
If the release fails at any point, run:
```bash
say "cmux release failed"
```
## Important Notes

View file

@ -0,0 +1,195 @@
# Release Nightly
End-to-end release: bump version, update changelog, create PR, merge, tag, build locally, sign, notarize, upload DMG. Combines `/release` + `/release-local` into a single command.
## Secrets
Source secrets directly (do NOT rely on direnv):
```bash
source ~/.secrets/cmuxterm.env && export SPARKLE_PRIVATE_KEY
```
## Signing Identity
The Manaflow signing identity exists in both login and system keychains. Always use the SHA-1 hash to avoid ambiguity:
```
SIGN_HASH="A050CC7E193C8221BDBA204E731B046CDCCC1B30"
```
Use `$SIGN_HASH` instead of `$APPLE_SIGNING_IDENTITY` for all codesign and create-dmg commands.
## Steps
### Phase 1: Version bump, changelog, PR, merge, tag (same as /release)
1. **Determine the new version number**
- Get the current version from `GhosttyTabs.xcodeproj/project.pbxproj` (look for `MARKETING_VERSION`)
- Bump the minor version unless the user specifies otherwise (e.g., 0.48.0 → 0.49.0)
2. **Create a release branch**
- Create branch: `git checkout -b release/vX.Y.Z`
3. **Gather changes since the last release**
- Find the most recent git tag: `git describe --tags --abbrev=0`
- Get commits since that tag: `git log --oneline <last-tag>..HEAD --no-merges`
- **Filter for end-user visible changes only** - ignore developer tooling, CI, docs, tests
- Categorize changes into: Added, Changed, Fixed, Removed
4. **Update the changelog**
- Add a new section at the top of `CHANGELOG.md` with the new version and today's date
- **Only include changes that affect the end-user experience**
- Write clear, user-facing descriptions (not raw commit messages)
- Also update `docs-site/content/docs/changelog.mdx` if it exists
- If there are no user-facing changes, ask the user if they still want to release
5. **Bump the version**
- Run `./scripts/bump-version.sh` (bumps minor by default)
6. **Commit and push the release branch**
- Stage: `CHANGELOG.md`, `GhosttyTabs.xcodeproj/project.pbxproj`
- Commit message: `Bump version to X.Y.Z`
- Push: `git push -u origin release/vX.Y.Z`
7. **Create PR and wait for CI**
- `gh pr create --title "Release vX.Y.Z" --body "...changelog..."`
- `gh pr checks --watch`
8. **Merge PR**
- `gh pr merge --squash --delete-branch`
- `git checkout main && git pull`
9. **Create and push the tag**
- `git tag vX.Y.Z && git push origin vX.Y.Z`
### Phase 2: Local build, sign, notarize, upload (same as /release-local)
10. **Build GhosttyKit (if needed)**
Skip if `GhosttyKit.xcframework` already exists.
```bash
if [ ! -d "GhosttyKit.xcframework" ]; then
cd ghostty && zig build -Demit-xcframework=true -Demit-macos-app=false -Dxcframework-target=native -Doptimize=ReleaseFast && cd ..
rm -rf GhosttyKit.xcframework
cp -R ghostty/macos/GhosttyKit.xcframework GhosttyKit.xcframework
fi
```
11. **Build app (Release, unsigned)**
```bash
rm -rf build/
xcodebuild -scheme cmux -configuration Release -derivedDataPath build CODE_SIGNING_ALLOWED=NO build
```
12. **Inject Sparkle keys into Info.plist**
```bash
source ~/.secrets/cmuxterm.env && export SPARKLE_PRIVATE_KEY
SPARKLE_PUBLIC_KEY_DERIVED=$(swift scripts/derive_sparkle_public_key.swift "$SPARKLE_PRIVATE_KEY")
APP_PLIST="build/Build/Products/Release/cmux.app/Contents/Info.plist"
/usr/libexec/PlistBuddy -c "Delete :SUPublicEDKey" "$APP_PLIST" 2>/dev/null || true
/usr/libexec/PlistBuddy -c "Delete :SUFeedURL" "$APP_PLIST" 2>/dev/null || true
/usr/libexec/PlistBuddy -c "Add :SUPublicEDKey string $SPARKLE_PUBLIC_KEY_DERIVED" "$APP_PLIST"
/usr/libexec/PlistBuddy -c "Add :SUFeedURL string https://github.com/manaflow-ai/cmux/releases/latest/download/appcast.xml" "$APP_PLIST"
```
13. **Codesign app**
```bash
APP_PATH="build/Build/Products/Release/cmux.app"
ENTITLEMENTS="cmux.entitlements"
SIGN_HASH="A050CC7E193C8221BDBA204E731B046CDCCC1B30"
CLI_PATH="$APP_PATH/Contents/Resources/bin/cmux"
if [ -f "$CLI_PATH" ]; then
/usr/bin/codesign --force --options runtime --timestamp --sign "$SIGN_HASH" --entitlements "$ENTITLEMENTS" "$CLI_PATH"
fi
/usr/bin/codesign --force --options runtime --timestamp --sign "$SIGN_HASH" --entitlements "$ENTITLEMENTS" --deep "$APP_PATH"
/usr/bin/codesign --verify --deep --strict --verbose=2 "$APP_PATH"
```
14. **Notarize app**
```bash
source ~/.secrets/cmuxterm.env
APP_PATH="build/Build/Products/Release/cmux.app"
ditto -c -k --sequesterRsrc --keepParent "$APP_PATH" cmux-notary.zip
xcrun notarytool submit cmux-notary.zip --apple-id "$APPLE_ID" --team-id "$APPLE_TEAM_ID" --password "$APPLE_APP_SPECIFIC_PASSWORD" --wait
xcrun stapler staple "$APP_PATH"
xcrun stapler validate "$APP_PATH"
rm -f cmux-notary.zip
```
15. **Create and notarize DMG**
```bash
source ~/.secrets/cmuxterm.env
APP_PATH="build/Build/Products/Release/cmux.app"
SIGN_HASH="A050CC7E193C8221BDBA204E731B046CDCCC1B30"
rm -f cmux-macos.dmg
create-dmg --codesign "$SIGN_HASH" cmux-macos.dmg "$APP_PATH"
xcrun notarytool submit cmux-macos.dmg --apple-id "$APPLE_ID" --team-id "$APPLE_TEAM_ID" --password "$APPLE_APP_SPECIFIC_PASSWORD" --wait
xcrun stapler staple cmux-macos.dmg
xcrun stapler validate cmux-macos.dmg
```
16. **Generate Sparkle appcast**
```bash
source ~/.secrets/cmuxterm.env && export SPARKLE_PRIVATE_KEY
./scripts/sparkle_generate_appcast.sh cmux-macos.dmg "$TAG" appcast.xml
```
17. **Upload to GitHub release**
If no release exists for the tag yet, create one:
```bash
gh release create "$TAG" cmux-macos.dmg appcast.xml --title "$TAG" --notes "...changelog..."
```
If it already exists:
```bash
gh release upload "$TAG" cmux-macos.dmg appcast.xml --clobber
```
18. **Cleanup and notify**
```bash
rm -rf build/ cmux-macos.dmg appcast.xml
say "Release complete"
```
## Completion
When the release is fully done (DMG uploaded, release verified), always run:
```bash
say "cmux release $TAG is live"
```
If the release fails at any point, run:
```bash
say "cmux release failed"
```
## Changelog Guidelines
**Include only end-user visible changes:**
- New features users can see or interact with
- Bug fixes users would notice (crashes, UI glitches, incorrect behavior)
- Performance improvements users would feel
- UI/UX changes
- Breaking changes or removed features
**Exclude internal/developer changes:**
- Setup scripts, build scripts, reload scripts
- CI/workflow changes
- Documentation updates (README, CONTRIBUTING, CLAUDE.md)
- Test additions or fixes
- Internal refactoring with no user-visible effect
- Dependency updates (unless they fix a user-facing bug)

View file

@ -55,6 +55,10 @@ Prepare a new release for cmux. This command updates the changelog, bumps the ve
- Verify the release appears at: https://github.com/manaflow-ai/cmux/releases
- Check that the DMG is attached to the release
12. **Notify**
- On success: `say "cmux release complete"`
- On failure: `say "cmux release failed"`
## Changelog Guidelines
**Include only end-user visible changes:**