Skip to content
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

chore(deps): bump the flutter-dependencies group across 1 directory with 11 updates

chore(deps): bump the flutter-dependencies group across 1 directory with 11 updates #2163

Workflow file for this run

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