native macOS codings agent orchestrator
6
fork

Configure Feed

Select the types of activity you want to include in your feed.

Remove unused CI release workflows

Release is now handled locally via the /release skill (build, sign,
notarize, publish all happen on the dev machine), so these GitHub
Actions workflows are no longer needed.

onevcat 7f79078f 5eb9d4a2

-604
-332
.github/workflows/release-tip.yml
··· 1 - name: Release Tip 2 - 3 - on: 4 - workflow_dispatch: {} 5 - 6 - concurrency: 7 - group: ${{ github.workflow }} 8 - cancel-in-progress: false 9 - 10 - jobs: 11 - check: 12 - if: github.event_name == 'workflow_dispatch' || github.ref_name == 'main' 13 - runs-on: ubuntu-latest 14 - outputs: 15 - should_skip: ${{ steps.check.outputs.should_skip }} 16 - steps: 17 - - name: Check if tip release already exists for this commit 18 - id: check 19 - env: 20 - GH_TOKEN: ${{ github.token }} 21 - run: | 22 - TIP_SHA=$(gh api repos/${{ github.repository }}/git/ref/tags/tip --jq '.object.sha' 2>/dev/null || echo "") 23 - ASSET_COUNT=$(gh release view tip -R "${{ github.repository }}" --json assets --jq '.assets | length' 2>/dev/null || echo "0") 24 - if [ "$TIP_SHA" = "${{ github.sha }}" ] && [ "$ASSET_COUNT" -gt 0 ]; then 25 - echo "Tip already points at ${{ github.sha }} with $ASSET_COUNT assets, skipping" 26 - echo "should_skip=true" >> "$GITHUB_OUTPUT" 27 - else 28 - echo "should_skip=false" >> "$GITHUB_OUTPUT" 29 - fi 30 - 31 - build: 32 - runs-on: macos-26 33 - needs: [check] 34 - if: needs.check.outputs.should_skip != 'true' 35 - permissions: 36 - contents: read 37 - env: 38 - MISE_HTTP_TIMEOUT: 120 39 - MISE_GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 40 - DEVELOPER_ID_CERT_P12: ${{ secrets.DEVELOPER_ID_CERT_P12 }} 41 - DEVELOPER_ID_CERT_PASSWORD: ${{ secrets.DEVELOPER_ID_CERT_PASSWORD }} 42 - DEVELOPER_ID_IDENTITY: ${{ secrets.DEVELOPER_ID_IDENTITY }} 43 - KEYCHAIN_PASSWORD: ${{ secrets.KEYCHAIN_PASSWORD }} 44 - APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }} 45 - APPLE_NOTARIZATION_ISSUER: ${{ secrets.APPLE_NOTARIZATION_ISSUER }} 46 - APPLE_NOTARIZATION_KEY_ID: ${{ secrets.APPLE_NOTARIZATION_KEY_ID }} 47 - APPLE_NOTARIZATION_KEY: ${{ secrets.APPLE_NOTARIZATION_KEY }} 48 - SPARKLE_PRIVATE_KEY: ${{ secrets.SPARKLE_PRIVATE_KEY }} 49 - steps: 50 - - uses: actions/checkout@v6 51 - with: 52 - submodules: recursive 53 - - uses: ./.github/actions/setup-macos 54 - - name: Override build number for tip 55 - run: | 56 - BASE=$(awk -F' = ' '/CURRENT_PROJECT_VERSION = [0-9]+/{gsub(/;/,""); print $2; exit}' supacode.xcodeproj/project.pbxproj) 57 - OFFSET=${{ github.run_number }} 58 - if [ "$OFFSET" -gt 999 ]; then 59 - echo "::error::Tip run_number ($OFFSET) exceeds 999. Bump CURRENT_PROJECT_VERSION before the next tip release." 60 - exit 1 61 - fi 62 - BUILD_NUMBER=$((BASE * 1000 + OFFSET)) 63 - sed -i '' "s/CURRENT_PROJECT_VERSION = [0-9]*/CURRENT_PROJECT_VERSION = $BUILD_NUMBER/g" supacode.xcodeproj/project.pbxproj 64 - echo "BUILD_NUMBER=$BUILD_NUMBER" >> "$GITHUB_ENV" 65 - - name: Setup keychain 66 - run: | 67 - echo "$DEVELOPER_ID_CERT_P12" | base64 --decode > build-cert.p12 68 - security create-keychain -p "$KEYCHAIN_PASSWORD" build.keychain 69 - security set-keychain-settings -t 3600 -u build.keychain 70 - security unlock-keychain -p "$KEYCHAIN_PASSWORD" build.keychain 71 - security import build-cert.p12 -k build.keychain -P "$DEVELOPER_ID_CERT_PASSWORD" -T /usr/bin/codesign -T /usr/bin/security -T /usr/bin/xcodebuild > /dev/null 72 - security set-key-partition-list -S apple-tool:,apple:,codesign: -s -k "$KEYCHAIN_PASSWORD" build.keychain > /dev/null 73 - security list-keychains -d user -s build.keychain $(security list-keychains -d user | tr -d '"') 74 - security default-keychain -s build.keychain 75 - DEVELOPER_ID_IDENTITY_SHA=$(security find-identity -v -p codesigning build.keychain | grep "Developer ID Application" | head -1 | awk '{print $2}') 76 - if [ -z "$DEVELOPER_ID_IDENTITY_SHA" ]; then 77 - echo "::error::Developer ID Application identity not found in keychain" 78 - exit 1 79 - fi 80 - echo "DEVELOPER_ID_IDENTITY_SHA=$DEVELOPER_ID_IDENTITY_SHA" >> "$GITHUB_ENV" 81 - - name: Inject secrets 82 - env: 83 - SENTRY_DSN: ${{ secrets.SENTRY_DSN }} 84 - POSTHOG_API_KEY: ${{ secrets.POSTHOG_API_KEY }} 85 - POSTHOG_HOST: ${{ secrets.POSTHOG_HOST }} 86 - run: | 87 - set -euo pipefail 88 - : "${SENTRY_DSN:?secret SENTRY_DSN is not set}" 89 - : "${POSTHOG_API_KEY:?secret POSTHOG_API_KEY is not set}" 90 - : "${POSTHOG_HOST:?secret POSTHOG_HOST is not set}" 91 - sed -i '' "s|__SENTRY_DSN__|${SENTRY_DSN}|g" supacode/App/supacodeApp.swift 92 - sed -i '' "s|__POSTHOG_API_KEY__|${POSTHOG_API_KEY}|g" supacode/App/supacodeApp.swift 93 - sed -i '' "s|__POSTHOG_HOST__|${POSTHOG_HOST}|g" supacode/App/supacodeApp.swift 94 - - name: Archive Xcode project 95 - run: make archive 96 - - name: Upload dSYMs 97 - uses: actions/upload-artifact@v6 98 - with: 99 - name: dsyms 100 - path: build/supacode.xcarchive/dSYMs 101 - - run: | 102 - cat > build/ExportOptions.plist <<EOF 103 - <?xml version="1.0" encoding="UTF-8"?> 104 - <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> 105 - <plist version="1.0"> 106 - <dict> 107 - <key>method</key> 108 - <string>developer-id</string> 109 - <key>signingStyle</key> 110 - <string>manual</string> 111 - <key>signingCertificate</key> 112 - <string>$DEVELOPER_ID_IDENTITY</string> 113 - <key>teamID</key> 114 - <string>$APPLE_TEAM_ID</string> 115 - </dict> 116 - </plist> 117 - EOF 118 - make export-archive 119 - - name: Re-sign frameworks 120 - run: | 121 - set -ex 122 - APP_PATH="$(find build/export -name "supacode.app" -maxdepth 3 -print -quit)" 123 - SPARKLE="$APP_PATH/Contents/Frameworks/Sparkle.framework/Versions/B" 124 - 125 - codesign -f -s "$DEVELOPER_ID_IDENTITY_SHA" -o runtime --timestamp -v "$SPARKLE/XPCServices/Installer.xpc" 126 - codesign -f -s "$DEVELOPER_ID_IDENTITY_SHA" -o runtime --timestamp --preserve-metadata=entitlements -v "$SPARKLE/XPCServices/Downloader.xpc" 127 - codesign -f -s "$DEVELOPER_ID_IDENTITY_SHA" -o runtime --timestamp -v "$SPARKLE/Updater.app" 128 - codesign -f -s "$DEVELOPER_ID_IDENTITY_SHA" -o runtime --timestamp -v "$SPARKLE/Autoupdate" 129 - codesign -f -s "$DEVELOPER_ID_IDENTITY_SHA" -o runtime --timestamp -v "$SPARKLE/Sparkle" 130 - codesign -f -s "$DEVELOPER_ID_IDENTITY_SHA" -o runtime --timestamp -v "$APP_PATH/Contents/Frameworks/Sparkle.framework" 131 - SENTRY_FRAMEWORK="$APP_PATH/Contents/Frameworks/Sentry.framework" 132 - codesign -f -s "$DEVELOPER_ID_IDENTITY_SHA" -o runtime --timestamp -v "$SENTRY_FRAMEWORK/Versions/A/Sentry" 133 - codesign -f -s "$DEVELOPER_ID_IDENTITY_SHA" -o runtime --timestamp -v "$SENTRY_FRAMEWORK" 134 - codesign -f -s "$DEVELOPER_ID_IDENTITY_SHA" -o runtime --timestamp --preserve-metadata=entitlements,requirements,flags -v "$APP_PATH" 135 - 136 - codesign -d --entitlements - "$APP_PATH/Contents/MacOS/supacode" 2>&1 | tee /tmp/supacode-entitlements.txt 137 - grep -q "com.apple.security.device.audio-input" /tmp/supacode-entitlements.txt 138 - 139 - codesign -vvv --deep --strict "$APP_PATH" 140 - - name: Store notarization credentials 141 - run: | 142 - echo "$APPLE_NOTARIZATION_KEY" > notarization_key.p8 143 - xcrun notarytool store-credentials "notarytool-profile" \ 144 - --key notarization_key.p8 \ 145 - --key-id "$APPLE_NOTARIZATION_KEY_ID" \ 146 - --issuer "$APPLE_NOTARIZATION_ISSUER" 147 - rm notarization_key.p8 148 - - name: Build DMG 149 - run: | 150 - APP_PATH="$(find build/export -name "supacode.app" -maxdepth 3 -print -quit)" 151 - 152 - mise exec -- create-dmg "$APP_PATH" build/ \ 153 - --overwrite \ 154 - --dmg-title="Supacode" \ 155 - --identity="$DEVELOPER_ID_IDENTITY_SHA" 156 - 157 - DMG_OUTPUT=$(find build -name "*.dmg" -maxdepth 1 | head -1) 158 - if [ "$DMG_OUTPUT" != "build/supacode.dmg" ]; then 159 - mv "$DMG_OUTPUT" build/supacode.dmg 160 - fi 161 - - name: Notarize and staple 162 - run: | 163 - APP_PATH="$(find build/export -name "supacode.app" -maxdepth 3 -print -quit)" 164 - 165 - for attempt in 1 2 3; do 166 - echo "Notarization attempt $attempt..." 167 - xcrun notarytool submit build/supacode.dmg --keychain-profile "notarytool-profile" --wait && break 168 - echo "Attempt $attempt failed, retrying in 30s..." 169 - sleep 30 170 - done 171 - 172 - xcrun stapler staple build/supacode.dmg 173 - xcrun stapler staple "$APP_PATH" 174 - - name: Create app zip for Sparkle 175 - run: | 176 - APP_PATH="$(find build/export -name "supacode.app" -maxdepth 3 -print -quit)" 177 - ditto -c -k --sequesterRsrc --keepParent "$APP_PATH" build/supacode.app.zip 178 - - name: Generate tip appcast 179 - env: 180 - GH_TOKEN: ${{ github.token }} 181 - run: | 182 - set -euo pipefail 183 - MAX_DELTAS=10 184 - STAGING=$(mktemp -d) 185 - cp build/supacode.app.zip "$STAGING/" 186 - 187 - # Download previous tip builds stored as history assets 188 - ASSETS=$(gh release view tip -R "${{ github.repository }}" --json assets --jq '.assets[].name' 2>/dev/null || true) 189 - for asset in $ASSETS; do 190 - case "$asset" in 191 - supacode-history-*.app.zip) 192 - gh release download tip -p "$asset" -O "$STAGING/$asset" -R "${{ github.repository }}" 2>/dev/null || true 193 - ;; 194 - esac 195 - done 196 - 197 - printf "%s" "$SPARKLE_PRIVATE_KEY" | tr -d '\r\n\t ' | ./bins/generate_appcast \ 198 - --download-url-prefix "https://supacode.sh/download/tip/" \ 199 - --maximum-versions $((MAX_DELTAS + 1)) \ 200 - --maximum-deltas $MAX_DELTAS \ 201 - --delta-compression lzma \ 202 - --ed-key-file - "$STAGING" 203 - # Inject sparkle:channel tag into the tip items 204 - sed -i '' 's|<enclosure |<sparkle:channel>tip</sparkle:channel>\n <enclosure |' "$STAGING/appcast.xml" 205 - cp "$STAGING/appcast.xml" build/appcast.xml 206 - find "$STAGING" -name "*.delta" -exec cp {} build/ \; 2>/dev/null || true 207 - - uses: actions/upload-artifact@v6 208 - with: 209 - name: build-artifacts 210 - path: | 211 - build/supacode.dmg 212 - build/supacode.app.zip 213 - build/appcast.xml 214 - build/*.delta 215 - 216 - tag: 217 - runs-on: ubuntu-latest 218 - needs: [check, build] 219 - if: needs.check.outputs.should_skip != 'true' 220 - permissions: 221 - contents: write 222 - steps: 223 - - uses: actions/checkout@v6 224 - with: 225 - token: ${{ secrets.GH_RELEASE_TOKEN }} 226 - - name: Force-move tip tag 227 - run: | 228 - git config user.name "github-actions[bot]" 229 - git config user.email "41898282+github-actions[bot]@users.noreply.github.com" 230 - git tag -fa tip -m "Latest Continuous Release" ${{ github.sha }} 231 - git push --force origin tip 232 - 233 - sentry-dsym: 234 - runs-on: ubuntu-latest 235 - needs: [check, build] 236 - if: needs.check.outputs.should_skip != 'true' 237 - steps: 238 - - name: Install sentry-cli 239 - run: curl -sL https://sentry.io/get-cli/ | sh 240 - - uses: actions/download-artifact@v7 241 - with: 242 - name: dsyms 243 - path: dsyms 244 - - name: Upload dSYMs to Sentry 245 - env: 246 - SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }} 247 - SENTRY_ORG: supabit 248 - SENTRY_PROJECT: supacode 249 - run: sentry-cli debug-files upload --include-sources dsyms 250 - 251 - publish: 252 - runs-on: ubuntu-latest 253 - needs: [check, build, tag] 254 - if: needs.check.outputs.should_skip != 'true' 255 - permissions: 256 - contents: write 257 - steps: 258 - - uses: actions/download-artifact@v7 259 - with: 260 - name: build-artifacts 261 - path: build 262 - - name: Update tip release 263 - env: 264 - GH_TOKEN: ${{ github.token }} 265 - run: | 266 - gh release create tip --prerelease --title 'Supacode Tip ("Nightly")' --target "${{ github.sha }}" --notes "" -R "${{ github.repository }}" 2>/dev/null || 267 - gh release edit tip --prerelease --title 'Supacode Tip ("Nightly")' --target "${{ github.sha }}" -R "${{ github.repository }}" 268 - 269 - # Delete stale delta files from previous tip 270 - STALE_DELTAS=$(gh release view tip -R "${{ github.repository }}" --json assets --jq '.assets[].name | select(endswith(".delta"))' 2>/dev/null || true) 271 - for d in $STALE_DELTAS; do 272 - gh release delete-asset tip "$d" -R "${{ github.repository }}" -y 2>/dev/null || true 273 - done 274 - 275 - DELTA_FILES=$(find build -name "*.delta" -type f 2>/dev/null | tr '\n' ' ' || true) 276 - gh release upload tip build/supacode.dmg build/supacode.app.zip build/appcast.xml $DELTA_FILES --clobber -R "${{ github.repository }}" 277 - 278 - # Store current build as history asset for future delta generation 279 - MAX_DELTAS=10 280 - cp build/supacode.app.zip "build/supacode-history-${{ github.run_number }}.app.zip" 281 - gh release upload tip "build/supacode-history-${{ github.run_number }}.app.zip" --clobber -R "${{ github.repository }}" 282 - 283 - # Prune oldest history assets beyond MAX_DELTAS 284 - HISTORY=$(gh release view tip -R "${{ github.repository }}" --json assets --jq '[.assets[].name | select(startswith("supacode-history-"))] | sort | reverse' 2>/dev/null || echo "[]") 285 - PRUNE=$(echo "$HISTORY" | jq -r ".[$MAX_DELTAS:][]" 2>/dev/null || true) 286 - for old in $PRUNE; do 287 - gh release delete-asset tip "$old" -R "${{ github.repository }}" -y 2>/dev/null || true 288 - done 289 - - name: Merge tip item into production appcast 290 - env: 291 - GH_TOKEN: ${{ github.token }} 292 - run: | 293 - LATEST_TAG=$(gh release list --exclude-drafts --exclude-pre-releases -R "${{ github.repository }}" --json tagName --jq '.[0].tagName' 2>/dev/null || echo "") 294 - if [ -z "$LATEST_TAG" ]; then 295 - echo "No stable release found, skipping appcast merge" 296 - exit 0 297 - fi 298 - gh release download "$LATEST_TAG" -p "appcast.xml" -O stable_appcast.xml -R "${{ github.repository }}" 2>/dev/null || exit 0 299 - 300 - python3 - stable_appcast.xml build/appcast.xml merged_appcast.xml <<'PYEOF' 301 - import sys 302 - import xml.etree.ElementTree as ET 303 - 304 - ET.register_namespace('sparkle', 'http://www.andymatuschak.org/xml-namespaces/sparkle') 305 - ET.register_namespace('dc', 'http://purl.org/dc/elements/1.1/') 306 - 307 - stable_path, tip_path, out_path = sys.argv[1], sys.argv[2], sys.argv[3] 308 - 309 - stable_tree = ET.parse(stable_path) 310 - stable_root = stable_tree.getroot() 311 - tip_tree = ET.parse(tip_path) 312 - tip_root = tip_tree.getroot() 313 - 314 - ns = {'sparkle': 'http://www.andymatuschak.org/xml-namespaces/sparkle'} 315 - stable_channel = stable_root.find('.//channel') 316 - 317 - # Remove existing tip items from stable appcast 318 - for item in stable_channel.findall('item'): 319 - ch = item.find('sparkle:channel', ns) 320 - if ch is not None and ch.text == 'tip': 321 - stable_channel.remove(item) 322 - 323 - # Add new tip items from tip appcast 324 - tip_channel = tip_root.find('.//channel') 325 - for item in tip_channel.findall('item'): 326 - stable_channel.append(item) 327 - 328 - stable_tree.write(out_path, xml_declaration=True, encoding='utf-8') 329 - PYEOF 330 - 331 - mv merged_appcast.xml appcast.xml 332 - gh release upload "$LATEST_TAG" appcast.xml --clobber -R "${{ github.repository }}"
-272
.github/workflows/release.yml
··· 1 - name: Release 2 - 3 - on: 4 - release: 5 - types: [published] 6 - 7 - concurrency: 8 - group: release 9 - cancel-in-progress: false 10 - 11 - jobs: 12 - check: 13 - runs-on: ubuntu-latest 14 - outputs: 15 - should_skip: ${{ steps.check.outputs.should_skip }} 16 - steps: 17 - - name: Check if release already has artifacts 18 - id: check 19 - env: 20 - GH_TOKEN: ${{ github.token }} 21 - TAG: ${{ github.event.release.tag_name }} 22 - run: | 23 - ASSETS=$(gh release view "$TAG" -R "${{ github.repository }}" --json assets --jq '.assets | length') 24 - if [ "$ASSETS" -gt 0 ]; then 25 - echo "Release $TAG already has $ASSETS assets, skipping" 26 - echo "should_skip=true" >> "$GITHUB_OUTPUT" 27 - else 28 - echo "should_skip=false" >> "$GITHUB_OUTPUT" 29 - fi 30 - 31 - build: 32 - runs-on: macos-26 33 - needs: [check] 34 - if: needs.check.outputs.should_skip != 'true' 35 - permissions: 36 - contents: read 37 - env: 38 - MISE_HTTP_TIMEOUT: 120 39 - MISE_GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 40 - DEVELOPER_ID_CERT_P12: ${{ secrets.DEVELOPER_ID_CERT_P12 }} 41 - DEVELOPER_ID_CERT_PASSWORD: ${{ secrets.DEVELOPER_ID_CERT_PASSWORD }} 42 - DEVELOPER_ID_IDENTITY: ${{ secrets.DEVELOPER_ID_IDENTITY }} 43 - KEYCHAIN_PASSWORD: ${{ secrets.KEYCHAIN_PASSWORD }} 44 - APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }} 45 - APPLE_NOTARIZATION_ISSUER: ${{ secrets.APPLE_NOTARIZATION_ISSUER }} 46 - APPLE_NOTARIZATION_KEY_ID: ${{ secrets.APPLE_NOTARIZATION_KEY_ID }} 47 - APPLE_NOTARIZATION_KEY: ${{ secrets.APPLE_NOTARIZATION_KEY }} 48 - SPARKLE_PRIVATE_KEY: ${{ secrets.SPARKLE_PRIVATE_KEY }} 49 - steps: 50 - - uses: actions/checkout@v6 51 - with: 52 - submodules: recursive 53 - - uses: ./.github/actions/setup-macos 54 - - run: echo "TAG=${{ github.event.release.tag_name }}" >> "$GITHUB_ENV" 55 - - name: Override build number for versioning scheme 56 - run: | 57 - BASE=$(awk -F' = ' '/CURRENT_PROJECT_VERSION = [0-9]+/{gsub(/;/,""); print $2; exit}' supacode.xcodeproj/project.pbxproj) 58 - BUILD_NUMBER=$((BASE * 1000)) 59 - sed -i '' "s/CURRENT_PROJECT_VERSION = [0-9]*/CURRENT_PROJECT_VERSION = $BUILD_NUMBER/g" supacode.xcodeproj/project.pbxproj 60 - - name: Setup keychain 61 - run: | 62 - echo "$DEVELOPER_ID_CERT_P12" | base64 --decode > build-cert.p12 63 - security create-keychain -p "$KEYCHAIN_PASSWORD" build.keychain 64 - security set-keychain-settings -t 3600 -u build.keychain 65 - security unlock-keychain -p "$KEYCHAIN_PASSWORD" build.keychain 66 - security import build-cert.p12 -k build.keychain -P "$DEVELOPER_ID_CERT_PASSWORD" -T /usr/bin/codesign -T /usr/bin/security -T /usr/bin/xcodebuild > /dev/null 67 - security set-key-partition-list -S apple-tool:,apple:,codesign: -s -k "$KEYCHAIN_PASSWORD" build.keychain > /dev/null 68 - security list-keychains -d user -s build.keychain $(security list-keychains -d user | tr -d '"') 69 - security default-keychain -s build.keychain 70 - DEVELOPER_ID_IDENTITY_SHA=$(security find-identity -v -p codesigning build.keychain | grep "Developer ID Application" | head -1 | awk '{print $2}') 71 - if [ -z "$DEVELOPER_ID_IDENTITY_SHA" ]; then 72 - echo "::error::Developer ID Application identity not found in keychain" 73 - exit 1 74 - fi 75 - echo "DEVELOPER_ID_IDENTITY_SHA=$DEVELOPER_ID_IDENTITY_SHA" >> "$GITHUB_ENV" 76 - - name: Inject secrets 77 - env: 78 - SENTRY_DSN: ${{ secrets.SENTRY_DSN }} 79 - POSTHOG_API_KEY: ${{ secrets.POSTHOG_API_KEY }} 80 - POSTHOG_HOST: ${{ secrets.POSTHOG_HOST }} 81 - run: | 82 - set -euo pipefail 83 - : "${SENTRY_DSN:?secret SENTRY_DSN is not set}" 84 - : "${POSTHOG_API_KEY:?secret POSTHOG_API_KEY is not set}" 85 - : "${POSTHOG_HOST:?secret POSTHOG_HOST is not set}" 86 - sed -i '' "s|__SENTRY_DSN__|${SENTRY_DSN}|g" supacode/App/supacodeApp.swift 87 - sed -i '' "s|__POSTHOG_API_KEY__|${POSTHOG_API_KEY}|g" supacode/App/supacodeApp.swift 88 - sed -i '' "s|__POSTHOG_HOST__|${POSTHOG_HOST}|g" supacode/App/supacodeApp.swift 89 - - name: Archive Xcode project 90 - run: make archive 91 - - name: Upload dSYMs 92 - uses: actions/upload-artifact@v6 93 - with: 94 - name: dsyms 95 - path: build/supacode.xcarchive/dSYMs 96 - - run: | 97 - cat > build/ExportOptions.plist <<EOF 98 - <?xml version="1.0" encoding="UTF-8"?> 99 - <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> 100 - <plist version="1.0"> 101 - <dict> 102 - <key>method</key> 103 - <string>developer-id</string> 104 - <key>signingStyle</key> 105 - <string>manual</string> 106 - <key>signingCertificate</key> 107 - <string>$DEVELOPER_ID_IDENTITY</string> 108 - <key>teamID</key> 109 - <string>$APPLE_TEAM_ID</string> 110 - </dict> 111 - </plist> 112 - EOF 113 - make export-archive 114 - - name: Re-sign Sparkle framework 115 - run: | 116 - set -ex 117 - APP_PATH="$(find build/export -name "supacode.app" -maxdepth 3 -print -quit)" 118 - SPARKLE="$APP_PATH/Contents/Frameworks/Sparkle.framework/Versions/B" 119 - 120 - echo "Using identity: $DEVELOPER_ID_IDENTITY" 121 - codesign -f -s "$DEVELOPER_ID_IDENTITY_SHA" -o runtime --timestamp -v "$SPARKLE/XPCServices/Installer.xpc" 122 - codesign -f -s "$DEVELOPER_ID_IDENTITY_SHA" -o runtime --timestamp --preserve-metadata=entitlements -v "$SPARKLE/XPCServices/Downloader.xpc" 123 - codesign -f -s "$DEVELOPER_ID_IDENTITY_SHA" -o runtime --timestamp -v "$SPARKLE/Updater.app" 124 - codesign -f -s "$DEVELOPER_ID_IDENTITY_SHA" -o runtime --timestamp -v "$SPARKLE/Autoupdate" 125 - codesign -f -s "$DEVELOPER_ID_IDENTITY_SHA" -o runtime --timestamp -v "$SPARKLE/Sparkle" 126 - codesign -f -s "$DEVELOPER_ID_IDENTITY_SHA" -o runtime --timestamp -v "$APP_PATH/Contents/Frameworks/Sparkle.framework" 127 - SENTRY_FRAMEWORK="$APP_PATH/Contents/Frameworks/Sentry.framework" 128 - codesign -f -s "$DEVELOPER_ID_IDENTITY_SHA" -o runtime --timestamp -v "$SENTRY_FRAMEWORK/Versions/A/Sentry" 129 - codesign -f -s "$DEVELOPER_ID_IDENTITY_SHA" -o runtime --timestamp -v "$SENTRY_FRAMEWORK" 130 - codesign -f -s "$DEVELOPER_ID_IDENTITY_SHA" -o runtime --timestamp --preserve-metadata=entitlements,requirements,flags -v "$APP_PATH" 131 - 132 - codesign -d --entitlements - "$APP_PATH/Contents/MacOS/supacode" 2>&1 | tee /tmp/supacode-entitlements.txt 133 - grep -q "com.apple.security.device.audio-input" /tmp/supacode-entitlements.txt 134 - 135 - codesign -vvv --deep --strict "$APP_PATH" 136 - codesign -dv --verbose=4 "$APP_PATH" 2>&1 | grep -E "Authority=Developer ID Application|Timestamp=" 137 - codesign -dv --verbose=4 "$APP_PATH/Contents/MacOS/supacode" 2>&1 | grep -E "Authority=Developer ID Application|Timestamp=" 138 - codesign -dv --verbose=4 "$SPARKLE/Updater.app/Contents/MacOS/Updater" 2>&1 | grep -E "Authority=Developer ID Application|Timestamp=" 139 - codesign -dv --verbose=4 "$SPARKLE/XPCServices/Installer.xpc/Contents/MacOS/Installer" 2>&1 | grep -E "Authority=Developer ID Application|Timestamp=" 140 - codesign -dv --verbose=4 "$SPARKLE/XPCServices/Downloader.xpc/Contents/MacOS/Downloader" 2>&1 | grep -E "Authority=Developer ID Application|Timestamp=" 141 - echo "Signature verified successfully" 142 - - name: Store notarization credentials 143 - run: | 144 - echo "$APPLE_NOTARIZATION_KEY" > notarization_key.p8 145 - xcrun notarytool store-credentials "notarytool-profile" \ 146 - --key notarization_key.p8 \ 147 - --key-id "$APPLE_NOTARIZATION_KEY_ID" \ 148 - --issuer "$APPLE_NOTARIZATION_ISSUER" 149 - rm notarization_key.p8 150 - - name: Build DMG 151 - run: | 152 - APP_PATH="$(find build/export -name "supacode.app" -maxdepth 3 -print -quit)" 153 - 154 - mise exec -- create-dmg "$APP_PATH" build/ \ 155 - --overwrite \ 156 - --dmg-title="Supacode" \ 157 - --identity="$DEVELOPER_ID_IDENTITY_SHA" 158 - 159 - DMG_OUTPUT=$(find build -name "*.dmg" -maxdepth 1 | head -1) 160 - if [ "$DMG_OUTPUT" != "build/supacode.dmg" ]; then 161 - mv "$DMG_OUTPUT" build/supacode.dmg 162 - fi 163 - - name: Notarize and staple 164 - run: | 165 - APP_PATH="$(find build/export -name "supacode.app" -maxdepth 3 -print -quit)" 166 - 167 - for attempt in 1 2 3; do 168 - echo "Notarization attempt $attempt..." 169 - xcrun notarytool submit build/supacode.dmg --keychain-profile "notarytool-profile" --wait && break 170 - echo "Attempt $attempt failed, retrying in 30s..." 171 - sleep 30 172 - done 173 - 174 - xcrun stapler staple build/supacode.dmg 175 - xcrun stapler staple "$APP_PATH" 176 - ditto -c -k --sequesterRsrc --keepParent "$APP_PATH" build/supacode.app.zip 177 - VERSION=$(/usr/libexec/PlistBuddy -c "Print :CFBundleShortVersionString" "$APP_PATH/Contents/Info.plist") 178 - echo "VERSION=$VERSION" >> "$GITHUB_ENV" 179 - - name: Fetch release notes 180 - env: 181 - GH_TOKEN: ${{ github.token }} 182 - run: | 183 - gh release view "$TAG" --json body --jq '.body' > build/release-notes.md 184 - - name: Generate appcast with history and deltas 185 - env: 186 - GH_TOKEN: ${{ github.token }} 187 - run: | 188 - set -euo pipefail 189 - MAX_DELTAS=10 190 - NOTES_FILE=build/release-notes.md 191 - STAGING=$(mktemp -d) 192 - ARCHIVE=build/supacode.app.zip 193 - ARCHIVE_BASE=$(basename "$ARCHIVE") 194 - ARCHIVE_BASE="${ARCHIVE_BASE%.zip}" 195 - cp "$ARCHIVE" "$STAGING/" 196 - cp "$NOTES_FILE" "$STAGING/$ARCHIVE_BASE.md" 197 - 198 - curl -fsSL "https://supacode.sh/download/latest/appcast.xml" -o "$STAGING/appcast.xml" || true 199 - 200 - RELEASE_REPO="supabitapp/supacode" 201 - SEEN_VERSIONS="" 202 - for i in $(seq 1 $MAX_DELTAS); do 203 - PREV_TAG=$(gh release list --limit $i --exclude-drafts --exclude-pre-releases -R "$RELEASE_REPO" --json tagName --jq ".[$((i-1))].tagName" 2>/dev/null || true) 204 - if [ -n "$PREV_TAG" ] && [ "$PREV_TAG" != "$TAG" ]; then 205 - echo "Downloading $PREV_TAG for delta generation..." 206 - gh release download "$PREV_TAG" -p "supacode.app.zip" -O "$STAGING/supacode-$PREV_TAG.app.zip" -R "$RELEASE_REPO" 2>/dev/null || true 207 - if [ -f "$STAGING/supacode-$PREV_TAG.app.zip" ]; then 208 - BUNDLE_VERSION=$(unzip -p "$STAGING/supacode-$PREV_TAG.app.zip" "supacode.app/Contents/Info.plist" 2>/dev/null | /usr/libexec/PlistBuddy -c "Print :CFBundleShortVersionString" /dev/stdin 2>/dev/null || echo "") 209 - if [ -n "$BUNDLE_VERSION" ] && echo "$SEEN_VERSIONS" | grep -q "^$BUNDLE_VERSION$"; then 210 - echo "Skipping $PREV_TAG (duplicate bundle version $BUNDLE_VERSION)" 211 - rm -f "$STAGING/supacode-$PREV_TAG.app.zip" 212 - else 213 - [ -n "$BUNDLE_VERSION" ] && SEEN_VERSIONS="$SEEN_VERSIONS$BUNDLE_VERSION"$'\n' 214 - gh release view "$PREV_TAG" -R "$RELEASE_REPO" --json body --jq '.body' > "$STAGING/supacode-$PREV_TAG.app.md" 2>/dev/null || true 215 - fi 216 - fi 217 - fi 218 - done 219 - 220 - printf "%s" "$SPARKLE_PRIVATE_KEY" | tr -d '\r\n\t ' | ./bins/generate_appcast --download-url-prefix "https://supacode.sh/download/$TAG/" --full-release-notes-url "https://github.com/supabitapp/supacode/releases" --embed-release-notes --maximum-versions $MAX_DELTAS --maximum-deltas $MAX_DELTAS --delta-compression lzma --ed-key-file - "$STAGING" 221 - cp "$STAGING/appcast.xml" build/appcast.xml 222 - find "$STAGING" -name "*.delta" -exec cp {} build/ \; 2>/dev/null || true 223 - - uses: actions/upload-artifact@v6 224 - with: 225 - name: build-artifacts 226 - path: | 227 - build/supacode.app.zip 228 - build/supacode.dmg 229 - build/appcast.xml 230 - build/*.delta 231 - 232 - sentry-dsym: 233 - runs-on: ubuntu-latest 234 - needs: [check, build] 235 - if: needs.check.outputs.should_skip != 'true' 236 - steps: 237 - - name: Install sentry-cli 238 - run: curl -sL https://sentry.io/get-cli/ | sh 239 - - uses: actions/download-artifact@v7 240 - with: 241 - name: dsyms 242 - path: dsyms 243 - - name: Upload dSYMs to Sentry 244 - env: 245 - SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }} 246 - SENTRY_ORG: supabit 247 - SENTRY_PROJECT: supacode 248 - run: sentry-cli debug-files upload --include-sources dsyms 249 - 250 - publish: 251 - runs-on: ubuntu-latest 252 - needs: [check, build] 253 - if: needs.check.outputs.should_skip != 'true' 254 - permissions: 255 - contents: write 256 - env: 257 - RELEASE_REPO: supabitapp/supacode 258 - steps: 259 - - run: echo "TAG=${{ github.event.release.tag_name }}" >> "$GITHUB_ENV" 260 - - uses: actions/download-artifact@v7 261 - with: 262 - name: build-artifacts 263 - path: build 264 - - run: | 265 - DELTA_FILES=$(find build -name "*.delta" -type f 2>/dev/null | tr '\n' ' ' || true) 266 - gh release upload "$TAG" build/supacode.app.zip build/supacode.dmg build/appcast.xml $DELTA_FILES --clobber -R "$RELEASE_REPO" 267 - env: 268 - GH_TOKEN: ${{ github.token }} 269 - - run: | 270 - set -euo pipefail 271 - curl -fsSL "https://supacode.sh/download/$TAG/supacode.app.zip" -o /dev/null 272 - curl -fsSL "https://supacode.sh/download/$TAG/supacode.dmg" -o /dev/null