Skip to content

refactor(ci): scope PR jobs to packages changed by the PR#2109

Draft
xsahil03x wants to merge 2 commits into
masterfrom
feat/melos-diff
Draft

refactor(ci): scope PR jobs to packages changed by the PR#2109
xsahil03x wants to merge 2 commits into
masterfrom
feat/melos-diff

Conversation

@xsahil03x
Copy link
Copy Markdown
Member

@xsahil03x xsahil03x commented Feb 13, 2025

Replaces the previous diff-aware-CI attempt on this branch with a version that addresses the bugs we found in the original — silent test failures from the bash &&/|| trap, and example packages leaking back into the affected set via melos's dependent expansion.

What changes

Four new diff-aware melos scripts that scope each job to only the packages a PR touches (plus their transitive dependents):

  • analyze:changesdart analyze on changed packages and their dependents (includes examples; analyzing them catches API drift in the sample code)
  • format:changesdart format --set-exit-if-changed on the same set
  • test:changesflutter test --no-pub --coverage, with a wrapper script that skips packages without a test/ directory
  • lint:pub:changesflutter pub publish --dry-run on publishable changed packages

The workflow's analyze/format/test jobs now call the :changes variants directly. Push events to master use the same scripts — HEAD~1 resolves to the previous tip there, scoping to the merge commit's changes.

Why HEAD~1...HEAD (no env var, no github.base_ref)

GitHub Actions's default checkout for pull_request events checks out an auto-generated merge commit at refs/pull/N/merge. On that commit:

  • HEAD^1 (first parent) = base branch tipmaster, v10.0.0, feat/design-refresh, whatever the PR targets
  • HEAD^2 (second parent) = PR head tip

So HEAD~1...HEAD is always exactly the PR's effective diff, regardless of which branch it targets, with zero workflow plumbing. On push events, HEAD~1 is the previous tip — same diff shape.

Verified empirically:

$ git merge --no-ff feature -m "Merge feature into main"
$ git diff HEAD~1...HEAD --name-only
c
d

Pre-existing files (a, b) correctly excluded; only the feature branch's added files appear.

Why test:changes needs a wrapper script

The melos filter pipeline (packages/melos/lib/src/package.dart:800) runs in this order:

  1. applyIgnore, applyDirExists, applyFileExists
  2. filterPrivatePackages, applyScope, applyCategories
  3. applyDependsOn / applyNoDependsOn
  4. filterNullSafe, filterPublishedPackages
  5. applyDiff
  6. applyIncludeDependentsOrDependencies ← runs LAST, walks _packageMap (the unfiltered workspace)

So --ignore=\"*example*\" and packageFilters: { dirExists: test } correctly filter examples out of the initial set — but --include-dependents then pulls them straight back in from the full workspace. Confirmed locally: a single change to stream_chat with --include-dependents selects 11 packages, 5 of them *_example.

We can't drop --include-dependents (we need to test that a change to stream_chat doesn't break tests in stream_chat_flutter), and we don't want to remove examples from the workspace (they need to participate in analyze/format). So the skip decision happens per-package, after melos has computed the affected set:

# .github/workflows/scripts/test-package.sh
set -e
if [[ \"\$MELOS_PACKAGE_NAME\" == *_example ]]; then exit 0; fi
if [ ! -d test ]; then exit 0; fi
exec flutter test --no-pub --coverage

set -e plus explicit exec ensures real test failures propagate — the original `[ -d test ] && flutter test ... || echo skip` pattern would silently swallow non-zero exits from flutter test (classic bash &&/|| precedence bug).

lint:pub:changes doesn't need a wrapper because pub publish --dry-run only validates this package's own pubspec/files/metadata — never reads the parent's code — so we drop --include-dependents entirely and --no-private filters examples cleanly.

Other workflow cleanups

  • Coverage threshold checks gain if: hashFiles('packages/X/coverage/lcov.info') != '' guards so they skip cleanly when their package wasn't touched (no "file not found" errors)
  • Codecov upload moves to last so we never push coverage that failed local gates
  • Drop the legacy ACTIONS_ALLOW_UNSECURE_COMMANDS: 'true' env var (security smell, deprecated)
  • Kept everything still useful from master: gate job, fastlane Android/iOS build matrix, SQLite3 install for persistence tests, validate-formatting.sh

Local usage

# Default — diffs against HEAD~1 (your last commit)
melos run test:changes
melos run analyze:changes
melos run format:changes
melos run lint:pub:changes

# For \"what does my whole branch touch vs master\", invoke melos exec directly:
melos exec --diff=\"origin/master...HEAD\" --include-dependents -- \"dart analyze --fatal-infos .\"

The full analyze / format / test:all / lint:pub scripts are kept untouched for the local "check everything" use case.

Caveats worth knowing

  • Requires actions/checkout defaults — if anyone later sets ref: \${{ github.event.pull_request.head.sha }}, the HEAD~1 trick stops resolving to the base branch.
  • Requires fetch-depth: 0 (already set everywhere in this workflow) so HEAD~1 resolves on the runner.

Summary by CodeRabbit

  • Chores
    • Optimized CI pipeline to run checks and tests only on changed packages, reducing build time and providing faster feedback on pull requests.
    • Enhanced test coverage validation with improved handling of packages without coverage data.

Review Change Stack

@codecov
Copy link
Copy Markdown

codecov Bot commented Feb 13, 2025

Codecov Report

All modified and coverable lines are covered by tests ✅

Project coverage is 60.57%. Comparing base (07e1a35) to head (a68cdfa).
Report is 22 commits behind head on master.

Additional details and impacted files
@@           Coverage Diff           @@
##           master    #2109   +/-   ##
=======================================
  Coverage   60.57%   60.57%           
=======================================
  Files         383      383           
  Lines       23927    23927           
=======================================
  Hits        14493    14493           
  Misses       9434     9434           

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@github-actions github-actions Bot added the Stale label Mar 5, 2025
@github-actions github-actions Bot removed the Stale label Mar 27, 2025
@github-actions github-actions Bot added Stale and removed Stale labels Apr 16, 2025
Adds `analyze:changes`, `format:changes`, `test:changes`, and
`lint:pub:changes` melos scripts that run only on packages a PR
touches (plus their transitive dependents). The diff range is
`HEAD~1...HEAD`, which works for both PR events (HEAD~1 is the
base-branch tip on GitHub's merge ref, regardless of which branch
the PR targets) and push events (HEAD~1 is the previous tip).

`test:changes` delegates to `.github/workflows/scripts/test-package.sh`
because melos applies packageFilters BEFORE `--include-dependents`,
so examples (no test/ directory, but path-depend on every changed
package) get pulled back into the set via transitive deps and can't
be filtered out by `dirExists`. The wrapper skips them per-package
after the affected set is computed, with explicit exit codes so real
test failures propagate.

Coverage checks gain `hashFiles()` guards so they skip cleanly when
their package wasn't touched on this PR. Codecov upload moves to
last so we never push coverage that failed local thresholds. The
legacy `ACTIONS_ALLOW_UNSECURE_COMMANDS` env var is removed.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@xsahil03x xsahil03x force-pushed the feat/melos-diff branch 2 times, most recently from a68cdfa to 316b915 Compare May 21, 2026 14:10
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 21, 2026

Important

Review skipped

Draft detected.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 06d04e8d-6a4a-4240-9c17-fdef8ac98f69

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/melos-diff

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@xsahil03x xsahil03x changed the title refactor(repo): use diff to only run commands for changes refactor(ci): scope PR jobs to packages changed by the PR May 21, 2026
…cles

- Remove `metrics` + `analyze:all` scripts. dart_code_metrics's OSS
  version was discontinued in 2024 (the maintainer pivoted to commercial
  DCM); the script wasn't actually installed anywhere, so the command
  would have failed if anyone ran it.
- Switch `generate:dart` and `generate:flutter` to `dart run build_runner`.
  `flutter pub run` has been deprecated since Flutter 3.7 (Feb 2023);
  `dart run` works for both Dart and Flutter packages and skips the
  Flutter binary warmup. Also moved both scripts to melos's `exec:`
  shorthand — `packageFilters` is now a first-class sibling key, no
  more `--depends-on=... --no-flutter` flag parsing in a quoted string.
- Add `lint:cycles` script (wraps `melos list --cycles`) and run it
  before bootstrap in the analyze job. Fails fast if anyone introduces
  a circular dep — ~1s to run, saves the full bootstrap cost on a
  doomed PR.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant