From 2c7621c1a541f1d71b01db878b4d3b0ece7c1922 Mon Sep 17 00:00:00 2001 From: Amonoman Date: Sat, 2 May 2026 19:28:26 +0200 Subject: [PATCH] revert: release.yml to normal --- .github/workflows/release.yml | 506 +++++++++++++++++++++++++++++++++- 1 file changed, 499 insertions(+), 7 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 51f74317..4769f2ec 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -1,4 +1,4 @@ -name: Build Android +name: Release on: push: @@ -12,18 +12,52 @@ on: default: "v1.0.0" jobs: + # Get version first (quick job) + get-version: + runs-on: ubuntu-latest + outputs: + version: ${{ steps.version.outputs.version }} + is_prerelease: ${{ steps.version.outputs.is_prerelease }} + steps: + - name: Get version + id: version + run: | + if [ "${{ github.event_name }}" = "workflow_dispatch" ]; then + VERSION="${{ github.event.inputs.version }}" + else + VERSION="${GITHUB_REF#refs/tags/}" + fi + echo "version=$VERSION" >> $GITHUB_OUTPUT + + # Check if version contains -preview, -beta, -rc, or -alpha (NOT -hotfix) + VERSION_LOWER=$(echo "$VERSION" | tr '[:upper:]' '[:lower:]') + if [[ "$VERSION_LOWER" == *"-preview"* ]] || [[ "$VERSION_LOWER" == *"-beta"* ]] || [[ "$VERSION_LOWER" == *"-rc"* ]] || [[ "$VERSION_LOWER" == *"-alpha"* ]]; then + echo "is_prerelease=true" >> $GITHUB_OUTPUT + echo "Detected pre-release version: $VERSION" + else + echo "is_prerelease=false" >> $GITHUB_OUTPUT + echo "Detected stable version: $VERSION" + fi + + # Android and iOS build in PARALLEL build-android: runs-on: ubuntu-latest + needs: get-version + steps: - name: Free disk space run: | + # Remove large unused tools (~15GB total) sudo rm -rf /usr/share/dotnet sudo rm -rf /opt/ghc sudo rm -rf /opt/hostedtoolcache/CodeQL sudo rm -rf /usr/local/share/boost sudo rm -rf /usr/share/swift sudo rm -rf /usr/local/.ghcup + # Clean docker images sudo docker image prune --all --force + # Show available space + df -h - name: Checkout repository uses: actions/checkout@v6 @@ -40,6 +74,7 @@ jobs: go-version: "1.25.8" cache-dependency-path: go_backend/go.sum + # Cache Gradle for faster builds - name: Cache Gradle uses: actions/cache@v5 with: @@ -47,11 +82,22 @@ jobs: ~/.gradle/caches ~/.gradle/wrapper key: gradle-${{ runner.os }}-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties') }} + restore-keys: gradle-${{ runner.os }}- - name: Install Android SDK & NDK run: | + # Use pre-installed Android SDK on GitHub runners + echo "ANDROID_HOME=$ANDROID_HOME" + echo "ANDROID_SDK_ROOT=$ANDROID_SDK_ROOT" + + # Accept licenses yes | $ANDROID_HOME/cmdline-tools/latest/bin/sdkmanager --licenses || true + + # Install NDK r29 (supports 16KB page size for Android 15+) + # Platform android-36 and build-tools 36.0.0 for targetSdk 36 (Android 16) $ANDROID_HOME/cmdline-tools/latest/bin/sdkmanager "ndk;29.0.14206865" "platforms;android-36" "build-tools;36.0.0" + + # Set NDK path echo "ANDROID_NDK_HOME=$ANDROID_HOME/ndk/29.0.14206865" >> $GITHUB_ENV - name: Install gomobile @@ -76,14 +122,460 @@ jobs: - name: Get Flutter dependencies run: flutter pub get - - name: Generate code - run: dart run build_runner build --delete-conflicting-outputs + - name: Generate app icons + run: dart run flutter_launcher_icons - - name: Build APK (Only arm64) - run: flutter build apk --release --target-platform android-arm64 + - name: Build APK (Release - unsigned) + run: | + flutter build apk --release --split-per-abi || true + # Verify APKs were created + ls -la build/app/outputs/flutter-apk/ + if [ ! -f "build/app/outputs/flutter-apk/app-arm64-v8a-release.apk" ]; then + echo "ERROR: APK not found!" + exit 1 + fi + + - name: Sign APKs + uses: r0adkll/sign-android-release@v1 + id: sign_arm64 + with: + releaseDirectory: build/app/outputs/flutter-apk + signingKeyBase64: ${{ secrets.KEYSTORE_BASE64 }} + alias: ${{ secrets.KEY_ALIAS }} + keyStorePassword: ${{ secrets.KEYSTORE_PASSWORD }} + keyPassword: ${{ secrets.KEY_PASSWORD }} + env: + BUILD_TOOLS_VERSION: "36.0.0" + + - name: Rename APKs + run: | + VERSION=${{ needs.get-version.outputs.version }} + cd build/app/outputs/flutter-apk + # Signed files have -signed suffix + mv app-arm64-v8a-release-signed.apk SpotiFLAC-${VERSION}-arm64.apk || mv app-arm64-v8a-release.apk SpotiFLAC-${VERSION}-arm64.apk || true + mv app-armeabi-v7a-release-signed.apk SpotiFLAC-${VERSION}-arm32.apk || mv app-armeabi-v7a-release.apk SpotiFLAC-${VERSION}-arm32.apk || true + mv app-release-signed.apk SpotiFLAC-${VERSION}-universal.apk || mv app-release.apk SpotiFLAC-${VERSION}-universal.apk || true + ls -la - name: Upload APK artifact uses: actions/upload-artifact@v6 with: - name: SpotiFLAC-arm64-apk - path: build/app/outputs/flutter-apk/app-release.apk + name: android-apk + path: build/app/outputs/flutter-apk/SpotiFLAC-*.apk + + build-ios: + runs-on: macos-15 + needs: get-version # Only depends on version, NOT android build! + + steps: + - name: Checkout repository + uses: actions/checkout@v6 + + - name: Select Xcode 26.1.1 + run: | + sudo xcode-select -s /Applications/Xcode_26.1.1.app + xcodebuild -version + + - name: Setup Go + uses: actions/setup-go@v6 + with: + go-version: "1.25.8" + cache-dependency-path: go_backend/go.sum + + # Cache CocoaPods + - name: Cache CocoaPods + uses: actions/cache@v5 + with: + path: ios/Pods + key: pods-${{ runner.os }}-${{ hashFiles('ios/Podfile.lock') }} + restore-keys: pods-${{ runner.os }}- + + - name: Install gomobile + run: | + go install golang.org/x/mobile/cmd/gomobile@latest + gomobile init + + - name: Build Go backend for iOS + working-directory: go_backend + run: | + mkdir -p ../ios/Frameworks + gomobile bind -target=ios -tags ios -o ../ios/Frameworks/Gobackend.xcframework . + env: + CGO_ENABLED: 1 + + - name: Verify XCFramework created + run: | + ls -la ios/Frameworks/ + ls -la ios/Frameworks/Gobackend.xcframework/ || (echo "ERROR: XCFramework not found!" && exit 1) + + - name: Add XCFramework to Xcode project + run: | + # Install xcodeproj gem for modifying Xcode project + sudo gem install xcodeproj + + # Create Ruby script to add framework + cat > add_framework.rb << 'EOF' + require 'xcodeproj' + + project_path = 'ios/Runner.xcodeproj' + project = Xcodeproj::Project.open(project_path) + + # Get the main target + target = project.targets.find { |t| t.name == 'Runner' } + + # Get or create Frameworks group + frameworks_group = project.main_group.find_subpath('Frameworks', true) + frameworks_group ||= project.main_group.new_group('Frameworks') + + # Add XCFramework reference + framework_path = 'Frameworks/Gobackend.xcframework' + framework_ref = frameworks_group.new_file(framework_path, :project) + + # Add to frameworks build phase + frameworks_build_phase = target.frameworks_build_phase + frameworks_build_phase.add_file_reference(framework_ref) + + # Add to embed frameworks build phase + embed_phase = target.build_phases.find { |p| p.is_a?(Xcodeproj::Project::Object::PBXCopyFilesBuildPhase) && p.name == 'Embed Frameworks' } + if embed_phase + build_file = embed_phase.add_file_reference(framework_ref) + build_file.settings = { 'ATTRIBUTES' => ['CodeSignOnCopy', 'RemoveHeadersOnCopy'] } + end + + project.save + puts "Successfully added Gobackend.xcframework to Xcode project" + EOF + + ruby add_framework.rb + + - name: Setup Flutter + uses: subosito/flutter-action@v2 + with: + channel: "stable" + cache: true + + - name: Get Flutter dependencies + run: flutter pub get + + - name: Generate app icons + run: dart run flutter_launcher_icons + + - name: Build iOS (unsigned) + run: | + # Build Flutter iOS without codesigning + flutter build ios --release --no-codesign --config-only + + # Use xcodebuild with code signing disabled + cd ios + xcodebuild -workspace Runner.xcworkspace \ + -scheme Runner \ + -configuration Release \ + -sdk iphoneos \ + -destination 'generic/platform=iOS' \ + -archivePath build/Runner.xcarchive \ + archive \ + CODE_SIGNING_ALLOWED=NO \ + CODE_SIGNING_REQUIRED=NO \ + CODE_SIGN_IDENTITY="" \ + DEVELOPMENT_TEAM="" + + - name: Create IPA + run: | + VERSION=${{ needs.get-version.outputs.version }} + mkdir -p build/ios/ipa + cd ios/build/Runner.xcarchive/Products/Applications + mkdir Payload + cp -r Runner.app Payload/ + # Use absolute path to avoid relative path issues + zip -r $GITHUB_WORKSPACE/build/ios/ipa/SpotiFLAC-${VERSION}-ios-unsigned.ipa Payload + rm -rf Payload + + - name: Verify IPA created + run: | + ls -la build/ios/ipa/ + VERSION=${{ needs.get-version.outputs.version }} + if [ ! -f "build/ios/ipa/SpotiFLAC-${VERSION}-ios-unsigned.ipa" ]; then + echo "ERROR: IPA not created!" + exit 1 + fi + + - name: Upload IPA artifact + uses: actions/upload-artifact@v6 + with: + name: ios-ipa + path: build/ios/ipa/SpotiFLAC-*.ipa + + create-release: + runs-on: ubuntu-latest + needs: [get-version, build-android, build-ios] + permissions: + contents: write + + steps: + - name: Checkout repository + uses: actions/checkout@v6 + with: + fetch-depth: 0 # Full history needed for git-cliff + + - name: Generate changelog with git-cliff + id: changelog + uses: orhun/git-cliff-action@v4 + with: + config: cliff.toml + args: --latest --strip header + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + OUTPUT: /tmp/changelog.txt + + - name: Show generated changelog + run: | + echo "Generated changelog:" + cat /tmp/changelog.txt + + - name: Download Android APK + uses: actions/download-artifact@v7 + with: + name: android-apk + path: ./release + + - name: Download iOS IPA + uses: actions/download-artifact@v7 + with: + name: ios-ipa + path: ./release + + - name: Prepare release body + run: | + VERSION=${{ needs.get-version.outputs.version }} + REPO_OWNER="${{ github.repository_owner }}" + REPO_NAME="${{ github.event.repository.name }}" + CURRENT_REF=$(git rev-list -n 1 "$VERSION" 2>/dev/null || git rev-parse HEAD) + PREVIOUS_TAG=$(git describe --tags --abbrev=0 "${CURRENT_REF}^" 2>/dev/null || true) + + # Start with git-cliff changelog, but replace its compare footer with a + # deterministic previous-tag lookup from git. + sed '/^## [0-9][0-9.[:alpha:]-]*$/d; /^\*\*Full Changelog\*\*/d' /tmp/changelog.txt > /tmp/release_body.txt + + if [ -n "$PREVIOUS_TAG" ]; then + printf '\n**Full Changelog**: [%s...%s](https://github.com/%s/%s/compare/%s...%s)\n' \ + "$PREVIOUS_TAG" "$VERSION" "$REPO_OWNER" "$REPO_NAME" "$PREVIOUS_TAG" "$VERSION" \ + >> /tmp/release_body.txt + fi + + # Append download section + cat >> /tmp/release_body.txt << FOOTER + + --- + + ### Downloads + + #### Android + - **arm64**: \`SpotiFLAC-${VERSION}-arm64.apk\` (recommended for modern devices) + - **arm32**: \`SpotiFLAC-${VERSION}-arm32.apk\` (older devices) + + #### iOS + - **iOS**: \`SpotiFLAC-${VERSION}-ios-unsigned.ipa\` (sideload required) + + ### Installation + **Android**: Enable "Install from unknown sources" and install the APK + **iOS**: Use AltStore, Sideloadly, or similar tools to sideload the IPA + + ![arm64](https://img.shields.io/github/downloads/${REPO_OWNER}/${REPO_NAME}/${VERSION}/SpotiFLAC-${VERSION}-arm64.apk?style=flat-square&logo=android&label=arm64&color=3DDC84) ![arm32](https://img.shields.io/github/downloads/${REPO_OWNER}/${REPO_NAME}/${VERSION}/SpotiFLAC-${VERSION}-arm32.apk?style=flat-square&logo=android&label=arm32&color=3DDC84) ![iOS](https://img.shields.io/github/downloads/${REPO_OWNER}/${REPO_NAME}/${VERSION}/SpotiFLAC-${VERSION}-ios-unsigned.ipa?style=flat-square&logo=apple&label=iOS&color=0078D6) + FOOTER + + echo "Release body:" + cat /tmp/release_body.txt + + - name: Create Release + uses: softprops/action-gh-release@v2 + with: + tag_name: ${{ needs.get-version.outputs.version }} + name: SpotiFLAC ${{ needs.get-version.outputs.version }} + body_path: /tmp/release_body.txt + files: ./release/* + draft: false + prerelease: ${{ needs.get-version.outputs.is_prerelease == 'true' }} + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + update-altstore: + runs-on: ubuntu-latest + needs: [get-version, build-ios, create-release] + if: ${{ needs.get-version.outputs.is_prerelease != 'true' }} + permissions: + contents: write + + steps: + - name: Checkout main branch + uses: actions/checkout@v6 + with: + ref: main + + - name: Download iOS IPA + uses: actions/download-artifact@v7 + with: + name: ios-ipa + path: ./release + + - name: Update apps.json + run: | + VERSION="${{ needs.get-version.outputs.version }}" + VERSION_NUM="${VERSION#v}" + DATE=$(date -u +%Y-%m-%d) + IPA_FILE=$(find ./release -name "*ios*.ipa" | head -1) + + if [ -z "$IPA_FILE" ]; then + echo "WARNING: IPA file not found, skipping apps.json update" + exit 0 + fi + + IPA_SIZE=$(stat -c%s "$IPA_FILE" 2>/dev/null || stat -f%z "$IPA_FILE") + + if [ ! -f apps.json ]; then + echo "WARNING: apps.json not found on main, skipping" + exit 0 + fi + + jq --arg ver "$VERSION_NUM" \ + --arg date "$DATE" \ + --arg url "https://github.com/zarzet/SpotiFLAC-Mobile/releases/download/${VERSION}/SpotiFLAC-${VERSION}-ios-unsigned.ipa" \ + --argjson size "$IPA_SIZE" \ + '.apps[0].version = $ver | .apps[0].versionDate = $date | .apps[0].downloadURL = $url | .apps[0].size = $size' \ + apps.json > apps.json.tmp && mv apps.json.tmp apps.json + + echo "Updated apps.json:" + cat apps.json + + - name: Commit and push + run: | + VERSION="${{ needs.get-version.outputs.version }}" + git config user.name "github-actions[bot]" + git config user.email "github-actions[bot]@users.noreply.github.com" + git add apps.json + git diff --cached --quiet && echo "No changes to commit" || \ + (git commit -m "chore: update AltStore source to ${VERSION}" && git push) + + notify-telegram: + runs-on: ubuntu-latest + needs: [get-version, create-release] + if: ${{ needs.get-version.outputs.is_prerelease != 'true' }} + + steps: + - name: Checkout repository + uses: actions/checkout@v6 + with: + fetch-depth: 0 + + - name: Download Android APK + uses: actions/download-artifact@v7 + with: + name: android-apk + path: ./release + + - name: Download iOS IPA + uses: actions/download-artifact@v7 + with: + name: ios-ipa + path: ./release + + - name: Generate changelog with git-cliff for Telegram + uses: orhun/git-cliff-action@v4 + with: + config: cliff.toml + args: --latest --strip all + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + OUTPUT: /tmp/cliff_tg.txt + + - name: Convert changelog for Telegram + id: changelog + run: | + if [ ! -s /tmp/cliff_tg.txt ]; then + echo "See release notes on GitHub for details." > /tmp/changelog.txt + else + # Convert Markdown to Telegram HTML + CHANGELOG=$(cat /tmp/cliff_tg.txt | \ + sed '/^## [0-9][0-9.[:alpha:]-]*$/d' | \ + sed '/^\*\*Full Changelog\*\*/d' | \ + sed 's/ by \[@[^]]*\](https:\/\/github\.com\/[^)]*)//g' | \ + sed 's/ by @[A-Za-z0-9_-]\+//g' | \ + sed 's/\[#\([0-9]*\)\]([^)]*)/#\1/g' | \ + sed 's/\[@\([^]]*\)\]([^)]*)/@\1/g' | \ + sed 's/&/\&/g' | \ + sed 's//\>/g' | \ + sed 's/\*\*\([^*]*\)\*\*/\1<\/b>/g' | \ + sed 's/^### \(.*\)$/\1<\/b>/g' | \ + sed 's/^## \(.*\)$/\1<\/b>/g' | \ + sed 's/^- /• /g') + + # Truncate for Telegram 4096 char limit + CHANGELOG=$(echo "$CHANGELOG" | head -c 2500 | sed '$d') + echo "$CHANGELOG" > /tmp/changelog.txt + fi + + echo "Telegram changelog:" + cat /tmp/changelog.txt + + - name: Send to Telegram Channel + env: + TELEGRAM_BOT_TOKEN: ${{ secrets.TELEGRAM_BOT_TOKEN }} + TELEGRAM_CHANNEL_ID: ${{ secrets.TELEGRAM_CHANNEL_ID }} + run: | + VERSION=${{ needs.get-version.outputs.version }} + CHANGELOG=$(cat /tmp/changelog.txt) + + # Find APK files + ARM64_APK=$(find ./release -name "*arm64*.apk" | head -1) + ARM32_APK=$(find ./release -name "*arm32*.apk" | head -1) + + # Prepare message with changelog (HTML format) + printf '%s\n' \ + "SpotiFLAC Mobile ${VERSION} Released!" \ + "" \ + "What's New:" \ + "${CHANGELOG}" \ + "" \ + "View Release Notes" \ + > /tmp/telegram_message.txt + + MESSAGE=$(cat /tmp/telegram_message.txt) + + # Send message first (using HTML parse mode) + # Use --data-urlencode for proper encoding of special chars (+, &, etc.) + # Use || true to ensure file uploads continue even if message fails + curl -s -X POST "https://api.telegram.org/bot${TELEGRAM_BOT_TOKEN}/sendMessage" \ + --data-urlencode "chat_id=${TELEGRAM_CHANNEL_ID}" \ + --data-urlencode "text=${MESSAGE}" \ + --data-urlencode "parse_mode=HTML" \ + --data-urlencode "disable_web_page_preview=true" || true + + # Upload arm64 APK to channel + if [ -f "$ARM64_APK" ]; then + echo "Uploading arm64 APK to Telegram..." + curl -s -X POST "https://api.telegram.org/bot${TELEGRAM_BOT_TOKEN}/sendDocument" \ + -F chat_id="${TELEGRAM_CHANNEL_ID}" \ + -F document=@"${ARM64_APK}" \ + -F caption="SpotiFLAC ${VERSION} - arm64 (recommended)" + fi + + # Upload arm32 APK to channel + if [ -f "$ARM32_APK" ]; then + echo "Uploading arm32 APK to Telegram..." + curl -s -X POST "https://api.telegram.org/bot${TELEGRAM_BOT_TOKEN}/sendDocument" \ + -F chat_id="${TELEGRAM_CHANNEL_ID}" \ + -F document=@"${ARM32_APK}" \ + -F caption="SpotiFLAC ${VERSION} - arm32" + fi + + # Upload iOS IPA to channel + IOS_IPA=$(find ./release -name "*ios*.ipa" | head -1) + if [ -f "$IOS_IPA" ]; then + echo "Uploading iOS IPA to Telegram..." + curl -s -X POST "https://api.telegram.org/bot${TELEGRAM_BOT_TOKEN}/sendDocument" \ + -F chat_id="${TELEGRAM_CHANNEL_ID}" \ + -F document=@"${IOS_IPA}" \ + -F caption="SpotiFLAC ${VERSION} - iOS (unsigned, sideload required)" + fi + + echo "Telegram notification sent!"