This repository was archived by the owner on Aug 5, 2026. It is now read-only.
chore(deps): bump the flutter-dependencies group across 1 directory with 11 updates #2163
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: CI | |
| on: | |
| push: | |
| branches: [master] | |
| pull_request: | |
| branches: [master] | |
| jobs: | |
| check: | |
| name: Check | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 10 | |
| steps: | |
| - name: Checkout code | |
| uses: actions/checkout@v4 | |
| with: | |
| fetch-depth: 1 | |
| - name: Setup Flutter | |
| uses: subosito/flutter-action@v2 | |
| with: | |
| flutter-version: '3.41.4' | |
| channel: 'stable' | |
| cache: true | |
| - name: Cache Flutter dependencies | |
| uses: actions/cache@v4 | |
| with: | |
| path: | | |
| ~/.pub-cache | |
| ${{ github.workspace }}/.pub-cache | |
| key: ${{ runner.os }}-pub-cache-${{ hashFiles('**/pubspec.lock') }} | |
| restore-keys: | | |
| ${{ runner.os }}-pub-cache- | |
| - name: Setup Rust | |
| uses: dtolnay/rust-toolchain@stable | |
| with: | |
| components: rustfmt, clippy | |
| - name: Cache Rust dependencies | |
| uses: actions/cache@v4 | |
| with: | |
| path: | | |
| ~/.cargo/registry | |
| ~/.cargo/git | |
| rust/target | |
| key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.lock') }} | |
| restore-keys: | | |
| ${{ runner.os }}-cargo- | |
| - name: Install dependencies | |
| run: | | |
| flutter pub get | |
| cd widgetbook && flutter pub get | |
| cd ../rust && cargo fetch | |
| - name: Check Rust formatting | |
| run: cd rust && cargo fmt --check | |
| - name: Check Dart formatting | |
| run: dart format --set-exit-if-changed lib/ test/ widgetbook/lib/ | |
| - name: L10n generation & validation | |
| run: | | |
| flutter gen-l10n | |
| chmod +x scripts/validate-locales-keys.sh | |
| ./scripts/validate-locales-keys.sh | |
| - name: Rust linting (Clippy) | |
| run: cd rust && cargo clippy --package rust_lib_whitenoise -- -D warnings | |
| - name: Flutter analyze | |
| run: flutter analyze --fatal-infos | |
| - name: Flutter analyze (widgetbook) | |
| run: cd widgetbook && flutter analyze --fatal-infos | |
| - name: Rust tests | |
| run: cd rust && cargo test | |
| test: | |
| name: Test (${{ matrix.shard }}/4) | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 10 | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| shard: [1, 2, 3, 4] | |
| steps: | |
| - name: Checkout code | |
| uses: actions/checkout@v4 | |
| with: | |
| fetch-depth: 1 | |
| - name: Setup Flutter | |
| uses: subosito/flutter-action@v2 | |
| with: | |
| flutter-version: '3.41.4' | |
| channel: 'stable' | |
| cache: true | |
| - name: Cache Flutter dependencies | |
| uses: actions/cache@v4 | |
| with: | |
| path: | | |
| ~/.pub-cache | |
| ${{ github.workspace }}/.pub-cache | |
| key: ${{ runner.os }}-pub-cache-${{ hashFiles('**/pubspec.lock') }} | |
| restore-keys: | | |
| ${{ runner.os }}-pub-cache- | |
| - name: Install Flutter dependencies | |
| run: flutter pub get | |
| - name: Install lcov (cached) | |
| uses: awalsh128/cache-apt-pkgs-action@v1 | |
| with: | |
| packages: lcov | |
| version: 1.0 | |
| - name: Run Flutter tests (shard ${{ matrix.shard }}) | |
| id: tests | |
| run: | | |
| set +e | |
| SHARD_INDEX=$(( ${{ matrix.shard }} - 1 )) | |
| flutter test --coverage --total-shards 4 --shard-index $SHARD_INDEX --reporter failures-only 2>&1 | tee test-output.txt | |
| EXIT_CODE=${PIPESTATUS[0]} | |
| echo "exit_code=$EXIT_CODE" >> "$GITHUB_OUTPUT" | |
| exit $EXIT_CODE | |
| - name: Upload test failures | |
| if: failure() && steps.tests.outputs.exit_code != '0' | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: test-failures-shard-${{ matrix.shard }} | |
| path: test-output.txt | |
| retention-days: 1 | |
| - name: Upload coverage shard | |
| if: always() | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: coverage-shard-${{ matrix.shard }} | |
| path: coverage/lcov.info | |
| retention-days: 1 | |
| if-no-files-found: ignore | |
| coverage: | |
| name: Coverage | |
| if: always() | |
| needs: test | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 10 | |
| permissions: | |
| actions: read | |
| contents: read | |
| pull-requests: write | |
| steps: | |
| - name: Checkout code | |
| uses: actions/checkout@v4 | |
| with: | |
| fetch-depth: 1 | |
| - name: Download test failure artifacts | |
| id: download_failures | |
| continue-on-error: true | |
| uses: actions/download-artifact@v4 | |
| with: | |
| pattern: test-failures-shard-* | |
| path: test-failures | |
| - name: Comment PR with test failures | |
| if: github.event_name == 'pull_request' && steps.download_failures.outcome == 'success' && github.event.pull_request.head.repo.full_name == github.repository | |
| uses: actions/github-script@v7 | |
| with: | |
| script: | | |
| const fs = require('fs'); | |
| const path = require('path'); | |
| const marker = '<!-- test-failures -->'; | |
| const failuresDir = 'test-failures'; | |
| const sections = []; | |
| if (fs.existsSync(failuresDir)) { | |
| const shardDirs = fs.readdirSync(failuresDir).sort(); | |
| for (const dir of shardDirs) { | |
| const filePath = path.join(failuresDir, dir, 'test-output.txt'); | |
| if (!fs.existsSync(filePath)) continue; | |
| const content = fs.readFileSync(filePath, 'utf8').trim(); | |
| if (!content) continue; | |
| const shardNum = dir.replace('test-failures-shard-', ''); | |
| sections.push( | |
| '<details>', | |
| `<summary>Shard ${shardNum}</summary>`, | |
| '', | |
| '```', | |
| content, | |
| '```', | |
| '', | |
| '</details>', | |
| '' | |
| ); | |
| } | |
| } | |
| const { data: comments } = await github.rest.issues.listComments({ | |
| issue_number: context.issue.number, | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| }); | |
| const existing = comments.find(c => c.body && c.body.includes(marker)); | |
| if (sections.length === 0) { | |
| if (existing) { | |
| await github.rest.issues.deleteComment({ | |
| comment_id: existing.id, | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| }); | |
| } | |
| return; | |
| } | |
| const body = [ | |
| '## :x: Test Failures', | |
| '', | |
| ...sections, | |
| marker, | |
| ].join('\n'); | |
| if (existing) { | |
| await github.rest.issues.updateComment({ | |
| comment_id: existing.id, | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| body: body, | |
| }); | |
| } else { | |
| await github.rest.issues.createComment({ | |
| issue_number: context.issue.number, | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| body: body, | |
| }); | |
| } | |
| - name: Install lcov (cached) | |
| uses: awalsh128/cache-apt-pkgs-action@v1 | |
| with: | |
| packages: lcov | |
| version: 1.0 | |
| - name: Download all coverage shards | |
| uses: actions/download-artifact@v4 | |
| with: | |
| pattern: coverage-shard-* | |
| path: coverage-shards | |
| - name: Merge coverage shards | |
| run: | | |
| mkdir -p coverage | |
| SHARDS=() | |
| for f in coverage-shards/coverage-shard-*/lcov.info; do | |
| [ -f "$f" ] && SHARDS+=("$f") | |
| done | |
| if [ ${#SHARDS[@]} -eq 0 ]; then | |
| echo "No coverage shards found, skipping merge" | |
| touch coverage/lcov.info | |
| exit 0 | |
| fi | |
| ARGS="" | |
| for shard in "${SHARDS[@]}"; do | |
| ARGS="$ARGS --add-tracefile $shard" | |
| done | |
| lcov $ARGS --output-file coverage/lcov.info | |
| echo "Merged ${#SHARDS[@]} shards" | |
| - name: Extract coverage percentage | |
| id: coverage | |
| run: | | |
| chmod +x scripts/check-coverage.sh | |
| COVERAGE=$(./scripts/check-coverage.sh coverage/lcov.info --warnings-file coverage/uncovered-files.txt) | |
| echo "percentage=$COVERAGE" >> "$GITHUB_OUTPUT" | |
| echo "Current coverage: $COVERAGE%" | |
| if [ -f coverage/uncovered-files.txt ] && [ -s coverage/uncovered-files.txt ]; then | |
| COUNT=$(wc -l < coverage/uncovered-files.txt | tr -d ' ') | |
| echo "uncovered_count=$COUNT" >> "$GITHUB_OUTPUT" | |
| { | |
| echo 'uncovered_list<<UNCOVERED_EOF' | |
| cat coverage/uncovered-files.txt | |
| echo 'UNCOVERED_EOF' | |
| } >> "$GITHUB_OUTPUT" | |
| else | |
| echo "uncovered_count=0" >> "$GITHUB_OUTPUT" | |
| echo "uncovered_list=" >> "$GITHUB_OUTPUT" | |
| fi | |
| - name: Generate coverage HTML report | |
| run: genhtml coverage/lcov.info --output-directory coverage/html | |
| - name: Upload coverage report | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: coverage-report | |
| path: | | |
| coverage/html/ | |
| coverage/lcov.info | |
| coverage/uncovered-files.txt | |
| retention-days: 90 | |
| - name: Save coverage baseline | |
| if: github.event_name == 'push' | |
| run: | | |
| mkdir -p coverage-baseline | |
| echo "${{ steps.coverage.outputs.percentage }}" > coverage-baseline/coverage-baseline.txt | |
| - name: Upload coverage baseline | |
| if: github.event_name == 'push' | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: coverage-baseline | |
| path: coverage-baseline/coverage-baseline.txt | |
| retention-days: 90 | |
| - name: Get master branch coverage | |
| if: github.event_name == 'pull_request' | |
| id: master_coverage | |
| run: | | |
| RUN_ID=$(gh run list \ | |
| --repo ${{ github.repository }} \ | |
| --workflow=ci.yml \ | |
| --branch master \ | |
| --status success \ | |
| --limit 1 \ | |
| --json databaseId \ | |
| --jq '.[0].databaseId') | |
| if [ -z "$RUN_ID" ] || [ "$RUN_ID" = "null" ]; then | |
| echo "baseline=0" >> "$GITHUB_OUTPUT" | |
| exit 0 | |
| fi | |
| if gh run download "$RUN_ID" \ | |
| --repo ${{ github.repository }} \ | |
| --name coverage-baseline \ | |
| --dir master-coverage; then | |
| if [ -f master-coverage/coverage-baseline.txt ]; then | |
| echo "baseline=$(cat master-coverage/coverage-baseline.txt)" >> "$GITHUB_OUTPUT" | |
| else | |
| echo "baseline=0" >> "$GITHUB_OUTPUT" | |
| fi | |
| else | |
| echo "baseline=0" >> "$GITHUB_OUTPUT" | |
| fi | |
| env: | |
| GH_TOKEN: ${{ github.token }} | |
| - name: Compute coverage delta | |
| if: github.event_name == 'pull_request' && steps.master_coverage.outputs.baseline != '0' | |
| id: coverage_delta | |
| run: | | |
| CURRENT=${{ steps.coverage.outputs.percentage }} | |
| BASELINE=${{ steps.master_coverage.outputs.baseline }} | |
| DIFF=$(awk "BEGIN {printf \"%.2f\", $CURRENT - $BASELINE}") | |
| if (( $(awk "BEGIN {print ($CURRENT < $BASELINE)}") )); then | |
| STATUS='decreased' | |
| elif (( $(awk "BEGIN {print ($CURRENT > $BASELINE)}") )); then | |
| STATUS='increased' | |
| else | |
| STATUS='same' | |
| fi | |
| echo "current=$CURRENT" >> "$GITHUB_OUTPUT" | |
| echo "baseline=$BASELINE" >> "$GITHUB_OUTPUT" | |
| echo "diff=$DIFF" >> "$GITHUB_OUTPUT" | |
| echo "status=$STATUS" >> "$GITHUB_OUTPUT" | |
| - name: Write coverage step summary | |
| if: github.event_name == 'pull_request' | |
| run: | | |
| if [ "${{ steps.master_coverage.outputs.baseline }}" = "0" ]; then | |
| { | |
| echo "## Coverage" | |
| echo "" | |
| echo "No master baseline found. Current PR coverage: **${{ steps.coverage.outputs.percentage }}%**" | |
| } >> "$GITHUB_STEP_SUMMARY" | |
| exit 0 | |
| fi | |
| CURRENT="${{ steps.coverage_delta.outputs.current }}" | |
| BASELINE="${{ steps.coverage_delta.outputs.baseline }}" | |
| DIFF="${{ steps.coverage_delta.outputs.diff }}" | |
| STATUS="${{ steps.coverage_delta.outputs.status }}" | |
| SIGN="" | |
| [ "$STATUS" = "increased" ] && SIGN="+" | |
| { | |
| echo "## Coverage" | |
| echo "" | |
| echo "| Baseline (master) | PR | Delta |" | |
| echo "| --- | --- | --- |" | |
| echo "| ${BASELINE}% | ${CURRENT}% | ${SIGN}${DIFF}% |" | |
| } >> "$GITHUB_STEP_SUMMARY" | |
| - name: Fail on coverage regression | |
| if: github.event_name == 'pull_request' && steps.master_coverage.outputs.baseline != '0' && steps.coverage_delta.outputs.status == 'decreased' | |
| run: | | |
| echo "::error::Coverage decreased from ${{ steps.coverage_delta.outputs.baseline }}% to ${{ steps.coverage_delta.outputs.current }}% (${{ steps.coverage_delta.outputs.diff }}%)" | |
| exit 1 | |
| - name: Comment PR with coverage | |
| if: github.event_name == 'pull_request' && always() && steps.master_coverage.outputs.baseline != '0' && github.event.pull_request.head.repo.full_name == github.repository | |
| uses: actions/github-script@v7 | |
| env: | |
| COVERAGE_PERCENTAGE: ${{ steps.coverage.outputs.percentage }} | |
| COVERAGE_BASELINE: ${{ steps.master_coverage.outputs.baseline }} | |
| COVERAGE_UNCOVERED_COUNT: ${{ steps.coverage.outputs.uncovered_count }} | |
| COVERAGE_UNCOVERED_LIST: ${{ steps.coverage.outputs.uncovered_list }} | |
| with: | |
| script: | | |
| const current = parseFloat(process.env.COVERAGE_PERCENTAGE); | |
| const baseline = parseFloat(process.env.COVERAGE_BASELINE); | |
| const uncoveredCount = parseInt(process.env.COVERAGE_UNCOVERED_COUNT) || 0; | |
| const uncoveredList = (process.env.COVERAGE_UNCOVERED_LIST || '').trim(); | |
| const rawDiff = current - baseline; | |
| const displayDiff = rawDiff.toFixed(2); | |
| const sha = context.sha.substring(0, 7); | |
| const now = new Date().toISOString().replace('T', ' ').substring(0, 19) + ' UTC'; | |
| const emoji = rawDiff > 0 ? ':white_check_mark:' : rawDiff < 0 ? ':x:' : ':white_check_mark:'; | |
| const sign = rawDiff > 0 ? '+' : ''; | |
| const marker = '<!-- coverage-report -->'; | |
| const historyStart = '<!-- coverage-history -->'; | |
| const historyEnd = '<!-- /coverage-history -->'; | |
| const { data: comments } = await github.rest.issues.listComments({ | |
| issue_number: context.issue.number, | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| }); | |
| const existing = comments.find(c => c.body && c.body.includes(marker)); | |
| let historyEntries = []; | |
| if (existing) { | |
| const hStart = existing.body.indexOf(historyStart); | |
| const hEnd = existing.body.indexOf(historyEnd); | |
| if (hStart !== -1 && hEnd !== -1) { | |
| const historyBlock = existing.body.substring( | |
| hStart + historyStart.length, hEnd | |
| ); | |
| historyEntries = historyBlock | |
| .split('\n') | |
| .filter(line => line.trim().startsWith('- ')); | |
| } | |
| } | |
| const shaEntryPrefix = `- \`${sha}\``; | |
| historyEntries = historyEntries.filter( | |
| line => !line.trim().startsWith(shaEntryPrefix) | |
| ); | |
| historyEntries.push( | |
| `- \`${sha}\` ${now} — **${current}%** (${sign}${displayDiff}% vs base)` | |
| ); | |
| const sections = [ | |
| `## ${emoji} Coverage: ${baseline}% → ${current}% (${sign}${displayDiff}%)`, | |
| '', | |
| ]; | |
| if (uncoveredCount > 0) { | |
| const files = uncoveredList.split('\n').filter(f => f.length > 0); | |
| sections.push( | |
| `### :warning: ${uncoveredCount} file(s) with no test coverage`, | |
| '', | |
| '<details>', | |
| '<summary>Show files</summary>', | |
| '', | |
| ...files.map(f => `- \`${f}\``), | |
| '', | |
| '</details>', | |
| '', | |
| ); | |
| } | |
| sections.push( | |
| '<details>', | |
| '<summary>History</summary>', | |
| '', | |
| historyStart, | |
| ...historyEntries, | |
| historyEnd, | |
| '', | |
| '</details>', | |
| '', | |
| marker, | |
| ); | |
| const body = sections.join('\n'); | |
| if (existing) { | |
| await github.rest.issues.updateComment({ | |
| comment_id: existing.id, | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| body: body, | |
| }); | |
| } else { | |
| await github.rest.issues.createComment({ | |
| issue_number: context.issue.number, | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| body: body, | |
| }); | |
| } | |
| - name: Fail if tests failed | |
| if: always() && needs.test.result != 'success' | |
| run: | | |
| echo "::error::Test shards failed" | |
| exit 1 |