Skip to content

Bump the pub-example group across 1 directory with 3 updates #70

Bump the pub-example group across 1 directory with 3 updates

Bump the pub-example group across 1 directory with 3 updates #70

Workflow file for this run

name: Package checks
on:
pull_request:
branches: [main]
push:
branches: [main]
concurrency:
# Per PR, or per ref on a direct push to main; a newer event cancels the older run.
group: pkg-${{ github.event.pull_request.number || github.ref }}
cancel-in-progress: true
permissions:
contents: read
jobs:
# A push to main that's just a merged PR was already fully checked on that PR, so the jobs
# below skip it; a *direct* push to main (and every PR) still runs them. The push payload can't
# distinguish the two, so ask the API whether the pushed commit belongs to a merged PR —
# strategy-agnostic (works for squash, merge-commit, and rebase merges).
gate:
name: Decide whether to run
runs-on: ubuntu-latest
permissions:
contents: read
pull-requests: read
outputs:
run: ${{ steps.decide.outputs.run }}
steps:
- id: decide
env:
GH_TOKEN: ${{ github.token }}
run: |
if [ "${{ github.event_name }}" != "push" ]; then
echo "run=true" >> "$GITHUB_OUTPUT" # PRs always run
exit 0
fi
# Did the pushed commit land via a merged PR? If so, it's a post-merge push.
merged=$(gh api "repos/${{ github.repository }}/commits/${{ github.sha }}/pulls" \
--jq '[.[] | select(.merged_at != null)] | length' 2>/dev/null || echo 0)
if [ "${merged:-0}" -gt 0 ]; then
echo "run=false" >> "$GITHUB_OUTPUT" # already checked on the PR — skip
else
echo "run=true" >> "$GITHUB_OUTPUT" # direct push to main — run
fi
analyze:
name: Flutter analyze
needs: gate
if: needs.gate.outputs.run == 'true'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v7
- uses: ./.github/actions/setup-flutter
# example/ resolves its own deps and is analyzed by example.yml, so it is not included here.
- run: flutter --no-version-check analyze --fatal-infos lib test pigeons
format:
name: Dart format
needs: gate
if: needs.gate.outputs.run == 'true'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v7
- uses: ./.github/actions/setup-flutter
# Whole repo; enforces analysis_options' formatter.page_width: 100. dart format needs no
# resolved deps, so one root run covers lib/test/pigeons AND example/.
- run: dart format --output=none --set-exit-if-changed .
# DCM (dart_code_metrics) CI job removed for now — pending dcm.dev's free open-source-project
# license (the DCM_CI_KEY / DCM_EMAIL secrets). The DCM rules still live in analysis_options.yaml
# and are applied by hand per CLAUDE.md's definition of done; restore this job from git history
# once the license lands and the secrets are set.
detekt:
name: detekt (Kotlin)
needs: gate
if: needs.gate.outputs.run == 'true'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v7
- uses: actions/setup-java@v5
with:
distribution: temurin
java-version: "17"
# detekt analyses the Kotlin sources directly — no Gradle/Android SDK needed. The config
# layers this project's deviations onto detekt's default ruleset; Messages.g.kt is generated.
- run: |
curl -fsSL -o detekt-cli.jar https://github.com/detekt/detekt/releases/download/v1.23.8/detekt-cli-1.23.8-all.jar
java -jar detekt-cli.jar --build-upon-default-config --config android/detekt.yml --input android/src --excludes '**/Messages.g.kt'
deps:
name: Dependency validator
needs: gate
if: needs.gate.outputs.run == 'true'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v7
- uses: ./.github/actions/setup-flutter
# Published 5.0.5 caps `analyzer` below the 14.1.0 that Dart 3.13 syntax needs to parse. Until
# Workiva/dependency_validator#181 releases, run its branch; merging deletes it and fails here
- run: |
dart pub global activate --source git \
https://github.com/Workiva/dependency_validator \
--git-ref dependabot/pub/major-0f75c2771b
- run: dart pub global run dependency_validator
test:
name: Tests
needs: gate
if: needs.gate.outputs.run == 'true'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v7
- uses: ./.github/actions/setup-flutter
- run: flutter --no-version-check test
# --- Native plugin CI -----------------------------------------------------
# The package's defining guarantee is that iOS links ZERO third-party ML
# libraries (no GoogleMLKit) — APPENDIX.md#no-bundling. The Dart jobs above
# never touch the native sides, so these build the example app per platform.
android-example:
name: Android example build + unit tests
needs: gate
if: needs.gate.outputs.run == 'true'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v7
- uses: actions/setup-java@v5
with:
distribution: temurin
java-version: "17"
# Restores ~/.gradle across runs (AGP, Kotlin, CameraX, ML Kit, Robolectric's android-all
# jars, JUnit/Mockito) — otherwise every run re-downloads the whole dependency graph.
cache: gradle
- uses: ./.github/actions/setup-flutter
with:
working-directory: example
# --release so R8 runs over the Kotlin + ML Kit deps and the Kotlin compiles.
# (Runtime R8 breakage — e.g. missing keep rules in consumer-rules.pro — only
# shows when the app runs, so catching that fully would need an emulator smoke
# test; this guards compile- and shrink-time regressions.)
- run: flutter --no-version-check build apk --release
working-directory: example
# Kotlin unit tests (android/src/test) run in the example's Gradle build, where the Flutter
# Gradle plugin supplies the engine (io.flutter). The build above also regenerates the
# example/android wrapper (not committed). The standalone android/ build can't resolve
# io.flutter in CI — its local.properties / flutter.sdk is gitignored.
- run: ./gradlew :text_sight:testDebugUnitTest
working-directory: example/android
ios-example:
name: iOS build + unit tests + no-bundling
# Runs on PRs and direct pushes to main; the gate skips post-merge pushes — which is what keeps
# redundant re-runs off the ~10x-cost macOS runner (the reason this used to be PR-only).
needs: gate
if: needs.gate.outputs.run == 'true'
runs-on: macos-latest
steps:
- uses: actions/checkout@v7
- uses: ./.github/actions/setup-flutter
with:
working-directory: example
# Full build (no signing) so the Swift compiles and the SPM graph is generated.
- run: flutter --no-version-check build ios --release --no-codesign
working-directory: example
# No-bundling: no ML Kit anywhere under example/ios — sources AND the generated
# SPM manifest (FlutterGeneratedPluginSwiftPackage). grep (not rg) so the
# gitignored generated path is scanned; -I skips built binaries.
- name: Assert no ML Kit on the iOS side
run: |
if grep -rEiI 'mlkit|googlemlkit|mlimage' example/ios; then
echo "::error::ML Kit found under example/ios — no-bundling invariant (APPENDIX.md#no-bundling) broken."
exit 1
fi
# Native XCTest cases (example/ios/RunnerTests, reaching TextSightCamera via @testable import)
# run through the Runner scheme's test action. The build above already wrote Generated.xcconfig.
# Pick the first available iPhone simulator so no device name is hardcoded across runner images
# (the iOS 18 floor is met by the bundled sims).
- name: RunnerTests (iOS simulator)
working-directory: example/ios
run: |
DEVICE_ID=$(xcrun simctl list devices available --json \
| jq -r '[.devices[][] | select(.name | startswith("iPhone"))][0].udid // empty')
if [ -z "$DEVICE_ID" ]; then
echo "::error::No iPhone simulator available on the runner"
exit 1
fi
# -quiet trims the log to warnings + errors; a full xcodebuild test transcript is several
# thousand lines (unlike `flutter build ios` above, which Flutter already prints tersely).
# A failing build/test still prints its error; drop -quiet to see the full transcript.
xcodebuild test -quiet \
-workspace Runner.xcworkspace \
-scheme Runner \
-only-testing:RunnerTests \
-destination "id=$DEVICE_ID" \
CODE_SIGNING_ALLOWED=NO
# SwiftLint over the plugin's Swift (ios/.swiftlint.yml) and the example's RunnerTests
# (example/ios/.swiftlint.yml) — two configs because SwiftLint resolves included/excluded
# relative to each config's own dir, and the trees need different deviations. Folded into this
# macOS job to avoid a second ~10x-cost runner; the portable binary is version-pinned.
- name: SwiftLint
run: |
curl -fsSL -o "$RUNNER_TEMP/swiftlint.zip" https://github.com/realm/SwiftLint/releases/download/0.63.3/portable_swiftlint.zip
unzip -oq "$RUNNER_TEMP/swiftlint.zip" -d "$RUNNER_TEMP/swiftlint"
SL="$RUNNER_TEMP/swiftlint/swiftlint"
# Disjoint trees, so lint them concurrently. Each writes its own log (printed in a
# collapsible group after both finish, to keep concurrent output readable); the step fails
# if EITHER run fails — so all lint errors surface in one run, not just the first config's.
"$SL" lint --strict --config ios/.swiftlint.yml > "$RUNNER_TEMP/sl-plugin.log" 2>&1 & p1=$!
"$SL" lint --strict --config example/ios/.swiftlint.yml > "$RUNNER_TEMP/sl-tests.log" 2>&1 & p2=$!
r1=0; wait "$p1" || r1=$?
r2=0; wait "$p2" || r2=$?
echo "::group::SwiftLint — plugin (ios/.swiftlint.yml)"; cat "$RUNNER_TEMP/sl-plugin.log"; echo "::endgroup::"
echo "::group::SwiftLint — RunnerTests (example/ios/.swiftlint.yml)"; cat "$RUNNER_TEMP/sl-tests.log"; echo "::endgroup::"
[ "$r1" -eq 0 ] && [ "$r2" -eq 0 ]
pigeon-freshness:
name: Pigeon codegen freshness
needs: gate
if: needs.gate.outputs.run == 'true'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v7
- uses: ./.github/actions/setup-flutter
# Regenerate, then format the Dart output (Pigeon emits ~80-col; the gate is
# 100) — the repo's two-step regen — then assert nothing changed.
- run: dart run pigeon --input pigeons/text_sight.dart
- run: dart format lib/src/platform/messages.g.dart
- name: Assert generated code is up to date
run: |
if ! git diff --exit-code; then
echo "::error::Pigeon output is stale — run: dart run pigeon --input pigeons/text_sight.dart && dart format lib/src/platform/messages.g.dart"
exit 1
fi