Skip to content

Commit d1ae4e6

Browse files
committed
Add CI/CD pipeline, website, Sparkle auto-updates, and app icon
- GitHub Actions: CI (build + test), release (sign, notarize, DMG, Sparkle appcast, GitHub Release, Homebrew cask update), GhosttyKit pre-built binary workflow - Landing page at macuake.com with GSAP animations and beta badge - Sparkle framework integration for auto-updates via GitHub Releases - App icon (tilde with neon glow), README, download-ghosttykit script - Build script improvements: bundle filtering, Sparkle embedding
1 parent 9fb6d78 commit d1ae4e6

18 files changed

Lines changed: 3024 additions & 491 deletions

File tree

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
name: Build GhosttyKit
2+
3+
on:
4+
push:
5+
branches: [main]
6+
paths:
7+
- '.gitmodules'
8+
- 'vendor/ghostty'
9+
- 'scripts/build-ghostty.sh'
10+
workflow_dispatch:
11+
12+
permissions:
13+
contents: write
14+
15+
jobs:
16+
build-ghosttykit:
17+
runs-on: macos-15
18+
steps:
19+
- name: Checkout
20+
uses: actions/checkout@v4
21+
with:
22+
submodules: recursive
23+
24+
- name: Get Ghostty SHA
25+
id: ghostty-sha
26+
run: |
27+
SHA=$(git -C vendor/ghostty rev-parse HEAD)
28+
echo "sha=$SHA" >> "$GITHUB_OUTPUT"
29+
echo "Ghostty SHA: $SHA"
30+
31+
- name: Check if release already exists
32+
id: check-release
33+
env:
34+
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
35+
run: |
36+
TAG="ghosttykit-${{ steps.ghostty-sha.outputs.sha }}"
37+
if gh release view "$TAG" >/dev/null 2>&1; then
38+
echo "exists=true" >> "$GITHUB_OUTPUT"
39+
echo "Release $TAG already exists, skipping build"
40+
else
41+
echo "exists=false" >> "$GITHUB_OUTPUT"
42+
echo "Release $TAG not found, will build"
43+
fi
44+
45+
- name: Install zig
46+
if: steps.check-release.outputs.exists == 'false'
47+
run: brew install zig
48+
49+
- name: Build GhosttyKit.xcframework
50+
if: steps.check-release.outputs.exists == 'false'
51+
run: ./scripts/build-ghostty.sh
52+
53+
- name: Package xcframework
54+
if: steps.check-release.outputs.exists == 'false'
55+
run: |
56+
tar czf GhosttyKit.xcframework.tar.gz -C vendor/ghostty/macos GhosttyKit.xcframework
57+
58+
- name: Upload as release
59+
if: steps.check-release.outputs.exists == 'false'
60+
env:
61+
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
62+
run: |
63+
TAG="ghosttykit-${{ steps.ghostty-sha.outputs.sha }}"
64+
gh release create "$TAG" \
65+
--title "GhosttyKit (${{ steps.ghostty-sha.outputs.sha }})" \
66+
--notes "Pre-built GhosttyKit.xcframework for Ghostty commit ${{ steps.ghostty-sha.outputs.sha }}" \
67+
GhosttyKit.xcframework.tar.gz
68+
echo "Published release $TAG"

.github/workflows/ci.yml

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -14,11 +14,10 @@ jobs:
1414
with:
1515
submodules: recursive
1616

17-
- name: Install dependencies
18-
run: brew install zig
19-
20-
- name: Build GhosttyKit
21-
run: ./scripts/build-ghostty.sh
17+
- name: Download pre-built GhosttyKit
18+
env:
19+
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
20+
run: ./scripts/download-ghosttykit.sh
2221

2322
- name: Build
2423
run: swift build

.github/workflows/release.yml

Lines changed: 96 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -20,21 +20,28 @@ jobs:
2020
with:
2121
submodules: recursive
2222

23-
- name: Install dependencies
24-
run: brew install zig
25-
2623
- name: Extract version from tag
2724
id: version
2825
run: |
2926
VERSION="${GITHUB_REF_NAME#v}"
3027
echo "version=$VERSION" >> "$GITHUB_OUTPUT"
3128
echo "Building version $VERSION"
3229
33-
- name: Build GhosttyKit
34-
run: ./scripts/build-ghostty.sh
30+
- name: Check available secrets
31+
id: secrets
32+
run: |
33+
echo "has_signing=${{ secrets.CERTIFICATE_P12 != '' }}" >> "$GITHUB_OUTPUT"
34+
echo "has_notarize=${{ secrets.APPLE_ID != '' }}" >> "$GITHUB_OUTPUT"
35+
echo "has_sparkle=${{ secrets.SPARKLE_PRIVATE_KEY != '' }}" >> "$GITHUB_OUTPUT"
36+
echo "has_tap=${{ secrets.HOMEBREW_TAP_TOKEN != '' }}" >> "$GITHUB_OUTPUT"
3537
36-
- name: Build universal release binary
37-
run: swift build -c release --arch arm64 --arch x86_64
38+
- name: Download pre-built GhosttyKit
39+
env:
40+
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
41+
run: ./scripts/download-ghosttykit.sh
42+
43+
- name: Build release binary
44+
run: swift build -c release
3845

3946
- name: Prepare app bundle
4047
run: |
@@ -51,22 +58,36 @@ jobs:
5158
/usr/libexec/PlistBuddy -c "Set :CFBundleVersion ${{ github.run_number }}" \
5259
MaQuake/Resources/Info.plist
5360
54-
# Copy binary (universal path)
55-
cp .build/apple/Products/Release/${{ env.APP_NAME }} "$APP_BUNDLE/Contents/MacOS/${{ env.APP_NAME }}"
61+
# Copy binary
62+
cp .build/release/${{ env.APP_NAME }} "$APP_BUNDLE/Contents/MacOS/${{ env.APP_NAME }}" 2>/dev/null \
63+
|| cp .build/arm64-apple-macosx/release/${{ env.APP_NAME }} "$APP_BUNDLE/Contents/MacOS/${{ env.APP_NAME }}"
5664
57-
# Copy Info.plist and resources
65+
# Copy Info.plist and icon
5866
cp MaQuake/Resources/Info.plist "$APP_BUNDLE/Contents/Info.plist"
5967
cp MaQuake/Resources/maquake.icns "$APP_BUNDLE/Contents/Resources/" 2>/dev/null || true
6068
69+
# Copy SPM resource bundles that have valid Info.plist (codesign-compatible)
70+
for dir in .build/release .build/arm64-apple-macosx/release .build/apple/Products/Release; do
71+
for bundle in "$dir"/*.bundle; do
72+
[ -d "$bundle" ] || continue
73+
[ -f "$bundle/Info.plist" ] || [ -f "$bundle/Contents/Info.plist" ] || continue
74+
name="$(basename "$bundle")"
75+
cp -R "$bundle" "$APP_BUNDLE/Contents/Resources/$name"
76+
cp -R "$bundle" "$APP_BUNDLE/Contents/MacOS/$name"
77+
done
78+
done
79+
6180
# Embed Sparkle.framework
62-
SPARKLE_SRC=".build/artifacts/sparkle/Sparkle/Sparkle.xcframework/macos-arm64_x86_64/Sparkle.framework"
81+
SPARKLE_SRC=$(find .build -path "*/macos-arm64*/Sparkle.framework" -type d | head -1)
82+
[ -z "$SPARKLE_SRC" ] && SPARKLE_SRC=".build/artifacts/sparkle/Sparkle/Sparkle.xcframework/macos-arm64_x86_64/Sparkle.framework"
6383
cp -R "$SPARKLE_SRC" "$APP_BUNDLE/Contents/Frameworks/Sparkle.framework"
6484
6585
# Add rpath for embedded frameworks
6686
install_name_tool -add_rpath "@executable_path/../Frameworks" \
6787
"$APP_BUNDLE/Contents/MacOS/${{ env.APP_NAME }}" 2>/dev/null || true
6888
6989
- name: Import signing certificate
90+
if: steps.secrets.outputs.has_signing == 'true'
7091
env:
7192
CERTIFICATE_P12: ${{ secrets.CERTIFICATE_P12 }}
7293
CERTIFICATE_PASSWORD: ${{ secrets.CERTIFICATE_PASSWORD }}
@@ -90,12 +111,19 @@ jobs:
90111
security list-keychains -d user -s "$KEYCHAIN_PATH" login.keychain
91112
92113
- name: Sign app bundle (inside-out)
114+
if: steps.secrets.outputs.has_signing == 'true'
93115
run: |
94116
APP_BUNDLE="build/${{ env.APP_NAME }}.app"
95117
IDENTITY="${{ secrets.SIGNING_IDENTITY }}"
96118
ENTITLEMENTS="MaQuake/Resources/MaQuake.entitlements"
97119
98-
# Sign Sparkle XPC services and helpers first (inside-out)
120+
# Sign resource bundles in MacOS/ first
121+
for bundle in "$APP_BUNDLE"/Contents/MacOS/*.bundle; do
122+
[ -d "$bundle" ] || continue
123+
codesign --force --sign "$IDENTITY" "$bundle"
124+
done
125+
126+
# Sign Sparkle XPC services and helpers (inside-out)
99127
codesign --force --sign "$IDENTITY" --options runtime \
100128
"$APP_BUNDLE/Contents/Frameworks/Sparkle.framework/Versions/B/XPCServices/Installer.xpc"
101129
codesign --force --sign "$IDENTITY" --options runtime \
@@ -115,16 +143,26 @@ jobs:
115143
116144
codesign --verify --deep --strict "$APP_BUNDLE"
117145
146+
- name: Ad-hoc sign (when no certificate)
147+
if: steps.secrets.outputs.has_signing != 'true'
148+
run: |
149+
codesign --force --deep --sign - "build/${{ env.APP_NAME }}.app"
150+
118151
- name: Create DMG
119152
run: |
120153
hdiutil create -volname "${{ env.APP_NAME }}" \
121154
-srcfolder "build/${{ env.APP_NAME }}.app" \
122155
-ov -format UDZO \
123156
"build/${{ env.APP_NAME }}.dmg"
157+
158+
- name: Sign DMG
159+
if: steps.secrets.outputs.has_signing == 'true'
160+
run: |
124161
codesign --force --sign "${{ secrets.SIGNING_IDENTITY }}" \
125162
"build/${{ env.APP_NAME }}.dmg"
126163
127164
- name: Notarize DMG
165+
if: steps.secrets.outputs.has_notarize == 'true'
128166
env:
129167
APPLE_ID: ${{ secrets.APPLE_ID }}
130168
APPLE_PASSWORD: ${{ secrets.APPLE_APP_PASSWORD }}
@@ -138,6 +176,7 @@ jobs:
138176
xcrun stapler staple "build/${{ env.APP_NAME }}.dmg"
139177
140178
- name: Sign update with Sparkle
179+
if: steps.secrets.outputs.has_sparkle == 'true'
141180
env:
142181
SPARKLE_KEY: ${{ secrets.SPARKLE_PRIVATE_KEY }}
143182
run: |
@@ -147,6 +186,7 @@ jobs:
147186
rm "$RUNNER_TEMP/sparkle_key"
148187
149188
- name: Generate appcast
189+
if: steps.secrets.outputs.has_sparkle == 'true'
150190
env:
151191
SPARKLE_KEY: ${{ secrets.SPARKLE_PRIVATE_KEY }}
152192
run: |
@@ -162,6 +202,8 @@ jobs:
162202
/tmp/appcast_dir
163203
164204
rm "$RUNNER_TEMP/sparkle_key"
205+
# Fix title to lowercase branding
206+
sed -i '' 's|<title>Macuake</title>|<title>macuake</title>|' /tmp/appcast_dir/appcast.xml
165207
cp /tmp/appcast_dir/appcast.xml build/appcast.xml
166208
167209
- name: Create GitHub Release
@@ -170,6 +212,48 @@ jobs:
170212
files: |
171213
build/${{ env.APP_NAME }}.dmg
172214
build/appcast.xml
215+
fail_on_unmatched_files: false
173216
generate_release_notes: true
174217
draft: false
175218
prerelease: ${{ contains(github.ref_name, 'beta') || contains(github.ref_name, 'rc') }}
219+
220+
- name: Update Homebrew cask
221+
if: steps.secrets.outputs.has_tap == 'true' && !contains(github.ref_name, 'beta') && !contains(github.ref_name, 'rc')
222+
env:
223+
GH_TOKEN: ${{ secrets.HOMEBREW_TAP_TOKEN }}
224+
run: |
225+
VERSION="${{ steps.version.outputs.version }}"
226+
SHA=$(shasum -a 256 "build/${{ env.APP_NAME }}.dmg" | awk '{print $1}')
227+
228+
git clone "https://x-access-token:${GH_TOKEN}@github.com/menemy/homebrew-macuake.git" /tmp/tap
229+
cd /tmp/tap
230+
231+
python3 -c "
232+
v, s = '$VERSION', '$SHA'
233+
print(f'''cask \"macuake\" do
234+
version \"{v}\"
235+
sha256 \"{s}\"
236+
237+
url \"https://github.com/menemy/macuake/releases/download/v#{{version}}/Macuake.dmg\"
238+
name \"Macuake\"
239+
desc \"Drop-down Quake-style terminal for macOS powered by Ghostty\"
240+
homepage \"https://macuake.com\"
241+
242+
depends_on macos: \">= :sonoma\"
243+
244+
app \"Macuake.app\"
245+
246+
zap trash: [
247+
\"~/Library/Preferences/com.macuake.terminal.plist\",
248+
\"~/Library/Application Support/macuake\",
249+
]
250+
end''')
251+
" | sed 's/^ //' > Casks/macuake.rb
252+
253+
git config user.name "github-actions[bot]"
254+
git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
255+
git add Casks/macuake.rb
256+
if ! git diff --cached --quiet; then
257+
git commit -m "Update macuake to $VERSION"
258+
git push
259+
fi

MaQuake/Resources/Info.plist

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,9 +13,9 @@
1313
<key>LSUIElement</key>
1414
<true/>
1515
<key>CFBundleIconFile</key>
16-
<string></string>
16+
<string>maquake</string>
1717
<key>SUFeedURL</key>
18-
<string>https://macuake.com/appcast.xml</string>
18+
<string>https://github.com/menemy/macuake/releases/latest/download/appcast.xml</string>
1919
<key>SUPublicEDKey</key>
2020
<string>DO/Ypd5E8hupynUNtIqqxZCs0CNEfeWoKX8ielcAfTM=</string>
2121
</dict>

MaQuake/Resources/maquake.icns

1.16 MB
Binary file not shown.

0 commit comments

Comments
 (0)