Merge pull request #44 from TableProApp/fix/bundle-dylibs-for-distrib… #61
Workflow file for this run
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: Build TablePro | |
| on: | |
| push: | |
| tags: ["v*"] | |
| paths-ignore: | |
| - "**.md" | |
| - "docs/**" | |
| - ".vscode/**" | |
| env: | |
| XCODE_PROJECT: TablePro.xcodeproj | |
| XCODE_SCHEME: TablePro | |
| BUILD_CONFIGURATION: Release | |
| XCODE_VERSION: "26.2" # Updated to support macOS 26 Tahoe liquid design | |
| jobs: | |
| lint: | |
| name: SwiftLint | |
| runs-on: macos-latest # Updated to macOS 26 (Tahoe) runner | |
| timeout-minutes: 10 | |
| steps: | |
| - name: Checkout code | |
| uses: actions/checkout@v4 | |
| - name: Cache SwiftLint | |
| uses: actions/cache@v4 | |
| with: | |
| path: ~/Library/Caches/Homebrew/swiftlint* | |
| key: swiftlint-${{ runner.os }}-${{ hashFiles('.swiftlint.yml') }} | |
| restore-keys: | | |
| swiftlint-${{ runner.os }}- | |
| - name: Install SwiftLint | |
| run: brew install swiftlint | |
| - name: Run SwiftLint | |
| run: swiftlint lint --strict | |
| build-arm64: | |
| name: Build ARM64 | |
| runs-on: macos-latest # Updated to macOS 26 (Tahoe) runner | |
| needs: lint | |
| timeout-minutes: 20 | |
| steps: | |
| - name: Checkout code | |
| uses: actions/checkout@v4 | |
| - name: Cache Homebrew downloads | |
| uses: actions/cache@v4 | |
| continue-on-error: true | |
| with: | |
| path: ~/Library/Caches/Homebrew | |
| key: brew-arm64-downloads-${{ runner.os }}-${{ hashFiles('**/scripts/build-release.sh') }} | |
| restore-keys: | | |
| brew-arm64-downloads-${{ runner.os }}- | |
| - name: Install ARM64 dependencies | |
| run: | | |
| echo "Installing ARM64 dependencies..." | |
| # Check and install only if needed | |
| if ! brew list mariadb-connector-c &>/dev/null; then | |
| echo "📦 Installing mariadb-connector-c..." | |
| brew install mariadb-connector-c | |
| else | |
| echo "✅ mariadb-connector-c already installed" | |
| fi | |
| if ! brew list libpq &>/dev/null; then | |
| echo "📦 Installing libpq..." | |
| brew install libpq | |
| else | |
| echo "✅ libpq already installed" | |
| fi | |
| # Link packages with --force (needed for keg-only formulas) | |
| brew link --force mariadb-connector-c | |
| brew link --force libpq | |
| # Verify installations | |
| if ! brew list mariadb-connector-c >/dev/null 2>&1; then | |
| echo "❌ ERROR: mariadb-connector-c installation failed" | |
| exit 1 | |
| fi | |
| if ! brew list libpq >/dev/null 2>&1; then | |
| echo "❌ ERROR: libpq installation failed" | |
| exit 1 | |
| fi | |
| echo "✅ ARM64 dependencies installed" | |
| - name: Prepare libmariadb | |
| run: | | |
| echo "📦 Preparing libmariadb.a for arm64..." | |
| cp Libs/libmariadb_arm64.a Libs/libmariadb.a | |
| echo "✅ libmariadb.a ready" | |
| lipo -info Libs/libmariadb.a | |
| ls -lh Libs/libmariadb.a | |
| - name: Select Xcode version | |
| run: | | |
| sudo xcode-select -s /Applications/Xcode_${{ env.XCODE_VERSION }}.app/Contents/Developer | |
| echo "Selected Xcode version:" | |
| xcodebuild -version | |
| - name: Build ARM64 | |
| run: | | |
| chmod +x scripts/build-release.sh | |
| scripts/build-release.sh arm64 | |
| - name: Verify build | |
| run: | | |
| echo "Verifying build output..." | |
| BINARY_PATH="build/Release/TablePro-arm64.app/Contents/MacOS/TablePro" | |
| # Check binary exists | |
| if [ ! -f "$BINARY_PATH" ]; then | |
| echo "❌ ERROR: Built binary not found at: $BINARY_PATH" | |
| echo "Build may have failed silently" | |
| exit 1 | |
| fi | |
| # Check it's not empty | |
| if [ ! -s "$BINARY_PATH" ]; then | |
| echo "❌ ERROR: Binary file is empty" | |
| exit 1 | |
| fi | |
| # Check architecture | |
| ARCH_INFO=$(lipo -info "$BINARY_PATH") | |
| echo "Architecture: $ARCH_INFO" | |
| if ! echo "$ARCH_INFO" | grep -q "arm64"; then | |
| echo "❌ ERROR: Binary does not contain arm64 architecture" | |
| echo "Expected: arm64 only" | |
| echo "Got: $ARCH_INFO" | |
| exit 1 | |
| fi | |
| if echo "$ARCH_INFO" | grep -q "x86_64"; then | |
| echo "❌ ERROR: Binary contains x86_64 but should be arm64 only" | |
| exit 1 | |
| fi | |
| # Check it's executable | |
| if [ ! -x "$BINARY_PATH" ]; then | |
| echo "❌ ERROR: Binary is not executable" | |
| exit 1 | |
| fi | |
| # Verify bundled dylibs | |
| FRAMEWORKS_DIR="build/Release/TablePro-arm64.app/Contents/Frameworks" | |
| if [ -d "$FRAMEWORKS_DIR" ]; then | |
| echo "Bundled dynamic libraries:" | |
| ls -lh "$FRAMEWORKS_DIR"/*.dylib 2>/dev/null || echo " (none)" | |
| # Verify no Homebrew paths remain in the binary | |
| if otool -L "$BINARY_PATH" | grep -q '/opt/homebrew/\|/usr/local/opt/'; then | |
| echo "❌ ERROR: Binary still references Homebrew paths:" | |
| otool -L "$BINARY_PATH" | grep '/opt/homebrew/\|/usr/local/opt/' | |
| exit 1 | |
| fi | |
| echo "✅ No Homebrew path references in binary" | |
| else | |
| echo "⚠️ WARNING: No Frameworks directory found — dylibs may not be bundled" | |
| fi | |
| # Display info | |
| echo "✅ Build verified successfully" | |
| echo "Binary size: $(ls -lh "$BINARY_PATH" | awk '{print $5}')" | |
| echo "App bundle size: $(du -sh build/Release/TablePro-arm64.app | awk '{print $1}')" | |
| - name: Create DMG installer | |
| run: | | |
| echo "Creating DMG installer..." | |
| # Install create-dmg tool for proper icon handling in CI | |
| echo "📦 Installing create-dmg tool..." | |
| brew install create-dmg | |
| # Make DMG creation script executable | |
| chmod +x scripts/create-dmg.sh | |
| # Create DMG with version from git tag or use default | |
| # The script handles app renaming internally | |
| VERSION=$(git describe --tags --abbrev=0 2>/dev/null | sed 's/^v//' || echo "0.1.13") | |
| echo "📌 Using version: $VERSION" | |
| scripts/create-dmg.sh "$VERSION" "arm64" "build/Release/TablePro-arm64.app" | |
| # Verify DMG was created - check for the specific file or any arm64 DMG | |
| DMG_FILE="build/Release/TablePro-${VERSION}-arm64.dmg" | |
| if [ -f "$DMG_FILE" ]; then | |
| echo "✅ DMG installer created successfully: $DMG_FILE" | |
| else | |
| echo "⚠️ Expected DMG not found at: $DMG_FILE" | |
| echo "📂 Checking for any DMG files in build/Release/:" | |
| ls -la build/Release/*.dmg 2>/dev/null || echo " No DMG files found" | |
| # Check if any arm64 DMG was created (version might differ) | |
| if ls build/Release/*-arm64.dmg 1>/dev/null 2>&1; then | |
| echo "✅ Found arm64 DMG file(s):" | |
| ls -lh build/Release/*-arm64.dmg | |
| else | |
| echo "❌ ERROR: No arm64 DMG file was created" | |
| exit 1 | |
| fi | |
| fi | |
| ls -lh build/Release/*.dmg | |
| - name: Create ZIP archive (fallback) | |
| run: | | |
| echo "Creating ZIP archive..." | |
| cd build/Release | |
| if ! zip -r TablePro-arm64.zip TablePro-arm64.app; then | |
| echo "❌ ERROR: Failed to create ZIP archive" | |
| exit 1 | |
| fi | |
| echo "✅ ZIP archive created" | |
| ls -lh TablePro-arm64.zip | |
| - name: Upload artifacts | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: TablePro-arm64-${{ github.sha }} | |
| path: | | |
| build/Release/*.dmg | |
| build/Release/TablePro-arm64.zip | |
| retention-days: ${{ startsWith(github.ref, 'refs/tags/v') && 90 || 7 }} | |
| build-x86_64: | |
| name: Build x86_64 | |
| runs-on: macos-latest # Updated to macOS 26 (Tahoe) runner | |
| needs: lint | |
| timeout-minutes: 20 | |
| steps: | |
| - name: Checkout code | |
| uses: actions/checkout@v4 | |
| - name: Cache Homebrew downloads | |
| uses: actions/cache@v4 | |
| continue-on-error: true | |
| with: | |
| path: ~/Library/Caches/Homebrew | |
| key: brew-x86_64-downloads-${{ runner.os }}-${{ hashFiles('**/scripts/build-release.sh') }} | |
| restore-keys: | | |
| brew-x86_64-downloads-${{ runner.os }}- | |
| - name: Install Rosetta 2 | |
| run: | | |
| if ! arch -x86_64 /usr/bin/true 2>/dev/null; then | |
| echo "Installing Rosetta 2..." | |
| if ! softwareupdate --install-rosetta --agree-to-license; then | |
| echo "❌ ERROR: Failed to install Rosetta 2" | |
| exit 1 | |
| fi | |
| # Verify Rosetta 2 works | |
| if ! arch -x86_64 /usr/bin/true 2>/dev/null; then | |
| echo "❌ ERROR: Rosetta 2 installed but not functional" | |
| exit 1 | |
| fi | |
| echo "✅ Rosetta 2 installed" | |
| else | |
| echo "✅ Rosetta 2 already installed" | |
| fi | |
| - name: Install x86_64 Homebrew | |
| run: | | |
| if [ ! -f /usr/local/bin/brew ]; then | |
| echo "Installing x86_64 Homebrew..." | |
| if ! arch -x86_64 /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"; then | |
| echo "❌ ERROR: Homebrew installation failed" | |
| exit 1 | |
| fi | |
| if [ ! -f /usr/local/bin/brew ]; then | |
| echo "❌ ERROR: Homebrew not found after installation" | |
| exit 1 | |
| fi | |
| if ! /usr/local/bin/brew --version; then | |
| echo "❌ ERROR: Homebrew not functional" | |
| exit 1 | |
| fi | |
| echo "✅ x86_64 Homebrew installed" | |
| else | |
| echo "x86_64 Homebrew already installed" | |
| if ! /usr/local/bin/brew --version; then | |
| echo "❌ ERROR: Homebrew not functional" | |
| exit 1 | |
| fi | |
| fi | |
| - name: Install x86_64 dependencies | |
| run: | | |
| echo "Installing x86_64 dependencies..." | |
| # Check and install only if needed | |
| if ! arch -x86_64 /usr/local/bin/brew list mariadb-connector-c &>/dev/null; then | |
| echo "📦 Installing mariadb-connector-c (x86_64)..." | |
| arch -x86_64 /usr/local/bin/brew install mariadb-connector-c | |
| else | |
| echo "✅ mariadb-connector-c (x86_64) already installed" | |
| fi | |
| if ! arch -x86_64 /usr/local/bin/brew list libpq &>/dev/null; then | |
| echo "📦 Installing libpq (x86_64)..." | |
| arch -x86_64 /usr/local/bin/brew install libpq | |
| else | |
| echo "✅ libpq (x86_64) already installed" | |
| fi | |
| # Link packages with --force (needed for keg-only formulas) | |
| arch -x86_64 /usr/local/bin/brew link --force mariadb-connector-c | |
| arch -x86_64 /usr/local/bin/brew link --force libpq | |
| # Verify installations | |
| if ! arch -x86_64 /usr/local/bin/brew list mariadb-connector-c >/dev/null 2>&1; then | |
| echo "❌ ERROR: mariadb-connector-c installation failed" | |
| exit 1 | |
| fi | |
| if ! arch -x86_64 /usr/local/bin/brew list libpq >/dev/null 2>&1; then | |
| echo "❌ ERROR: libpq installation failed" | |
| exit 1 | |
| fi | |
| echo "✅ x86_64 dependencies installed" | |
| - name: Prepare libmariadb | |
| run: | | |
| echo "📦 Preparing libmariadb.a for x86_64..." | |
| cp Libs/libmariadb_x86_64.a Libs/libmariadb.a | |
| echo "✅ libmariadb.a ready" | |
| lipo -info Libs/libmariadb.a | |
| ls -lh Libs/libmariadb.a | |
| - name: Select Xcode version | |
| run: | | |
| sudo xcode-select -s /Applications/Xcode_${{ env.XCODE_VERSION }}.app/Contents/Developer | |
| echo "Selected Xcode version:" | |
| xcodebuild -version | |
| - name: Build x86_64 | |
| run: | | |
| chmod +x scripts/build-release.sh | |
| scripts/build-release.sh x86_64 | |
| - name: Verify build | |
| run: | | |
| echo "Verifying build output..." | |
| BINARY_PATH="build/Release/TablePro-x86_64.app/Contents/MacOS/TablePro" | |
| # Check binary exists | |
| if [ ! -f "$BINARY_PATH" ]; then | |
| echo "❌ ERROR: Built binary not found at: $BINARY_PATH" | |
| exit 1 | |
| fi | |
| # Check it's not empty | |
| if [ ! -s "$BINARY_PATH" ]; then | |
| echo "❌ ERROR: Binary file is empty" | |
| exit 1 | |
| fi | |
| # Check architecture | |
| ARCH_INFO=$(lipo -info "$BINARY_PATH") | |
| echo "Architecture: $ARCH_INFO" | |
| if ! echo "$ARCH_INFO" | grep -q "x86_64"; then | |
| echo "❌ ERROR: Binary does not contain x86_64 architecture" | |
| echo "Expected: x86_64 only" | |
| echo "Got: $ARCH_INFO" | |
| exit 1 | |
| fi | |
| if echo "$ARCH_INFO" | grep -q "arm64"; then | |
| echo "❌ ERROR: Binary contains arm64 but should be x86_64 only" | |
| exit 1 | |
| fi | |
| # Check it's executable | |
| if [ ! -x "$BINARY_PATH" ]; then | |
| echo "❌ ERROR: Binary is not executable" | |
| exit 1 | |
| fi | |
| # Verify bundled dylibs | |
| FRAMEWORKS_DIR="build/Release/TablePro-x86_64.app/Contents/Frameworks" | |
| if [ -d "$FRAMEWORKS_DIR" ]; then | |
| echo "Bundled dynamic libraries:" | |
| ls -lh "$FRAMEWORKS_DIR"/*.dylib 2>/dev/null || echo " (none)" | |
| # Verify no Homebrew paths remain in the binary | |
| if otool -L "$BINARY_PATH" | grep -q '/opt/homebrew/\|/usr/local/opt/'; then | |
| echo "❌ ERROR: Binary still references Homebrew paths:" | |
| otool -L "$BINARY_PATH" | grep '/opt/homebrew/\|/usr/local/opt/' | |
| exit 1 | |
| fi | |
| echo "✅ No Homebrew path references in binary" | |
| else | |
| echo "⚠️ WARNING: No Frameworks directory found — dylibs may not be bundled" | |
| fi | |
| # Display info | |
| echo "✅ Build verified successfully" | |
| echo "Binary size: $(ls -lh "$BINARY_PATH" | awk '{print $5}')" | |
| echo "App bundle size: $(du -sh build/Release/TablePro-x86_64.app | awk '{print $1}')" | |
| - name: Create DMG installer | |
| run: | | |
| echo "Creating DMG installer..." | |
| # Install create-dmg tool for proper icon handling in CI | |
| echo "📦 Installing create-dmg tool..." | |
| brew install create-dmg | |
| # Make DMG creation script executable | |
| chmod +x scripts/create-dmg.sh | |
| # Create DMG with version from git tag or use default | |
| # The script handles app renaming internally | |
| VERSION=$(git describe --tags --abbrev=0 2>/dev/null | sed 's/^v//' || echo "0.1.13") | |
| echo "📌 Using version: $VERSION" | |
| scripts/create-dmg.sh "$VERSION" "x86_64" "build/Release/TablePro-x86_64.app" | |
| # Verify DMG was created - check for the specific file or any x86_64 DMG | |
| DMG_FILE="build/Release/TablePro-${VERSION}-x86_64.dmg" | |
| if [ -f "$DMG_FILE" ]; then | |
| echo "✅ DMG installer created successfully: $DMG_FILE" | |
| else | |
| echo "⚠️ Expected DMG not found at: $DMG_FILE" | |
| echo "📂 Checking for any DMG files in build/Release/:" | |
| ls -la build/Release/*.dmg 2>/dev/null || echo " No DMG files found" | |
| # Check if any x86_64 DMG was created (version might differ) | |
| if ls build/Release/*-x86_64.dmg 1>/dev/null 2>&1; then | |
| echo "✅ Found x86_64 DMG file(s):" | |
| ls -lh build/Release/*-x86_64.dmg | |
| else | |
| echo "❌ ERROR: No x86_64 DMG file was created" | |
| exit 1 | |
| fi | |
| fi | |
| ls -lh build/Release/*.dmg | |
| - name: Create ZIP archive (fallback) | |
| run: | | |
| echo "Creating ZIP archive..." | |
| cd build/Release | |
| if ! zip -r TablePro-x86_64.zip TablePro-x86_64.app; then | |
| echo "❌ ERROR: Failed to create ZIP archive" | |
| exit 1 | |
| fi | |
| echo "✅ ZIP archive created" | |
| ls -lh TablePro-x86_64.zip | |
| - name: Upload artifacts | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: TablePro-x86_64-${{ github.sha }} | |
| path: | | |
| build/Release/*.dmg | |
| build/Release/TablePro-x86_64.zip | |
| retention-days: ${{ startsWith(github.ref, 'refs/tags/v') && 90 || 7 }} | |
| release: | |
| name: Create GitHub Release | |
| runs-on: macos-latest # Updated to macOS 26 (Tahoe) runner | |
| needs: [build-arm64, build-x86_64] | |
| if: startsWith(github.ref, 'refs/tags/v') | |
| timeout-minutes: 10 | |
| permissions: | |
| contents: write | |
| steps: | |
| - name: Checkout code | |
| uses: actions/checkout@v4 | |
| with: | |
| fetch-depth: 0 | |
| - name: Download ARM64 artifact | |
| uses: actions/download-artifact@v4 | |
| with: | |
| name: TablePro-arm64-${{ github.sha }} | |
| path: artifacts/ | |
| - name: Download x86_64 artifact | |
| uses: actions/download-artifact@v4 | |
| with: | |
| name: TablePro-x86_64-${{ github.sha }} | |
| path: artifacts/ | |
| - name: Verify and organize artifacts for release | |
| run: | | |
| VERSION=${GITHUB_REF#refs/tags/v} | |
| if [ -z "$VERSION" ]; then | |
| echo "❌ ERROR: Failed to extract version from ref: $GITHUB_REF" | |
| exit 1 | |
| fi | |
| echo "Preparing artifacts for version: $VERSION" | |
| echo "Contents of artifacts directory:" | |
| ls -la artifacts/ | |
| # Note: DMG files should already have correct names from build | |
| # ZIP files need to be renamed | |
| # Rename ZIP files if they exist | |
| if [ -f "artifacts/TablePro-arm64.zip" ]; then | |
| mv artifacts/TablePro-arm64.zip "artifacts/TablePro-${VERSION}-arm64.zip" | |
| fi | |
| if [ -f "artifacts/TablePro-x86_64.zip" ]; then | |
| mv artifacts/TablePro-x86_64.zip "artifacts/TablePro-${VERSION}-x86_64.zip" | |
| fi | |
| echo "✅ Artifacts organized successfully" | |
| echo "Final artifacts:" | |
| ls -lh artifacts/ | |
| - name: Sign update archives with Sparkle | |
| if: env.SPARKLE_PRIVATE_KEY != '' | |
| env: | |
| SPARKLE_PRIVATE_KEY: ${{ secrets.SPARKLE_PRIVATE_KEY }} | |
| run: | | |
| VERSION=${GITHUB_REF#refs/tags/v} | |
| # Install Sparkle tools (Cask — binaries in Caskroom, not on PATH) | |
| brew install --cask sparkle | |
| SPARKLE_BIN="$(brew --caskroom)/sparkle/$(ls "$(brew --caskroom)/sparkle" | head -1)/bin" | |
| ARM64_ZIP="artifacts/TablePro-${VERSION}-arm64.zip" | |
| X86_64_ZIP="artifacts/TablePro-${VERSION}-x86_64.zip" | |
| # Sign each ZIP with EdDSA using sign_update | |
| KEY_FILE=$(mktemp) | |
| echo "$SPARKLE_PRIVATE_KEY" > "$KEY_FILE" | |
| ARM64_SIG=$("$SPARKLE_BIN/sign_update" "$ARM64_ZIP" -f "$KEY_FILE") | |
| X86_64_SIG=$("$SPARKLE_BIN/sign_update" "$X86_64_ZIP" -f "$KEY_FILE") | |
| rm -f "$KEY_FILE" | |
| # Parse signature and length from sign_update output | |
| # Output format: sparkle:edSignature="..." length="..." | |
| ARM64_ED_SIG=$(echo "$ARM64_SIG" | sed -n 's/.*sparkle:edSignature="\([^"]*\)".*/\1/p') | |
| ARM64_LENGTH=$(echo "$ARM64_SIG" | sed -n 's/.*length="\([^"]*\)".*/\1/p') | |
| X86_64_ED_SIG=$(echo "$X86_64_SIG" | sed -n 's/.*sparkle:edSignature="\([^"]*\)".*/\1/p') | |
| X86_64_LENGTH=$(echo "$X86_64_SIG" | sed -n 's/.*length="\([^"]*\)".*/\1/p') | |
| # Extract version info from the top-level app's Info.plist inside the ZIP | |
| # Use -maxdepth 3 to avoid nested framework plists (e.g. Sparkle.framework) | |
| TEMP_DIR=$(mktemp -d) | |
| unzip -q "$ARM64_ZIP" -d "$TEMP_DIR" | |
| INFO_PLIST=$(find "$TEMP_DIR" -maxdepth 3 -path "*/Contents/Info.plist" | head -1) | |
| if [ -n "$INFO_PLIST" ] && [ -f "$INFO_PLIST" ]; then | |
| BUILD_NUMBER=$(/usr/libexec/PlistBuddy -c "Print :CFBundleVersion" "$INFO_PLIST" 2>/dev/null || echo "1") | |
| SHORT_VERSION=$(/usr/libexec/PlistBuddy -c "Print :CFBundleShortVersionString" "$INFO_PLIST" 2>/dev/null || echo "$VERSION") | |
| MIN_OS=$(/usr/libexec/PlistBuddy -c "Print :LSMinimumSystemVersion" "$INFO_PLIST" 2>/dev/null || echo "13.5") | |
| else | |
| echo "⚠️ Could not find app Info.plist in ZIP, using defaults from tag" | |
| BUILD_NUMBER="1" | |
| SHORT_VERSION="$VERSION" | |
| MIN_OS="13.5" | |
| fi | |
| rm -rf "$TEMP_DIR" | |
| # Build appcast.xml with architecture-specific items (Sparkle 2 convention) | |
| # Each item has sparkle:architectures on the enclosure so the client | |
| # automatically picks the matching architecture | |
| DOWNLOAD_PREFIX="https://github.com/datlechin/TablePro/releases/download/v${VERSION}" | |
| PUB_DATE=$(date -u '+%a, %d %b %Y %H:%M:%S +0000') | |
| mkdir -p appcast | |
| cat > appcast/appcast.xml << APPCAST_EOF | |
| <?xml version="1.0" standalone="yes"?> | |
| <rss xmlns:sparkle="http://www.andymatuschak.org/xml-namespaces/sparkle" version="2.0"> | |
| <channel> | |
| <title>TablePro</title> | |
| <item> | |
| <title>${SHORT_VERSION}</title> | |
| <pubDate>${PUB_DATE}</pubDate> | |
| <sparkle:version>${BUILD_NUMBER}</sparkle:version> | |
| <sparkle:shortVersionString>${SHORT_VERSION}</sparkle:shortVersionString> | |
| <sparkle:minimumSystemVersion>${MIN_OS}</sparkle:minimumSystemVersion> | |
| <enclosure url="${DOWNLOAD_PREFIX}/TablePro-${VERSION}-arm64.zip" length="${ARM64_LENGTH}" type="application/octet-stream" sparkle:edSignature="${ARM64_ED_SIG}" sparkle:architectures="arm64"/> | |
| </item> | |
| <item> | |
| <title>${SHORT_VERSION}</title> | |
| <pubDate>${PUB_DATE}</pubDate> | |
| <sparkle:version>${BUILD_NUMBER}</sparkle:version> | |
| <sparkle:shortVersionString>${SHORT_VERSION}</sparkle:shortVersionString> | |
| <sparkle:minimumSystemVersion>${MIN_OS}</sparkle:minimumSystemVersion> | |
| <enclosure url="${DOWNLOAD_PREFIX}/TablePro-${VERSION}-x86_64.zip" length="${X86_64_LENGTH}" type="application/octet-stream" sparkle:edSignature="${X86_64_ED_SIG}" sparkle:architectures="x86_64"/> | |
| </item> | |
| </channel> | |
| </rss> | |
| APPCAST_EOF | |
| echo "✅ Appcast generated with architecture-specific items:" | |
| cat appcast/appcast.xml | |
| - name: Upload appcast artifact | |
| if: env.SPARKLE_PRIVATE_KEY != '' | |
| uses: actions/upload-artifact@v4 | |
| env: | |
| SPARKLE_PRIVATE_KEY: ${{ secrets.SPARKLE_PRIVATE_KEY }} | |
| with: | |
| name: appcast-${{ github.sha }} | |
| path: appcast/appcast.xml | |
| retention-days: 90 | |
| - name: Commit appcast.xml to repo | |
| if: env.SPARKLE_PRIVATE_KEY != '' | |
| env: | |
| SPARKLE_PRIVATE_KEY: ${{ secrets.SPARKLE_PRIVATE_KEY }} | |
| run: | | |
| if [ ! -f appcast/appcast.xml ]; then | |
| echo "⚠️ No appcast.xml to commit" | |
| exit 0 | |
| fi | |
| cp appcast/appcast.xml appcast.xml | |
| git config user.name "github-actions[bot]" | |
| git config user.email "github-actions[bot]@users.noreply.github.com" | |
| git add appcast.xml | |
| git diff --cached --quiet && echo "No changes to appcast.xml" && exit 0 | |
| git commit -m "Update appcast.xml for v${GITHUB_REF#refs/tags/v}" | |
| git push origin HEAD:main | |
| - name: Extract release notes from CHANGELOG.md | |
| run: | | |
| VERSION=${GITHUB_REF#refs/tags/v} | |
| echo "Extracting release notes for version: $VERSION" | |
| # Extract the section for this version from CHANGELOG.md | |
| # Matches from "## [X.Y.Z]" until the next "## [" or end of file | |
| NOTES=$(awk -v ver="$VERSION" ' | |
| /^## \[/ { | |
| if (found) exit | |
| if ($0 ~ "\\[" ver "\\]") { found=1; next } | |
| } | |
| found { print } | |
| ' CHANGELOG.md) | |
| if [ -z "$NOTES" ]; then | |
| echo "⚠️ No changelog entry found for version $VERSION, using fallback" | |
| echo "- Bug fixes and improvements" > release_notes.md | |
| else | |
| echo "$NOTES" > release_notes.md | |
| fi | |
| echo "✅ Release notes extracted" | |
| cat release_notes.md | |
| - name: Create GitHub Release | |
| uses: softprops/action-gh-release@v1 | |
| with: | |
| files: | | |
| artifacts/*.dmg | |
| artifacts/*.zip | |
| body_path: release_notes.md | |
| draft: false | |
| prerelease: ${{ contains(github.ref, '-beta') || contains(github.ref, '-alpha') || contains(github.ref, '-rc') }} | |
| env: | |
| GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} |