diff --git a/.github/workflows/ci.yml b/.github/workflows/pr.yml similarity index 72% rename from .github/workflows/ci.yml rename to .github/workflows/pr.yml index 8dd64c6..161eb3a 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/pr.yml @@ -1,14 +1,23 @@ -name: CI +name: PR Check on: - push: - branches: [main, develop] - tags: - - 'v*' pull_request: + types: [opened, synchronize, reopened, ready_for_review] branches: [main, develop] - -# Cancel in-progress runs for the same branch + paths-ignore: + - '**.md' + - '.gitignore' + - '.github/ISSUE_TEMPLATE/**' + - 'fastlane/**' + workflow_dispatch: + inputs: + run_all_builds: + description: 'Run all platform builds' + required: false + default: false + type: boolean + +# Cancel in-progress runs for the same PR concurrency: group: ${{ github.workflow }}-${{ github.ref }} cancel-in-progress: true @@ -19,14 +28,55 @@ env: jobs: # ============================================================ - # Code Quality Checks (Strict) - Also generates code for other jobs + # Path Detection - Determine which platforms need building + # ============================================================ + changes: + name: Detect Changes + if: ${{ !github.event.pull_request.draft }} + runs-on: ubuntu-latest + permissions: + pull-requests: read + outputs: + core: ${{ steps.filter.outputs.core }} + android: ${{ steps.filter.outputs.android }} + windows: ${{ steps.filter.outputs.windows }} + ios: ${{ steps.filter.outputs.ios }} + macos: ${{ steps.filter.outputs.macos }} + linux: ${{ steps.filter.outputs.linux }} + steps: + - uses: actions/checkout@v4 + - uses: dorny/paths-filter@v3 + id: filter + with: + filters: | + core: + - 'lib/**' + - 'pubspec.yaml' + - 'pubspec.lock' + - 'test/**' + - 'assets/**' + - '.github/workflows/**' + android: + - 'android/**' + windows: + - 'windows/**' + ios: + - 'ios/**' + macos: + - 'macos/**' + linux: + - 'linux/**' + + # ============================================================ + # Code Quality Checks (Always runs) # ============================================================ code-quality: name: Code Quality + if: ${{ !github.event.pull_request.draft }} runs-on: ubuntu-latest steps: - name: Checkout code - uses: actions/checkout@v6 + uses: actions/checkout@v4 - name: Setup Flutter uses: subosito/flutter-action@v2 @@ -36,7 +86,7 @@ jobs: cache: true - name: Cache pub dependencies - uses: actions/cache@v5 + uses: actions/cache@v4 with: path: ~/.pub-cache key: pub-${{ runner.os }}-${{ hashFiles('pubspec.lock') }} @@ -53,7 +103,7 @@ jobs: run: dart format . - name: Upload generated code - uses: actions/upload-artifact@v6 + uses: actions/upload-artifact@v4 with: name: generated-code path: lib/**/*.g.dart @@ -72,17 +122,16 @@ jobs: echo "::endgroup::" # ============================================================ - # Unit Tests with Coverage Gate + # Unit Tests with Coverage # ============================================================ test: name: Tests + if: ${{ !github.event.pull_request.draft }} runs-on: ubuntu-latest needs: [code-quality] steps: - name: Checkout code - uses: actions/checkout@v6 - with: - fetch-depth: 2 + uses: actions/checkout@v4 - name: Setup Flutter uses: subosito/flutter-action@v2 @@ -92,7 +141,7 @@ jobs: cache: true - name: Cache pub dependencies - uses: actions/cache@v5 + uses: actions/cache@v4 with: path: ~/.pub-cache key: pub-${{ runner.os }}-${{ hashFiles('pubspec.lock') }} @@ -100,7 +149,7 @@ jobs: pub-${{ runner.os }}- - name: Download generated code - uses: actions/download-artifact@v7 + uses: actions/download-artifact@v4 with: name: generated-code path: lib/ @@ -113,48 +162,39 @@ jobs: - name: Check coverage threshold run: | - # Install lcov for coverage analysis sudo apt-get update && sudo apt-get install -y lcov - - # Generate coverage summary lcov --summary coverage/lcov.info > coverage_summary.txt 2>&1 || true cat coverage_summary.txt - - # Extract line coverage percentage COVERAGE=$(lcov --summary coverage/lcov.info 2>&1 | grep "lines" | sed 's/.*: //' | sed 's/%.*//' | head -1) - if [ -z "$COVERAGE" ]; then echo "::warning::Could not determine coverage percentage" exit 0 fi - echo "Coverage: ${COVERAGE}%" - - # Check against threshold (50% for now, increase gradually) THRESHOLD=50 if (( $(echo "$COVERAGE < $THRESHOLD" | bc -l) )); then echo "::warning::Coverage ${COVERAGE}% is below threshold ${THRESHOLD}%" - # Uncomment the next line to enforce threshold - # exit 1 else echo "::notice::Coverage ${COVERAGE}% meets threshold ${THRESHOLD}%" fi - name: Upload coverage reports to Codecov - uses: codecov/codecov-action@v5 + uses: codecov/codecov-action@v4 with: token: ${{ secrets.CODECOV_TOKEN }} + continue-on-error: true # ============================================================ # Code Complexity Analysis # ============================================================ complexity: name: Code Complexity + if: ${{ !github.event.pull_request.draft }} runs-on: ubuntu-latest needs: [code-quality] steps: - name: Checkout code - uses: actions/checkout@v6 + uses: actions/checkout@v4 - name: Setup Flutter uses: subosito/flutter-action@v2 @@ -164,7 +204,7 @@ jobs: cache: true - name: Cache pub dependencies - uses: actions/cache@v5 + uses: actions/cache@v4 with: path: ~/.pub-cache key: pub-${{ runner.os }}-${{ hashFiles('pubspec.lock') }} @@ -172,7 +212,7 @@ jobs: pub-${{ runner.os }}- - name: Download generated code - uses: actions/download-artifact@v7 + uses: actions/download-artifact@v4 with: name: generated-code path: lib/ @@ -194,29 +234,16 @@ jobs: --maximum-nesting-level=4 || true echo "::endgroup::" - - name: Generate HTML report - run: | - mkdir -p metrics-report - dart pub global run dart_code_linter:metrics analyze lib \ - --reporter=html \ - --output-directory=metrics-report || true - - - name: Upload complexity report - uses: actions/upload-artifact@v6 - with: - name: complexity-report - path: metrics-report/ - retention-days: 14 - # ============================================================ # Security Scan # ============================================================ security: name: Security Scan + if: ${{ !github.event.pull_request.draft }} runs-on: ubuntu-latest steps: - name: Checkout code - uses: actions/checkout@v6 + uses: actions/checkout@v4 - name: Setup Flutter uses: subosito/flutter-action@v2 @@ -225,14 +252,6 @@ jobs: channel: 'stable' cache: true - - name: Cache pub dependencies - uses: actions/cache@v5 - with: - path: ~/.pub-cache - key: pub-${{ runner.os }}-${{ hashFiles('pubspec.lock') }} - restore-keys: | - pub-${{ runner.os }}- - - name: Get dependencies run: flutter pub get @@ -244,25 +263,24 @@ jobs: - name: Audit dependencies run: | - # Export dependencies for analysis flutter pub deps --json > deps.json - echo "Dependencies exported for analysis" - - # Check for known security issues in pubspec.lock echo "::group::Dependency Analysis" cat pubspec.lock | grep -E "name:|version:" | head -50 echo "::endgroup::" # ============================================================ - # Build Tests (All Platforms) + # Platform Builds (Smart - only affected platforms) # ============================================================ build-windows: name: Build Windows + needs: [changes, code-quality] + if: | + !github.event.pull_request.draft && + (needs.changes.outputs.core == 'true' || needs.changes.outputs.windows == 'true' || github.event.inputs.run_all_builds == 'true') runs-on: windows-latest - needs: [code-quality] steps: - name: Checkout code - uses: actions/checkout@v6 + uses: actions/checkout@v4 - name: Setup Flutter uses: subosito/flutter-action@v2 @@ -272,7 +290,7 @@ jobs: cache: true - name: Cache pub dependencies - uses: actions/cache@v5 + uses: actions/cache@v4 with: path: ~\AppData\Local\Pub\Cache key: pub-${{ runner.os }}-${{ hashFiles('pubspec.lock') }} @@ -280,7 +298,7 @@ jobs: pub-${{ runner.os }}- - name: Download generated code - uses: actions/download-artifact@v7 + uses: actions/download-artifact@v4 with: name: generated-code path: lib/ @@ -292,19 +310,22 @@ jobs: run: flutter build windows --release - name: Upload build artifact - uses: actions/upload-artifact@v6 + uses: actions/upload-artifact@v4 with: name: windows-build-${{ github.sha }} path: build/windows/x64/runner/Release/ - retention-days: 7 + retention-days: 3 build-macos: name: Build macOS + needs: [changes, code-quality] + if: | + !github.event.pull_request.draft && + (needs.changes.outputs.core == 'true' || needs.changes.outputs.macos == 'true' || github.event.inputs.run_all_builds == 'true') runs-on: macos-latest - needs: [code-quality] steps: - name: Checkout code - uses: actions/checkout@v6 + uses: actions/checkout@v4 - name: Setup Flutter uses: subosito/flutter-action@v2 @@ -314,7 +335,7 @@ jobs: cache: true - name: Cache pub dependencies - uses: actions/cache@v5 + uses: actions/cache@v4 with: path: ~/.pub-cache key: pub-${{ runner.os }}-${{ hashFiles('pubspec.lock') }} @@ -322,7 +343,7 @@ jobs: pub-${{ runner.os }}- - name: Cache CocoaPods - uses: actions/cache@v5 + uses: actions/cache@v4 with: path: | macos/Pods @@ -332,7 +353,7 @@ jobs: pods-macos- - name: Download generated code - uses: actions/download-artifact@v7 + uses: actions/download-artifact@v4 with: name: generated-code path: lib/ @@ -344,19 +365,22 @@ jobs: run: flutter build macos --release - name: Upload build artifact - uses: actions/upload-artifact@v6 + uses: actions/upload-artifact@v4 with: name: macos-build-${{ github.sha }} path: build/macos/Build/Products/Release/ - retention-days: 7 + retention-days: 3 build-linux: name: Build Linux + needs: [changes, code-quality] + if: | + !github.event.pull_request.draft && + (needs.changes.outputs.core == 'true' || needs.changes.outputs.linux == 'true' || github.event.inputs.run_all_builds == 'true') runs-on: ubuntu-latest - needs: [code-quality] steps: - name: Checkout code - uses: actions/checkout@v6 + uses: actions/checkout@v4 - name: Install Linux dependencies run: | @@ -372,7 +396,7 @@ jobs: cache: true - name: Cache pub dependencies - uses: actions/cache@v5 + uses: actions/cache@v4 with: path: ~/.pub-cache key: pub-${{ runner.os }}-${{ hashFiles('pubspec.lock') }} @@ -380,7 +404,7 @@ jobs: pub-${{ runner.os }}- - name: Download generated code - uses: actions/download-artifact@v7 + uses: actions/download-artifact@v4 with: name: generated-code path: lib/ @@ -392,22 +416,25 @@ jobs: run: flutter build linux --release - name: Upload build artifact - uses: actions/upload-artifact@v6 + uses: actions/upload-artifact@v4 with: name: linux-build-${{ github.sha }} path: build/linux/x64/release/bundle/ - retention-days: 7 + retention-days: 3 build-android: name: Build Android + needs: [changes, code-quality] + if: | + !github.event.pull_request.draft && + (needs.changes.outputs.core == 'true' || needs.changes.outputs.android == 'true' || github.event.inputs.run_all_builds == 'true') runs-on: ubuntu-latest - needs: [code-quality] steps: - name: Checkout code - uses: actions/checkout@v6 + uses: actions/checkout@v4 - name: Setup Java - uses: actions/setup-java@v5 + uses: actions/setup-java@v4 with: distribution: 'temurin' java-version: ${{ env.JAVA_VERSION }} @@ -420,7 +447,7 @@ jobs: cache: true - name: Cache pub dependencies - uses: actions/cache@v5 + uses: actions/cache@v4 with: path: ~/.pub-cache key: pub-${{ runner.os }}-${{ hashFiles('pubspec.lock') }} @@ -428,7 +455,7 @@ jobs: pub-${{ runner.os }}- - name: Cache Gradle - uses: actions/cache@v5 + uses: actions/cache@v4 with: path: | ~/.gradle/caches @@ -444,7 +471,7 @@ jobs: https://github.com/spotify/android-sdk/raw/master/app-remote-lib/spotify-app-remote-release-0.8.0.aar - name: Download generated code - uses: actions/download-artifact@v7 + uses: actions/download-artifact@v4 with: name: generated-code path: lib/ @@ -456,19 +483,22 @@ jobs: run: flutter build apk --release - name: Upload build artifact - uses: actions/upload-artifact@v6 + uses: actions/upload-artifact@v4 with: name: android-build-${{ github.sha }} path: build/app/outputs/flutter-apk/app-release.apk - retention-days: 7 + retention-days: 3 build-ios: name: Build iOS + needs: [changes, code-quality] + if: | + !github.event.pull_request.draft && + (needs.changes.outputs.core == 'true' || needs.changes.outputs.ios == 'true' || github.event.inputs.run_all_builds == 'true') runs-on: macos-latest - needs: [code-quality] steps: - name: Checkout code - uses: actions/checkout@v6 + uses: actions/checkout@v4 - name: Setup Flutter uses: subosito/flutter-action@v2 @@ -478,7 +508,7 @@ jobs: cache: true - name: Cache pub dependencies - uses: actions/cache@v5 + uses: actions/cache@v4 with: path: ~/.pub-cache key: pub-${{ runner.os }}-${{ hashFiles('pubspec.lock') }} @@ -486,7 +516,7 @@ jobs: pub-${{ runner.os }}- - name: Cache CocoaPods - uses: actions/cache@v5 + uses: actions/cache@v4 with: path: | ios/Pods @@ -496,7 +526,7 @@ jobs: pods-ios- - name: Download generated code - uses: actions/download-artifact@v7 + uses: actions/download-artifact@v4 with: name: generated-code path: lib/ @@ -508,8 +538,8 @@ jobs: run: flutter build ios --release --no-codesign - name: Upload build artifact - uses: actions/upload-artifact@v6 + uses: actions/upload-artifact@v4 with: name: ios-build-${{ github.sha }} path: build/ios/iphoneos/ - retention-days: 7 + retention-days: 3 diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 0c3f93e..de44880 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -1,10 +1,15 @@ name: Release on: - workflow_run: - workflows: ["CI"] - types: - - completed + push: + tags: + - 'v*' + workflow_dispatch: + inputs: + tag: + description: 'Tag to release (e.g., v1.0.0)' + required: true + type: string # Ensure only one release workflow runs at a time concurrency: @@ -18,30 +23,32 @@ env: jobs: # ============================================================ - # Pre-release Checks (Must Pass) + # Pre-release Validation # ============================================================ - pre-release-checks: - name: Pre-release Checks + validate: + name: Validate Release runs-on: ubuntu-latest - # Only run if CI succeeded AND it was triggered by a tag push (v*) - if: | - github.event.workflow_run.conclusion == 'success' && - startsWith(github.event.workflow_run.head_branch, 'v') outputs: version: ${{ steps.version.outputs.version }} + tag: ${{ steps.version.outputs.tag }} steps: - - name: Checkout code - uses: actions/checkout@v6 - with: - ref: ${{ github.event.workflow_run.head_branch }} - - - name: Extract version from tag + - name: Determine tag id: version run: | - TAG="${{ github.event.workflow_run.head_branch }}" + if [ "${{ github.event_name }}" = "workflow_dispatch" ]; then + TAG="${{ github.event.inputs.tag }}" + else + TAG="${{ github.ref_name }}" + fi VERSION=${TAG#v} + echo "tag=$TAG" >> $GITHUB_OUTPUT echo "version=$VERSION" >> $GITHUB_OUTPUT - echo "Building version: $VERSION" + echo "Building version: $VERSION (tag: $TAG)" + + - name: Checkout code + uses: actions/checkout@v4 + with: + ref: ${{ steps.version.outputs.tag }} - name: Setup Flutter uses: subosito/flutter-action@v2 @@ -59,6 +66,13 @@ jobs: - name: Format generated files run: dart format . + - name: Upload generated code + uses: actions/upload-artifact@v4 + with: + name: generated-code + path: lib/**/*.g.dart + retention-days: 1 + - name: Check formatting run: dart format --output=none --set-exit-if-changed . @@ -74,12 +88,12 @@ jobs: build-windows: name: Build Windows runs-on: windows-latest - needs: [pre-release-checks] + needs: [validate] steps: - name: Checkout code - uses: actions/checkout@v6 + uses: actions/checkout@v4 with: - ref: ${{ github.event.workflow_run.head_branch }} + ref: ${{ needs.validate.outputs.tag }} - name: Setup Flutter uses: subosito/flutter-action@v2 @@ -88,28 +102,35 @@ jobs: channel: 'stable' cache: true + - name: Cache pub dependencies + uses: actions/cache@v4 + with: + path: ~\AppData\Local\Pub\Cache + key: pub-${{ runner.os }}-${{ hashFiles('pubspec.lock') }} + restore-keys: | + pub-${{ runner.os }}- + + - name: Download generated code + uses: actions/download-artifact@v4 + with: + name: generated-code + path: lib/ + - name: Get dependencies run: flutter pub get - - name: Generate code (build_runner) - run: dart run build_runner build --delete-conflicting-outputs - - name: Build Windows Release run: flutter build windows --release --obfuscate --split-debug-info=build/debug-info/windows - name: Create release directory structure shell: pwsh run: | - $version = "${{ needs.pre-release-checks.outputs.version }}" + $version = "${{ needs.validate.outputs.version }}" $releaseDir = "release/${{ env.APP_NAME }}-$version-windows" - # Create release directory New-Item -ItemType Directory -Force -Path $releaseDir - - # Copy build output Copy-Item -Path "build/windows/x64/runner/Release/*" -Destination $releaseDir -Recurse - # Create version info file @" ${{ env.APP_NAME }} v$version Built: $(Get-Date -Format "yyyy-MM-dd HH:mm:ss UTC") @@ -119,7 +140,7 @@ jobs: - name: Create ZIP archive shell: pwsh run: | - $version = "${{ needs.pre-release-checks.outputs.version }}" + $version = "${{ needs.validate.outputs.version }}" $releaseDir = "release/${{ env.APP_NAME }}-$version-windows" $zipFile = "release/${{ env.APP_NAME }}-$version-windows-x64.zip" @@ -133,7 +154,7 @@ jobs: - name: Create installer script shell: pwsh run: | - $version = "${{ needs.pre-release-checks.outputs.version }}" + $version = "${{ needs.validate.outputs.version }}" $issContent = @" #define MyAppName "${{ env.APP_NAME }}" #define MyAppVersion "$version" @@ -193,7 +214,7 @@ jobs: & "C:\Program Files (x86)\Inno Setup 6\ISCC.exe" installer.iss - name: Upload artifacts - uses: actions/upload-artifact@v6 + uses: actions/upload-artifact@v4 with: name: windows-release path: | @@ -202,17 +223,17 @@ jobs: retention-days: 30 # ============================================================ - # Build macOS Release (Universal Binary - ARM64 + x86_64) + # Build macOS Release # ============================================================ build-macos: name: Build macOS runs-on: macos-latest - needs: [pre-release-checks] + needs: [validate] steps: - name: Checkout code - uses: actions/checkout@v6 + uses: actions/checkout@v4 with: - ref: ${{ github.event.workflow_run.head_branch }} + ref: ${{ needs.validate.outputs.tag }} - name: Setup Flutter uses: subosito/flutter-action@v2 @@ -221,34 +242,48 @@ jobs: channel: 'stable' cache: true + - name: Cache pub dependencies + uses: actions/cache@v4 + with: + path: ~/.pub-cache + key: pub-${{ runner.os }}-${{ hashFiles('pubspec.lock') }} + restore-keys: | + pub-${{ runner.os }}- + + - name: Cache CocoaPods + uses: actions/cache@v4 + with: + path: | + macos/Pods + ~/.cocoapods + key: pods-macos-${{ hashFiles('macos/Podfile.lock') }} + restore-keys: | + pods-macos- + + - name: Download generated code + uses: actions/download-artifact@v4 + with: + name: generated-code + path: lib/ + - name: Get dependencies run: flutter pub get - - name: Generate code (build_runner) - run: dart run build_runner build --delete-conflicting-outputs - - name: Build macOS Release run: flutter build macos --release --obfuscate --split-debug-info=build/debug-info/macos - name: Create DMG run: | - VERSION="${{ needs.pre-release-checks.outputs.version }}" - # Find the actual app name (could be different from APP_NAME) + VERSION="${{ needs.validate.outputs.version }}" APP_FILE=$(ls -d build/macos/Build/Products/Release/*.app | head -1) APP_BASENAME=$(basename "$APP_FILE") DMG_NAME="${{ env.APP_NAME }}-${VERSION}-macos-universal.dmg" - # Create release directory mkdir -p release - - # Create a temporary directory for DMG contents mkdir -p dmg_contents cp -R "$APP_FILE" dmg_contents/ - - # Create a symbolic link to Applications folder ln -s /Applications dmg_contents/Applications - # Create DMG hdiutil create -volname "${{ env.APP_NAME }}" \ -srcfolder dmg_contents \ -ov -format UDZO \ @@ -258,33 +293,32 @@ jobs: - name: Create ZIP archive run: | - VERSION="${{ needs.pre-release-checks.outputs.version }}" + VERSION="${{ needs.validate.outputs.version }}" mkdir -p release - # Find the actual app name APP_FILE=$(ls -d build/macos/Build/Products/Release/*.app | head -1) APP_BASENAME=$(basename "$APP_FILE") cd build/macos/Build/Products/Release/ zip -r "../../../../../release/${{ env.APP_NAME }}-${VERSION}-macos-universal.zip" "$APP_BASENAME" - name: Upload artifacts - uses: actions/upload-artifact@v6 + uses: actions/upload-artifact@v4 with: name: macos-release path: release/ retention-days: 30 # ============================================================ - # Build Linux Release (AppImage + tar.gz) + # Build Linux Release # ============================================================ build-linux: name: Build Linux runs-on: ubuntu-latest - needs: [pre-release-checks] + needs: [validate] steps: - name: Checkout code - uses: actions/checkout@v6 + uses: actions/checkout@v4 with: - ref: ${{ github.event.workflow_run.head_branch }} + ref: ${{ needs.validate.outputs.tag }} - name: Install Linux dependencies run: | @@ -300,18 +334,29 @@ jobs: channel: 'stable' cache: true + - name: Cache pub dependencies + uses: actions/cache@v4 + with: + path: ~/.pub-cache + key: pub-${{ runner.os }}-${{ hashFiles('pubspec.lock') }} + restore-keys: | + pub-${{ runner.os }}- + + - name: Download generated code + uses: actions/download-artifact@v4 + with: + name: generated-code + path: lib/ + - name: Get dependencies run: flutter pub get - - name: Generate code (build_runner) - run: dart run build_runner build --delete-conflicting-outputs - - name: Build Linux Release run: flutter build linux --release --obfuscate --split-debug-info=build/debug-info/linux - name: Create tar.gz archive run: | - VERSION="${{ needs.pre-release-checks.outputs.version }}" + VERSION="${{ needs.validate.outputs.version }}" mkdir -p release cd build/linux/x64/release/bundle tar -czvf "../../../../../release/${{ env.APP_NAME }}-${VERSION}-linux-x64.tar.gz" . @@ -323,18 +368,15 @@ jobs: - name: Create AppImage run: | - VERSION="${{ needs.pre-release-checks.outputs.version }}" + VERSION="${{ needs.validate.outputs.version }}" - # Create AppDir structure mkdir -p AppDir/usr/bin mkdir -p AppDir/usr/lib mkdir -p AppDir/usr/share/applications mkdir -p AppDir/usr/share/icons/hicolor/256x256/apps - # Copy bundle contents cp -r build/linux/x64/release/bundle/* AppDir/usr/bin/ - # Create desktop entry cat > AppDir/usr/share/applications/${{ env.APP_NAME }}.desktop << EOF [Desktop Entry] Type=Application @@ -345,15 +387,10 @@ jobs: Comment=Focus music player powered by Spotify EOF - # Copy icon (use existing icon or create placeholder) - if [ -f "assets/icon/app_icon.png" ]; then - cp assets/icon/app_icon.png AppDir/usr/share/icons/hicolor/256x256/apps/fullstop.png - else - # Create a simple placeholder icon - convert -size 256x256 xc:purple AppDir/usr/share/icons/hicolor/256x256/apps/fullstop.png 2>/dev/null || true + if [ -f "assets/icon/logo.png" ]; then + cp assets/icon/logo.png AppDir/usr/share/icons/hicolor/256x256/apps/fullstop.png fi - # Create AppRun script cat > AppDir/AppRun << 'EOF' #!/bin/bash SELF=$(readlink -f "$0") @@ -364,35 +401,33 @@ jobs: EOF chmod +x AppDir/AppRun - # Copy desktop and icon to AppDir root cp AppDir/usr/share/applications/${{ env.APP_NAME }}.desktop AppDir/ cp AppDir/usr/share/icons/hicolor/256x256/apps/fullstop.png AppDir/ 2>/dev/null || touch AppDir/fullstop.png - # Build AppImage ARCH=x86_64 ./appimagetool AppDir "release/${{ env.APP_NAME }}-${VERSION}-linux-x64.AppImage" || true - name: Upload artifacts - uses: actions/upload-artifact@v6 + uses: actions/upload-artifact@v4 with: name: linux-release path: release/ retention-days: 30 # ============================================================ - # Build Android Release (APK) + # Build Android Release # ============================================================ build-android: name: Build Android runs-on: ubuntu-latest - needs: [pre-release-checks] + needs: [validate] steps: - name: Checkout code - uses: actions/checkout@v6 + uses: actions/checkout@v4 with: - ref: ${{ github.event.workflow_run.head_branch }} + ref: ${{ needs.validate.outputs.tag }} - name: Setup Java - uses: actions/setup-java@v5 + uses: actions/setup-java@v4 with: distribution: 'temurin' java-version: ${{ env.JAVA_VERSION }} @@ -404,18 +439,39 @@ jobs: channel: 'stable' cache: true + - name: Cache pub dependencies + uses: actions/cache@v4 + with: + path: ~/.pub-cache + key: pub-${{ runner.os }}-${{ hashFiles('pubspec.lock') }} + restore-keys: | + pub-${{ runner.os }}- + + - name: Cache Gradle + uses: actions/cache@v4 + with: + path: | + ~/.gradle/caches + ~/.gradle/wrapper + key: gradle-${{ runner.os }}-${{ hashFiles('android/**/*.gradle*', 'android/**/gradle-wrapper.properties') }} + restore-keys: | + gradle-${{ runner.os }}- + - name: Download Spotify App Remote SDK run: | mkdir -p android/spotify-app-remote curl -L -o android/spotify-app-remote/spotify-app-remote.aar \ https://github.com/spotify/android-sdk/raw/master/app-remote-lib/spotify-app-remote-release-0.8.0.aar + - name: Download generated code + uses: actions/download-artifact@v4 + with: + name: generated-code + path: lib/ + - name: Get dependencies run: flutter pub get - - name: Generate code (build_runner) - run: dart run build_runner build --delete-conflicting-outputs - - name: Build Android APK (Release - unsigned) run: flutter build apk --release --obfuscate --split-debug-info=build/debug-info/android @@ -424,7 +480,7 @@ jobs: - name: Rename and organize artifacts run: | - VERSION="${{ needs.pre-release-checks.outputs.version }}" + VERSION="${{ needs.validate.outputs.version }}" mkdir -p release cp build/app/outputs/flutter-apk/app-release.apk \ @@ -434,24 +490,24 @@ jobs: "release/${{ env.APP_NAME }}-${VERSION}-android.aab" - name: Upload artifacts - uses: actions/upload-artifact@v6 + uses: actions/upload-artifact@v4 with: name: android-release path: release/ retention-days: 30 # ============================================================ - # Build iOS Release (IPA - No Codesign) + # Build iOS Release # ============================================================ build-ios: name: Build iOS runs-on: macos-latest - needs: [pre-release-checks] + needs: [validate] steps: - name: Checkout code - uses: actions/checkout@v6 + uses: actions/checkout@v4 with: - ref: ${{ github.event.workflow_run.head_branch }} + ref: ${{ needs.validate.outputs.tag }} - name: Setup Flutter uses: subosito/flutter-action@v2 @@ -460,31 +516,50 @@ jobs: channel: 'stable' cache: true + - name: Cache pub dependencies + uses: actions/cache@v4 + with: + path: ~/.pub-cache + key: pub-${{ runner.os }}-${{ hashFiles('pubspec.lock') }} + restore-keys: | + pub-${{ runner.os }}- + + - name: Cache CocoaPods + uses: actions/cache@v4 + with: + path: | + ios/Pods + ~/.cocoapods + key: pods-ios-${{ hashFiles('ios/Podfile.lock') }} + restore-keys: | + pods-ios- + + - name: Download generated code + uses: actions/download-artifact@v4 + with: + name: generated-code + path: lib/ + - name: Get dependencies run: flutter pub get - - name: Generate code (build_runner) - run: dart run build_runner build --delete-conflicting-outputs - - name: Build iOS Release (No Codesign) run: flutter build ios --release --no-codesign --obfuscate --split-debug-info=build/debug-info/ios - name: Create IPA (unsigned) run: | - VERSION="${{ needs.pre-release-checks.outputs.version }}" + VERSION="${{ needs.validate.outputs.version }}" mkdir -p release - # Create Payload directory structure for IPA mkdir -p Payload cp -r build/ios/iphoneos/Runner.app Payload/ - # Create IPA (unsigned) zip -r "release/${{ env.APP_NAME }}-${VERSION}-ios-unsigned.ipa" Payload rm -rf Payload - name: Upload artifacts - uses: actions/upload-artifact@v6 + uses: actions/upload-artifact@v4 with: name: ios-release path: release/ @@ -496,18 +571,18 @@ jobs: create-release: name: Create Release runs-on: ubuntu-latest - needs: [pre-release-checks, build-windows, build-macos, build-linux, build-android, build-ios] + needs: [validate, build-windows, build-macos, build-linux, build-android, build-ios] permissions: contents: write steps: - name: Checkout code - uses: actions/checkout@v6 + uses: actions/checkout@v4 with: - ref: ${{ github.event.workflow_run.head_branch }} + ref: ${{ needs.validate.outputs.tag }} fetch-depth: 0 - name: Download all artifacts - uses: actions/download-artifact@v7 + uses: actions/download-artifact@v4 with: path: artifacts/ @@ -515,7 +590,6 @@ jobs: run: | mkdir -p release - # Copy all artifacts to release directory cp artifacts/windows-release/* release/ 2>/dev/null || true cp artifacts/macos-release/* release/ 2>/dev/null || true cp artifacts/linux-release/* release/ 2>/dev/null || true @@ -530,30 +604,13 @@ jobs: sha256sum * > SHA256SUMS.txt || true cat SHA256SUMS.txt - - name: Generate changelog - id: changelog - run: | - # Get the previous tag - PREV_TAG=$(git describe --tags --abbrev=0 HEAD^ 2>/dev/null || echo "") - - if [ -z "$PREV_TAG" ]; then - echo "No previous tag found, using all commits" - CHANGELOG=$(git log --pretty=format:"- %s (%h)" --no-merges HEAD) - else - echo "Generating changelog from $PREV_TAG to HEAD" - CHANGELOG=$(git log --pretty=format:"- %s (%h)" --no-merges $PREV_TAG..HEAD) - fi - - # Write to file for multiline support - echo "$CHANGELOG" > changelog.txt - - name: Create Release uses: softprops/action-gh-release@v2 with: - name: "${{ env.APP_NAME }} v${{ needs.pre-release-checks.outputs.version }}" - tag_name: ${{ github.event.workflow_run.head_branch }} + name: "${{ env.APP_NAME }} v${{ needs.validate.outputs.version }}" + tag_name: ${{ needs.validate.outputs.tag }} draft: false - prerelease: ${{ contains(github.event.workflow_run.head_branch, '-beta') || contains(github.event.workflow_run.head_branch, '-alpha') || contains(github.event.workflow_run.head_branch, '-rc') }} + prerelease: ${{ contains(needs.validate.outputs.tag, '-beta') || contains(needs.validate.outputs.tag, '-alpha') || contains(needs.validate.outputs.tag, '-rc') }} generate_release_notes: true files: | release/*