@@ -17,10 +17,10 @@ jobs:
1717 - name : Run tests
1818 run : swift test
1919
20- - name : Build release binary
20+ - name : Build CLI release binaries
2121 run : swift build -c release
2222
23- - name : Package artifacts
23+ - name : Package CLI tarball
2424 run : |
2525 mkdir -p staging/agents
2626 cp .build/release/ClaudeMemory staging/memory
@@ -31,12 +31,166 @@ jobs:
3131 cp agents/*.md staging/agents/
3232 cd staging && tar czf ../claude-memory-macos-arm64.tar.gz *
3333
34- - name : Create release
34+ - name : Import signing certificate
35+ if : env.DEVELOPER_ID_CERT_BASE64 != ''
36+ env :
37+ DEVELOPER_ID_CERT_BASE64 : ${{ secrets.DEVELOPER_ID_CERT_BASE64 }}
38+ DEVELOPER_ID_CERT_PASSWORD : ${{ secrets.DEVELOPER_ID_CERT_PASSWORD }}
39+ run : |
40+ KEYCHAIN_PATH=$RUNNER_TEMP/signing.keychain-db
41+ KEYCHAIN_PASSWORD=$(openssl rand -base64 32)
42+
43+ # Create temporary keychain
44+ security create-keychain -p "$KEYCHAIN_PASSWORD" "$KEYCHAIN_PATH"
45+ security set-keychain-settings -lut 21600 "$KEYCHAIN_PATH"
46+ security unlock-keychain -p "$KEYCHAIN_PASSWORD" "$KEYCHAIN_PATH"
47+
48+ # Import certificate
49+ echo "$DEVELOPER_ID_CERT_BASE64" | base64 --decode > $RUNNER_TEMP/cert.p12
50+ security import $RUNNER_TEMP/cert.p12 -P "$DEVELOPER_ID_CERT_PASSWORD" \
51+ -A -t cert -f pkcs12 -k "$KEYCHAIN_PATH"
52+ security set-key-partition-list -S apple-tool:,apple: -k "$KEYCHAIN_PASSWORD" "$KEYCHAIN_PATH"
53+
54+ # Add to search list
55+ security list-keychains -d user -s "$KEYCHAIN_PATH" $(security list-keychains -d user | tr -d '"')
56+
57+ - name : Archive app with xcodebuild
58+ if : env.DEVELOPER_ID_CERT_BASE64 != ''
59+ env :
60+ DEVELOPER_ID_CERT_BASE64 : ${{ secrets.DEVELOPER_ID_CERT_BASE64 }}
61+ APPLE_TEAM_ID : ${{ secrets.APPLE_TEAM_ID }}
62+ run : |
63+ VERSION=${GITHUB_REF_NAME#v}
64+ xcodebuild archive \
65+ -project MemoryVisualizer.xcodeproj \
66+ -scheme MemoryVisualizer \
67+ -archivePath build/MemoryVisualizer.xcarchive \
68+ -configuration Release \
69+ CODE_SIGN_IDENTITY="Developer ID Application" \
70+ CODE_SIGN_STYLE=Manual \
71+ DEVELOPMENT_TEAM="$APPLE_TEAM_ID" \
72+ MARKETING_VERSION="$VERSION" \
73+ OTHER_CODE_SIGN_FLAGS="--keychain $RUNNER_TEMP/signing.keychain-db"
74+
75+ - name : Export archive to .app
76+ if : env.DEVELOPER_ID_CERT_BASE64 != ''
77+ env :
78+ DEVELOPER_ID_CERT_BASE64 : ${{ secrets.DEVELOPER_ID_CERT_BASE64 }}
79+ APPLE_TEAM_ID : ${{ secrets.APPLE_TEAM_ID }}
80+ run : |
81+ # Inject team ID into ExportOptions.plist
82+ sed -i '' "s|</dict>| <key>teamID</key><string>$APPLE_TEAM_ID</string></dict>|" ExportOptions.plist
83+ xcodebuild -exportArchive \
84+ -archivePath build/MemoryVisualizer.xcarchive \
85+ -exportPath build/export \
86+ -exportOptionsPlist ExportOptions.plist
87+
88+ - name : Bundle CLI binaries into .app
89+ if : env.DEVELOPER_ID_CERT_BASE64 != ''
90+ env :
91+ DEVELOPER_ID_CERT_BASE64 : ${{ secrets.DEVELOPER_ID_CERT_BASE64 }}
92+ APPLE_TEAM_ID : ${{ secrets.APPLE_TEAM_ID }}
93+ run : |
94+ APP="build/export/MemoryVisualizer.app"
95+ CLI_DIR="$APP/Contents/Resources/cli"
96+ AGENTS_DIR="$CLI_DIR/agents"
97+ mkdir -p "$CLI_DIR" "$AGENTS_DIR"
98+
99+ cp .build/release/ClaudeMemory "$CLI_DIR/memory"
100+ cp .build/release/ClaudeMemoryHooks "$CLI_DIR/memory-hooks"
101+ cp -R .build/release/ClaudeMemory_ClaudeMemoryLib.bundle "$CLI_DIR/"
102+ cp -R .build/release/swift-transformers_Hub.bundle "$CLI_DIR/"
103+ cp -R .build/release/SwiftLM_SwiftLM.bundle "$CLI_DIR/"
104+ cp agents/*.md "$AGENTS_DIR/"
105+
106+ # Re-codesign since contents changed
107+ codesign --deep --force --sign "Developer ID Application" \
108+ --keychain "$RUNNER_TEMP/signing.keychain-db" \
109+ --options runtime \
110+ "$APP"
111+
112+ - name : Create DMG
113+ if : env.DEVELOPER_ID_CERT_BASE64 != ''
114+ env :
115+ DEVELOPER_ID_CERT_BASE64 : ${{ secrets.DEVELOPER_ID_CERT_BASE64 }}
116+ run : |
117+ brew install create-dmg
118+ VERSION=${GITHUB_REF_NAME#v}
119+ create-dmg \
120+ --volname "MemoryVisualizer $VERSION" \
121+ --window-pos 200 120 \
122+ --window-size 600 400 \
123+ --icon-size 100 \
124+ --icon "MemoryVisualizer.app" 150 190 \
125+ --app-drop-link 450 190 \
126+ --no-internet-enable \
127+ "MemoryVisualizer-${VERSION}.dmg" \
128+ "build/export/MemoryVisualizer.app"
129+
130+ - name : Notarize DMG
131+ if : env.DEVELOPER_ID_CERT_BASE64 != ''
132+ env :
133+ DEVELOPER_ID_CERT_BASE64 : ${{ secrets.DEVELOPER_ID_CERT_BASE64 }}
134+ APPLE_ID : ${{ secrets.APPLE_ID }}
135+ APPLE_APP_PASSWORD : ${{ secrets.APPLE_APP_PASSWORD }}
136+ APPLE_TEAM_ID : ${{ secrets.APPLE_TEAM_ID }}
137+ run : |
138+ VERSION=${GITHUB_REF_NAME#v}
139+ DMG="MemoryVisualizer-${VERSION}.dmg"
140+
141+ xcrun notarytool submit "$DMG" \
142+ --apple-id "$APPLE_ID" \
143+ --password "$APPLE_APP_PASSWORD" \
144+ --team-id "$APPLE_TEAM_ID" \
145+ --wait
146+
147+ xcrun stapler staple "$DMG"
148+
149+ - name : Generate Sparkle appcast
150+ if : env.SPARKLE_PRIVATE_KEY != ''
151+ env :
152+ SPARKLE_PRIVATE_KEY : ${{ secrets.SPARKLE_PRIVATE_KEY }}
153+ run : |
154+ VERSION=${GITHUB_REF_NAME#v}
155+ DMG="MemoryVisualizer-${VERSION}.dmg"
156+
157+ # Download Sparkle tools
158+ SPARKLE_VERSION="2.7.5"
159+ curl -sL "https://github.com/sparkle-project/Sparkle/releases/download/${SPARKLE_VERSION}/Sparkle-${SPARKLE_VERSION}.tar.xz" | tar xJ -C /tmp/sparkle-tools bin/
160+
161+ # Move DMG to a staging dir for generate_appcast
162+ mkdir -p appcast_staging
163+ cp "$DMG" appcast_staging/
164+
165+ # Generate appcast from the DMG
166+ echo "$SPARKLE_PRIVATE_KEY" | /tmp/sparkle-tools/bin/generate_appcast \
167+ --ed-key-file - \
168+ --download-url-prefix "https://github.com/jsflax/ClaudeMemory/releases/download/${GITHUB_REF_NAME}/" \
169+ appcast_staging
170+
171+ # Use generated appcast
172+ cp appcast_staging/appcast.xml appcast.xml
173+
174+ - name : Create GitHub Release
35175 uses : softprops/action-gh-release@v2
36176 with :
37- files : claude-memory-macos-arm64.tar.gz
177+ files : |
178+ claude-memory-macos-arm64.tar.gz
179+ MemoryVisualizer-*.dmg
38180 generate_release_notes : true
39181
182+ - name : Commit appcast.xml to main
183+ if : env.SPARKLE_PRIVATE_KEY != ''
184+ env :
185+ SPARKLE_PRIVATE_KEY : ${{ secrets.SPARKLE_PRIVATE_KEY }}
186+ run : |
187+ git config user.name "github-actions[bot]"
188+ git config user.email "github-actions[bot]@users.noreply.github.com"
189+ git fetch origin main
190+ git checkout main
191+ git add appcast.xml
192+ git diff --cached --quiet || (git commit -m "Update appcast.xml for ${GITHUB_REF_NAME}" && git push origin main)
193+
40194 - name : Update Homebrew formula
41195 if : env.TAP_TOKEN != ''
42196 env :
@@ -56,3 +210,48 @@ jobs:
56210 git config user.email "github-actions[bot]@users.noreply.github.com"
57211 git add Formula/claude-memory.rb
58212 git diff --cached --quiet || (git commit -m "Update claude-memory to ${VERSION}" && git push)
213+
214+ - name : Notify Slack
215+ if : always() && secrets.SLACK_WEBHOOK_URL != ''
216+ env :
217+ SLACK_WEBHOOK_URL : ${{ secrets.SLACK_WEBHOOK_URL }}
218+ run : |
219+ TAG="${GITHUB_REF_NAME}"
220+ REPO="${{ github.repository }}"
221+ URL="https://github.com/${REPO}/releases/tag/${TAG}"
222+ STATUS="${{ job.status }}"
223+
224+ if [ "$STATUS" = "success" ]; then
225+ EMOJI="✅"
226+ TEXT="Release *${TAG}* published successfully"
227+ else
228+ EMOJI="❌"
229+ TEXT="Release *${TAG}* failed"
230+ fi
231+
232+ curl -s -X POST "$SLACK_WEBHOOK_URL" \
233+ -H 'Content-Type: application/json' \
234+ -d "{
235+ \"blocks\": [
236+ {
237+ \"type\": \"header\",
238+ \"text\": {\"type\": \"plain_text\", \"text\": \"${EMOJI} ClaudeMemory ${TAG}\"}
239+ },
240+ {
241+ \"type\": \"section\",
242+ \"text\": {\"type\": \"mrkdwn\", \"text\": \"${TEXT}\"},
243+ \"accessory\": {
244+ \"type\": \"button\",
245+ \"text\": {\"type\": \"plain_text\", \"text\": \"View Release\"},
246+ \"url\": \"${URL}\"
247+ }
248+ }
249+ ]
250+ }"
251+
252+ - name : Cleanup keychain
253+ if : always()
254+ run : |
255+ if [ -f "$RUNNER_TEMP/signing.keychain-db" ]; then
256+ security delete-keychain "$RUNNER_TEMP/signing.keychain-db" 2>/dev/null || true
257+ fi
0 commit comments