shorebird_ci
CI tooling for Dart and Flutter monorepos. Scans your repo, generates a GitHub Actions workflow, and verifies that every package has coverage. Designed to be used by both humans and AI agents.
Install
dart pub global activate shorebird_ci
Or from a local checkout:
dart pub global activate --source path packages/shorebird_ci
Commands
| Command | Used by | Purpose |
|---|---|---|
generate |
human / Claude at setup | Write .github/workflows/shorebird_ci.yaml |
verify |
human / Claude for health checks | Fail if any package is missing CI coverage |
affected_packages |
CI runtime (dynamic workflows) | Emit JSON [{name, path, ...}] for affected packages |
flutter_version |
CI runtime (Flutter packages) | Resolve Flutter version from pubspec (see note below) |
update_actions |
human / Claude for maintenance | Bump uses: versions in workflow files to latest major |
Using it
shorebird_ci generate --repo-root . --dry-run # review
shorebird_ci generate --repo-root . # write
The tool auto-detects Dart vs. Flutter, Dart workspaces, codecov,
cspell, nested subpackages, bloc_lint, integration tests, and pinned
Flutter versions. The generated workflow includes shorebird_ci verify
in its setup job, so CI coverage is checked on every PR.
generate also writes .github/dependabot.yml (if missing) so action
versions stay current over time.
How the generated workflow works
Two-stage structure. A setup job checks out the code, runs
shorebird_ci verify as a sanity check, then runs
shorebird_ci affected_packages to compute which packages the PR
touches (including transitive dependents via the Dart dep graph). A
matrix job fans out over only the affected packages, running per
entry: checkout, SDK setup, pub get (plus nested subpackages), format,
analyze, bloc lint (if bloc_lint is a dep), tests (with coverage if
codecov is configured), integration tests (if Flutter + integration_test/
exists), codecov upload.
Plus a CSpell job if a cspell config file exists.
Adding or removing packages requires no workflow changes — the setup job discovers them at runtime.
Manual runs and the empty-diff case
The workflow includes a workflow_dispatch: trigger so you can launch
a run from the Run workflow button in the Actions tab. Manual runs
bypass the affected-packages diff and execute CI against every
package, which is what you want when:
- You just pushed an initial commit to
mainand the diff vs.origin/mainis empty. - You want to force a full re-check after editing CI configuration.
- Something looks off and you want a baseline green run.
For normal push: main events where the diff is empty, setup emits a
GitHub notice pointing at the manual button so a green-but-skipped run
isn't confused for a full pass.
--no-update-actions
generate auto-bumps action pins by querying GitHub for current
latest majors. --no-update-actions skips that network call and
leaves the static pins in the template as-is. Use it in offline
environments. Once you push, Dependabot picks up bumps on its weekly
schedule.
--style static (advanced)
generate --style static emits a pre-computed dorny filters: block,
not a full workflow. You paste it into your own workflow, wire up the
dorny step and per-package jobs yourself, and run verify to catch
path drift. This is for people who already have a dorny-based pipeline
and want help keeping the filter paths in sync with the Dart dep
graph — not a drop-in alternative to the default.
The dynamic default pays a small setup cost per PR — roughly 15–30
seconds to check out, install the Dart SDK, pub global activate
shorebird_ci, and run affected_packages. That cost can probably be
cut a lot in the future (prebuilt snapshot, a composite action), but
it's what you pay today. For most repos it's noise. If you have a
high-volume monorepo where most PRs don't touch Dart, static lets the
workflow skip entirely at the trigger level.
A note on subpackage double-coverage
Subpackages of a Flutter root get CI'd twice: once in their own
matrix job, once inside the root's job. Intentional. The root needs
them for pub get, and the standalone job gives focused per-package
pass/fail. Cost is a duplicate analyze/test on affected PRs.
For AI agents
This tool handles the deterministic parts (package discovery, dep graph, workflow generation). You handle the judgment calls — merging with existing workflows, naming, resolving conflicts.
If the repo already has .github/workflows/*.yaml, read them before
generating. Look for existing Dart CI that would be superseded,
duplicate job names, overlapping path filters. Ask the user whether
to replace existing CI or run alongside.
Watch for: custom runner requirements (self-hosted, ARM, etc.).
Generated workflow defaults to ubuntu-latest.
When verify reports missing packages (only possible in repos
using static dorny filters): the tool outputs the exact dorny entry
with transitive deps computed. You decide which workflow file and
which filter group to add it to — read the existing structure and
make the call.
--ignore: for packages that intentionally have no CI (e.g.,
e2e test packages): shorebird_ci verify --ignore e2e.
Generated file is safe to edit. It's a normal YAML file, not a
locked artifact. The tool's ongoing role is verify, not
regeneration.
A note on flutter_version
This command exists because subosito/flutter-action accepts only
exact version strings — it can't resolve constraints like
>=3.19.0 <4.0.0 from environment.flutter. This arguably belongs
upstream. If flutter-action (or the Flutter SDK) ships equivalent
resolution, this command should be deprecated.