Skip to content

Merge main v1.2.0 into develop #683

Merge main v1.2.0 into develop

Merge main v1.2.0 into develop #683

Workflow file for this run

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