Affected Dependencies
In a monorepo, a change to one workspace rarely stays there. The affected set is the workspace you touched plus everything that depends on it — the packages whose tests, builds and publishes could break because of your change.
vlt has no affected command. Affected is inherited state, so it is
expressed with Dependency Selector Syntax instead:
:diff() finds what changed,
and :has() walks back up to
what depends on it.
What changed
:diff() matches packages whose
files changed against a git commitish, defaulting to HEAD:
# uncommitted changes$ vlt query ':workspace:diff()'# everything this branch changed$ vlt query ':workspace:diff(main...HEAD)'Use the three-dot form. :diff(<ref>) runs
git diff --name-only <ref>, which compares that ref to your working
tree in both directions — as soon as main moves ahead, everything
changed on main is reported as changed here too. main...HEAD diffs
against the merge base instead, which is what “changed on this branch”
means.
Quote any ref containing a /: :diff("origin/main...HEAD"). Bare,
the selector parser rejects the slash and the query exits non-zero.
git diff also ignores untracked files, so a brand new file only
counts once it is staged or committed.
Why the :workspace prefix
:diff() matches the project root whenever anything in the repo
changed, so a bare :diff(main...HEAD) almost always includes . as
well. :workspace drops it.
The trade-off: a change to root-level files only — tsconfig.json, a
CI workflow, the lockfile — maps to the root package alone, so
:workspace:diff() returns nothing at all for it. A gate built on
:workspace skips the job on those changes.
What depends on what changed
:has() matches packages with a
direct dependency on a match:
$ vlt query ':workspace:has(:diff(main...HEAD))'Nest it to reach further up the graph. Each level of :has() is one
more hop away from the change:
# dependents of the dependents$ vlt query ':workspace:has(:has(:diff(main...HEAD)))'The affected set
Combine both with a comma. Selector lists are unioned, so this is “changed, or depending on something changed”:
$ vlt query ':workspace:diff(main...HEAD), :workspace:has(:diff(main...HEAD))'Given a monorepo where packages/utils changed on your branch:
my-monorepo├── packages/utils/ ← files changed├── packages/core/ depends on utils├── packages/cli/ depends on core└── apps/docs/ depends on nothingthat union selects:
my-monorepo├── packages/utils/ ✅ changed├── packages/core/ ✅ depends on utils├── packages/cli/ depends on core, two hops away└── apps/docs/packages/cli is two hops from the change, so it needs another level
of :has(). Add one selector per hop your graph is deep:
$ vlt query ':workspace:diff(main...HEAD), :workspace:has(:diff(main...HEAD)), :workspace:has(:has(:diff(main...HEAD)))'Running scripts on the affected set
vlt run takes the same selector via --scope, so a CI job can test
only what your branch could have broken:
$ vlt run test --scope=':workspace:diff(main...HEAD), :workspace:has(:diff(main...HEAD)), :workspace:has(:has(:diff(main...HEAD)))'Workspaces without the script are skipped rather than failing:
--scope implies --if-present, as do --workspace,
--workspace-group and --recursive.
--workspace and --workspace-group narrow by path glob or named
group instead of by query, but they are not combinable with --scope
— when --scope is given it wins and both are ignored.
Gating a CI job
--expect-results turns a query into a check: vlt query exits
non-zero when the result count does not match, so a job can bail out
early when nothing relevant changed.
$ vlt query ':workspace:diff("origin/main...HEAD")' --expect-results='>0' \|| echo 'nothing affected, skipping'Keep the ref quoted here. A parse error also exits non-zero, so an
unquoted origin/main would be swallowed by the || and report
“nothing affected” forever.
Results are counted as graph edges, not as packages: one workspace
depended on from several places counts more than once. --view=json
emits those same edges — each entry is an edge with the package under
to — so workspace paths need extracting and deduping:
$ vlt query ':workspace:diff("origin/main...HEAD")' --view=json \| jq -r '.[].to.location' | sort -uSee also
:diff()— match packages by git changes:has()— match packages by their dependencies- Workspaces — defining workspaces and groups
vlt run— running scripts across workspaces