Affected Package Detection
What you'll learn
- How git-diff-based detection identifies changed packages
- Running affected detection locally and in CI
- Configuring exclusions and output formats
- Using affected results for selective builds and tests
Prerequisites
- An existing Archipelago monorepo (see Monorepo Scaffolding)
- The affected command generated in your monorepo toolkit (see Brick: Affected)
- A git history with at least one commit on your base branch
Step 1: How Affected Detection Works
The affected command compares your current branch against a base branch using git diff. It identifies which files changed, maps those files back to their owning packages, and then resolves the full dependency graph to find all transitively affected packages.
git diff → changed files → owning packages → dependency graph → affected listFor example, if you change a file in packages/core_network/, any package that depends on core_network (directly or transitively) is also considered affected.
Step 2: Run Locally
Detect affected packages against your base branch:
# Compare against main
dart run devtools affected --base main
# Compare against develop
dart run devtools affected --base develop
# Output as JSON for scripting
dart run devtools affected --base main --format jsonExample text output:
Affected packages (3):
- packages/core_network
- packages/feature_auth
- appStep 3: Configure Exclusions
Create affected_config.yaml in your monorepo root to exclude packages that should never trigger downstream rebuilds:
exclude:
- packages/app_debugger_impl
- packages/internal_tools
- devtools
ignore_files:
- "*.md"
- "*.txt"
- ".gitignore"- exclude — Packages to skip entirely (changes in these won't trigger dependents)
- ignore_files — File patterns to ignore when detecting changes
Step 4: Use in CI for Selective Builds
The real power of affected detection is skipping unchanged packages in CI. This can reduce pipeline time from 20+ minutes to under 5 minutes in large monorepos.
jobs:
detect:
runs-on: ubuntu-latest
outputs:
affected: ${{ steps.affected.outputs.packages }}
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- uses: subosito/flutter-action@v2
- name: Detect affected
id: affected
run: |
packages=$(dart run devtools affected --base origin/main --format json)
echo "packages=$packages" >> $GITHUB_OUTPUT
test:
needs: detect
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: subosito/flutter-action@v2
- name: Test affected packages
run: |
for pkg in $(echo '${{ needs.detect.outputs.affected }}' | jq -r '.[]'); do
echo "Testing $pkg..."
(cd "$pkg" && flutter test)
doneStep 5: Understand Transitive Detection
Affected detection follows the dependency graph. Given this structure:
app → feature_auth → core_network → core_commonIf you change core_network, the affected list includes:
core_network(directly changed)feature_auth(depends oncore_network)app(depends onfeature_auth)
But core_common is not affected because it does not depend on core_network.
Key Customization Points
| Customization | Where to Change |
|---|---|
| Base branch for comparison | --base flag (default: main) |
| Output format | --format flag (text, json) |
| Exclude packages | affected_config.yaml — exclude list |
| Ignore file patterns | affected_config.yaml — ignore_files list |
| Dependency resolution depth | dependency_graph.dart — adjust traversal |
Next Steps
- Set up test coverage to measure coverage on affected packages only
- Visualize your dependency graph to understand package relationships