Merge main v1.2.0 into develop #683
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: CI | |
| on: | |
| push: | |
| branches: [develop] | |
| tags: ['v*'] | |
| pull_request: | |
| branches: [main] | |
| workflow_dispatch: | |
| concurrency: | |
| group: ci-${{ github.ref }} | |
| cancel-in-progress: true | |
| env: | |
| FLUTTER_VERSION: '3.41.6' | |
| jobs: | |
| test: | |
| if: "!startsWith(github.ref, 'refs/tags/v')" | |
| strategy: | |
| matrix: | |
| os: [macos-latest, windows-latest, ubuntu-latest] | |
| runs-on: ${{ matrix.os }} | |
| steps: | |
| - uses: actions/checkout@v5 | |
| - uses: subosito/flutter-action@v2 | |
| with: | |
| flutter-version: ${{ env.FLUTTER_VERSION }} | |
| channel: stable | |
| cache: true | |
| - name: Cache pub packages | |
| uses: actions/cache@v5 | |
| with: | |
| path: ~/.pub-cache | |
| key: pub-${{ runner.os }}-${{ hashFiles('pubspec.yaml') }} | |
| restore-keys: pub-${{ runner.os }}- | |
| - name: Install dependencies | |
| run: flutter pub get | |
| - name: Verify Dart formatting | |
| shell: bash | |
| run: tool/check_dart_format.sh | |
| - name: Run code generation | |
| run: dart run build_runner build --delete-conflicting-outputs | |
| - name: Run tests | |
| run: flutter test --dart-define=DB_FILE_NAME=finance_copilot_test.db | |
| integration-test: | |
| if: "!startsWith(github.ref, 'refs/tags/v')" | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| include: | |
| - os: macos-14 | |
| device: macos | |
| # Pinned to windows-2022: windows-latest moved to an MSVC (14.51) | |
| # that removed <experimental/coroutine>, which flutter_inappwebview_windows | |
| # still uses (error C2338/STL1011). 2022's VS toolchain still ships it. | |
| - os: windows-2022 | |
| device: windows | |
| runs-on: ${{ matrix.os }} | |
| timeout-minutes: 35 | |
| steps: | |
| - uses: actions/checkout@v5 | |
| - uses: subosito/flutter-action@v2 | |
| with: | |
| flutter-version: ${{ env.FLUTTER_VERSION }} | |
| channel: stable | |
| cache: true | |
| - name: Cache pub packages | |
| uses: actions/cache@v5 | |
| with: | |
| path: ~/.pub-cache | |
| key: pub-${{ runner.os }}-${{ hashFiles('pubspec.yaml') }} | |
| restore-keys: pub-${{ runner.os }}- | |
| - name: Install dependencies | |
| run: flutter pub get | |
| - name: Run code generation | |
| run: dart run build_runner build --delete-conflicting-outputs | |
| - name: Run integration tests | |
| timeout-minutes: 25 | |
| run: flutter test integration_test/all_tests.dart -d ${{ matrix.device }} --dart-define=DB_FILE_NAME=finance_copilot_test.db --dart-define=SLOW_TESTS=true | |
| - name: Run live data fetch test | |
| if: matrix.device == 'macos' | |
| continue-on-error: true | |
| timeout-minutes: 10 | |
| run: flutter test integration_test/live_data_fetch_test.dart -d macos --dart-define=DB_FILE_NAME=finance_copilot_test.db --dart-define=SLOW_TESTS=true | |
| integration-test-android: | |
| if: "!startsWith(github.ref, 'refs/tags/v')" | |
| runs-on: ubuntu-latest | |
| steps: | |
| - uses: actions/checkout@v5 | |
| - uses: actions/setup-java@v5 | |
| with: | |
| distribution: temurin | |
| java-version: '17' | |
| - name: Enable KVM | |
| run: | | |
| echo 'KERNEL=="kvm", GROUP="kvm", MODE="0666", OPTIONS+="static_node=kvm"' | sudo tee /etc/udev/rules.d/99-kvm4all.rules | |
| sudo udevadm control --reload-rules | |
| sudo udevadm trigger --name-match=kvm | |
| - uses: subosito/flutter-action@v2 | |
| with: | |
| flutter-version: ${{ env.FLUTTER_VERSION }} | |
| channel: stable | |
| cache: true | |
| - name: Cache pub packages | |
| uses: actions/cache@v5 | |
| with: | |
| path: ~/.pub-cache | |
| key: pub-${{ runner.os }}-${{ hashFiles('pubspec.yaml') }} | |
| restore-keys: pub-${{ runner.os }}- | |
| - name: Install dependencies | |
| run: flutter pub get | |
| - name: Run code generation | |
| run: dart run build_runner build --delete-conflicting-outputs | |
| - name: Run Android integration tests | |
| uses: reactivecircus/android-emulator-runner@v2 | |
| continue-on-error: true | |
| with: | |
| api-level: 34 | |
| arch: x86_64 | |
| profile: pixel_6 | |
| # swangle_indirect (ANGLE software renderer) instead of | |
| # swiftshader_indirect: the latest emulator engine the runner installs | |
| # regressed SwiftShader with "Failed to find ColorBuffer" errors that | |
| # broke the heavy import UI test (full_walkthrough). SwANGLE is | |
| # Google's recommended headless CI renderer and avoids that path. | |
| emulator-options: -no-snapshot-save -no-window -gpu swangle_indirect -noaudio -no-boot-anim | |
| emulator-boot-timeout: 300 | |
| script: flutter test integration_test/all_tests.dart --dart-define=DB_FILE_NAME=finance_copilot_test.db --dart-define=SLOW_TESTS=true 2>&1 | tee /tmp/android_test_output.txt; grep -qE '(All tests passed|tests passed\.)' /tmp/android_test_output.txt && touch /tmp/android_tests_passed; true | |
| - name: Fail if tests did not pass | |
| run: | | |
| if [ ! -f /tmp/android_tests_passed ]; then | |
| echo "::error::Android integration tests failed or emulator did not boot" | |
| exit 1 | |
| fi | |
| build: | |
| needs: [test] | |
| if: always() && !failure() && !cancelled() && (startsWith(github.ref, 'refs/tags/v') || github.ref == 'refs/heads/develop' || github.event_name == 'workflow_dispatch') | |
| strategy: | |
| matrix: | |
| include: | |
| - os: macos-latest | |
| platform: macos | |
| artifact: FinanceCopilot-macos | |
| # Pinned to windows-2022 (see integration-test note): avoids the | |
| # MSVC 14.51 <experimental/coroutine> removal that breaks | |
| # flutter_inappwebview_windows on windows-latest. | |
| - os: windows-2022 | |
| platform: windows | |
| artifact: FinanceCopilot-windows | |
| - os: ubuntu-latest | |
| platform: apk | |
| artifact: FinanceCopilot-android | |
| runs-on: ${{ matrix.os }} | |
| steps: | |
| - uses: actions/checkout@v5 | |
| - uses: subosito/flutter-action@v2 | |
| with: | |
| flutter-version: ${{ env.FLUTTER_VERSION }} | |
| channel: stable | |
| cache: true | |
| - name: Cache pub packages | |
| uses: actions/cache@v5 | |
| with: | |
| path: ~/.pub-cache | |
| key: pub-${{ runner.os }}-${{ hashFiles('pubspec.yaml') }} | |
| restore-keys: pub-${{ runner.os }}- | |
| - name: Install dependencies | |
| run: flutter pub get | |
| - name: Run code generation | |
| run: dart run build_runner build --delete-conflicting-outputs | |
| - name: Determine channel and stamp version | |
| id: channel | |
| shell: bash | |
| run: | | |
| if [[ "${{ github.ref }}" == refs/tags/v* ]]; then | |
| VERSION="${GITHUB_REF#refs/tags/v}" | |
| echo "channel=stable" >> "$GITHUB_OUTPUT" | |
| else | |
| VERSION="0.0.0-${GITHUB_SHA::7}" | |
| echo "channel=nightly" >> "$GITHUB_OUTPUT" | |
| fi | |
| # Android versionName: strip any pre-release suffix (e.g. -dev / -sha) | |
| # so it is a clean dotted version; versionCode must be a strictly | |
| # increasing integer, so derive it from the run number. | |
| BUILD_NAME="${VERSION%%-*}" | |
| BUILD_NUMBER="${{ github.run_number }}" | |
| echo "build_name=$BUILD_NAME" >> "$GITHUB_OUTPUT" | |
| echo "build_number=$BUILD_NUMBER" >> "$GITHUB_OUTPUT" | |
| # Rewrite the appVersion constant in-place (not committed) | |
| python3 -c " | |
| import re, pathlib | |
| p = pathlib.Path('lib/version.dart') | |
| s = p.read_text() | |
| s = re.sub(r\"const appVersion = '[^']*';\", f\"const appVersion = '${VERSION}';\", s) | |
| p.write_text(s) | |
| " | |
| - name: Set up Java (Android only) | |
| if: matrix.platform == 'apk' | |
| uses: actions/setup-java@v5 | |
| with: | |
| distribution: temurin | |
| java-version: '17' | |
| - name: Decode Android keystore | |
| if: matrix.platform == 'apk' | |
| shell: bash | |
| run: echo "${{ secrets.ANDROID_KEYSTORE }}" | base64 --decode > android/app/financecopilot-release.keystore | |
| env: | |
| ANDROID_KEYSTORE_PASSWORD: ${{ secrets.ANDROID_KEYSTORE_PASSWORD }} | |
| ANDROID_KEY_ALIAS: ${{ secrets.ANDROID_KEY_ALIAS }} | |
| ANDROID_KEY_PASSWORD: ${{ secrets.ANDROID_KEY_PASSWORD }} | |
| - name: Build release | |
| shell: bash | |
| env: | |
| ANDROID_KEYSTORE_PASSWORD: ${{ secrets.ANDROID_KEYSTORE_PASSWORD }} | |
| ANDROID_KEY_ALIAS: ${{ secrets.ANDROID_KEY_ALIAS }} | |
| ANDROID_KEY_PASSWORD: ${{ secrets.ANDROID_KEY_PASSWORD }} | |
| run: | | |
| sed -i'' -e '/integration_test\/fixtures/d' pubspec.yaml | |
| flutter build ${{ matrix.platform }} --release \ | |
| --build-name=${{ steps.channel.outputs.build_name }} \ | |
| --build-number=${{ steps.channel.outputs.build_number }} \ | |
| --dart-define=COMMIT_SHA=${{ github.sha }} \ | |
| --dart-define=CHANNEL=${{ steps.channel.outputs.channel }} \ | |
| --dart-define=DB_FILE_NAME=finance_copilot.db \ | |
| --dart-define=GOOGLE_CLIENT_ID=${{ secrets.GOOGLE_CLIENT_ID }} \ | |
| --dart-define=GOOGLE_CLIENT_SECRET=${{ secrets.GOOGLE_CLIENT_SECRET }} \ | |
| --dart-define=GOOGLE_WEB_CLIENT_ID=${{ secrets.GOOGLE_WEB_CLIENT_ID }} \ | |
| --dart-define=GOOGLE_ANDROID_CLIENT_ID=${{ secrets.GOOGLE_ANDROID_CLIENT_ID }} | |
| - name: Ad-hoc sign macOS build | |
| if: matrix.platform == 'macos' | |
| run: codesign --force --deep --sign - --entitlements macos/Runner/Release.entitlements build/macos/Build/Products/Release/FinanceCopilot.app | |
| - name: Verify entitlements | |
| if: matrix.platform == 'macos' | |
| run: | | |
| ENTITLEMENTS=$(codesign -d --entitlements - build/macos/Build/Products/Release/FinanceCopilot.app 2>&1) | |
| echo "$ENTITLEMENTS" | |
| echo "$ENTITLEMENTS" | grep -q "user-selected" || (echo "ERROR: file access entitlement missing from signed app" && exit 1) | |
| - name: Strip quarantine attributes | |
| if: matrix.platform == 'macos' | |
| run: xattr -crs build/macos/Build/Products/Release/FinanceCopilot.app | |
| - name: Create macOS DMG | |
| if: matrix.platform == 'macos' | |
| run: | | |
| brew install create-dmg | |
| create-dmg \ | |
| --volname "FinanceCopilot" \ | |
| --window-pos 200 120 \ | |
| --window-size 600 400 \ | |
| --icon-size 100 \ | |
| --icon "FinanceCopilot.app" 150 190 \ | |
| --app-drop-link 450 190 \ | |
| build/FinanceCopilot-macos.dmg \ | |
| build/macos/Build/Products/Release/FinanceCopilot.app | |
| xattr -d com.apple.quarantine build/FinanceCopilot-macos.dmg 2>/dev/null || true | |
| - name: Upload artifact (macOS) | |
| if: matrix.platform == 'macos' | |
| uses: actions/upload-artifact@v5 | |
| with: | |
| name: ${{ matrix.artifact }} | |
| path: build/FinanceCopilot-macos.dmg | |
| - name: Upload artifact (Windows) | |
| if: matrix.platform == 'windows' | |
| uses: actions/upload-artifact@v5 | |
| with: | |
| name: ${{ matrix.artifact }} | |
| path: build/windows/x64/runner/Release/ | |
| - name: Upload artifact (Android) | |
| if: matrix.platform == 'apk' | |
| uses: actions/upload-artifact@v5 | |
| with: | |
| name: ${{ matrix.artifact }} | |
| path: build/app/outputs/flutter-apk/app-release.apk | |
| release: | |
| needs: [build] | |
| # Tests are skipped on tag pushes — they already ran on the PR that | |
| # merged this commit into main, gated by branch protection. | |
| if: always() && needs.build.result == 'success' && startsWith(github.ref, 'refs/tags/v') | |
| runs-on: ubuntu-latest | |
| permissions: | |
| contents: write | |
| steps: | |
| - name: Download all artifacts | |
| uses: actions/download-artifact@v5 | |
| with: | |
| path: artifacts | |
| - name: Prepare Windows zip | |
| run: | | |
| cd artifacts/FinanceCopilot-windows | |
| zip -r ../FinanceCopilot-windows.zip . | |
| - name: Rename Android APK | |
| run: cp artifacts/FinanceCopilot-android/app-release.apk artifacts/FinanceCopilot-android.apk | |
| - name: Create GitHub Release | |
| uses: softprops/action-gh-release@v2 | |
| with: | |
| generate_release_notes: true | |
| files: | | |
| artifacts/FinanceCopilot-macos/FinanceCopilot-macos.dmg | |
| artifacts/FinanceCopilot-windows.zip | |
| artifacts/FinanceCopilot-android.apk | |
| update-tap: | |
| needs: release | |
| # Skip pre-release tags (anything with a hyphen, e.g. v0.7.1-rc1) so | |
| # they don't reach the Homebrew stable tap. | |
| if: always() && needs.release.result == 'success' && startsWith(github.ref, 'refs/tags/v') && !contains(github.ref_name, '-') | |
| runs-on: ubuntu-latest | |
| steps: | |
| - name: Update Homebrew cask version | |
| env: | |
| GH_TOKEN: ${{ secrets.HOMEBREW_TAP_TOKEN }} | |
| run: | | |
| VERSION="${GITHUB_REF#refs/tags/v}" | |
| git clone https://x-access-token:${GH_TOKEN}@github.com/marcobazzani/homebrew-financecopilot.git tap | |
| cd tap | |
| sed -i "s/version \".*\"/version \"${VERSION}\"/" Casks/financecopilot.rb | |
| git config user.name "github-actions[bot]" | |
| git config user.email "github-actions[bot]@users.noreply.github.com" | |
| git add Casks/financecopilot.rb | |
| git diff --cached --quiet && echo "Already up to date" && exit 0 | |
| git commit -m "Update FinanceCopilot to ${VERSION}" | |
| git push | |
| latest: | |
| needs: [test, integration-test, integration-test-android, build] | |
| if: always() && needs.test.result == 'success' && needs.integration-test.result == 'success' && needs.integration-test-android.result == 'success' && needs.build.result == 'success' && github.ref == 'refs/heads/develop' | |
| runs-on: ubuntu-latest | |
| permissions: | |
| contents: write | |
| steps: | |
| - uses: actions/checkout@v5 | |
| - name: Download all artifacts | |
| uses: actions/download-artifact@v5 | |
| with: | |
| path: artifacts | |
| - name: Prepare Windows zip | |
| run: | | |
| cd artifacts/FinanceCopilot-windows | |
| zip -r ../FinanceCopilot-windows.zip . | |
| - name: Delete existing latest release | |
| run: gh release delete latest --yes --cleanup-tag || true | |
| env: | |
| GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| - name: Rename Android APK | |
| run: cp artifacts/FinanceCopilot-android/app-release.apk artifacts/FinanceCopilot-android.apk | |
| - name: Create latest release | |
| env: | |
| GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| run: | | |
| gh release create latest \ | |
| --prerelease \ | |
| --draft=false \ | |
| --title "Nightly Build" \ | |
| --notes "Automated nightly build from develop. Commit: ${{ github.sha }}" \ | |
| artifacts/FinanceCopilot-macos/FinanceCopilot-macos.dmg \ | |
| artifacts/FinanceCopilot-windows.zip \ | |
| artifacts/FinanceCopilot-android.apk | |
| update-tap-nightly: | |
| needs: latest | |
| if: always() && needs.latest.result == 'success' && github.ref == 'refs/heads/develop' | |
| runs-on: ubuntu-latest | |
| steps: | |
| - name: Update nightly Homebrew cask | |
| env: | |
| GH_TOKEN: ${{ secrets.HOMEBREW_TAP_TOKEN }} | |
| run: | | |
| SHA="${{ github.sha }}" | |
| VERSION="nightly-${SHA:0:7}" | |
| git clone https://x-access-token:${GH_TOKEN}@github.com/marcobazzani/homebrew-financecopilot.git tap | |
| cd tap | |
| sed -i "s/version \".*\"/version \"${VERSION}\"/" Casks/financecopilot-nightly.rb | |
| git config user.name "github-actions[bot]" | |
| git config user.email "github-actions[bot]@users.noreply.github.com" | |
| git add Casks/financecopilot-nightly.rb | |
| git diff --cached --quiet && echo "Already up to date" && exit 0 | |
| git commit -m "Update nightly to ${VERSION}" | |
| git push |