feat(cli): support '!' exclusion patterns in bundle.packages.embed [RED-892] [ship] - #1445
Merged
Merged
Conversation
…ED-892] An entry prefixed with '!' removes the packages it matches from what the entries before it selected, so entries apply in order: ['@acme/*', '!@acme/legacy'] embeds the whole scope except @acme/legacy, while the reverse order embeds the whole scope. Exclusions are applied to an entry's matches before they are resolved, rather than by pruning the finished plan, so the per-entry diagnostics stay in step with what actually ships: an entry never fails over, nor warns about, a package the configuration goes on to exclude. Matching happens at the version-filtered level so that appending an entry's own pin as an exclusion cancels it, and version-less lockfile records (git resolutions, workspace links) can neither silence nor explain a pinned entry, since they match any pin. A configuration whose entries select no packages at all is now reported as a warning, which catches reading '!' as gitignore's implicit "everything except" rather than as a subtraction. Configurations without a '!' entry are unaffected: with no exclusions the filtered and unfiltered match sets are identical, so neither new branch can be reached. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Covers the '!' prefix and the in-order semantics in the config JSDoc and the AI-context Playwright reference: what an exclusion subtracts from, that one removing nothing is a no-op, that silencing an entry also silences skip warnings for packages it matched but did not exclude (with a pointer to the debug channel), and that a configuration selecting no packages is reported as a warning. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The `name matches && (spec is unpinned || versions are equal)` test was spelled out at four points in the embedded-packages planner, once with the operands reversed, and the variant that also accepts a version-less entry was interleaved with it. Both are now named: specMatchesPackage() and specLooselyMatchesPackage(), alongside the existing specMatchesPackageName(). Naming them makes the distinction between the two sets explicit where it matters — a lockfile entry recorded without a version (a git resolution, a workspace link) matches any pin, so it can describe why a pinned entry failed but must not be what silences it. Behaviour is unchanged. The strict predicate implies the loose one, so the strict excluded set can now be filtered straight off the lockfile entries instead of being derived from the loose set; a unit test pins that implication, since the diagnostics rely on it and it is no longer structural. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Linear: RED-892
Affected Components
What changed
bundle.packages.embedentries may now be prefixed with!to make themexclusions. Entries apply in order, so an exclusion only subtracts from what
the entries before it selected:
Wildcards already made it easy to embed a whole scope, but not to embed a scope
except a package or two; the only workaround was enumerating every wanted
package by hand, which drifts as the scope grows.
How
parseEmbeddedPackageSpecstrips a leading!intoEmbeddedPackageSpec.exclude(before the version split, so
!@acme/foostill reads as a scoped name), and abare
!is rejected.In the planner, each entry filters its own matches against the
!entries thatfollow it, rather than the plan being pruned afterwards. That ordering is what
keeps the diagnostics honest: an entry never fails over, nor warns about, a
package the configuration goes on to exclude.
Matching happens at the version-filtered level, through two named predicates
(
specMatchesPackage/specLooselyMatchesPackage) that replace the same testpreviously spelled out at four points. The distinction between them carries real
weight: a lockfile entry recorded without a version — a git resolution, a
workspace link — matches any pin, so it can describe why a pinned entry failed
but must not be what silences it.
Configurations without a
!entry are unaffected: with no exclusions thefiltered and unfiltered match sets are identical, so neither new branch is
reachable.
Notes for the Reviewer
The interesting part is the "this entry now embeds nothing" guard in
materializer.ts, which decides whether an entry left empty by later exclusionsis silent or still an error. Three bugs found in review all lived there, each
covered by a regression test:
['bar@2.0.0', '!bar@2.0.0']— appending an entry's own pin to switch it offaborted the deploy with a
spec-version-not-foundthat named the wrongversions as all the lockfile had.
['keep', 'bar@9.9.9', '!bar']wherebaralso has a git resolution — theversion-less record satisfied the guard, so a mistyped pin was swallowed with
no error and the package silently went missing from the bundle.
['git-dep@1.0.0', '!git-dep']— the accurate "cannot be embedded as aregistry tarball" reason degraded into "does not match any package in the
lockfile".
One deliberate trade-off, documented in the code and both doc surfaces: an entry
emptied by later exclusions also drops the skip warning for un-embeddable
packages it matched but did not exclude. Keeping that warning means keeping the
entry alive into the path where such a match is fatal, which is the first bug
above.
DEBUG='checkly:cli:services:embedded-packages'shows what such an entryreached.
Beyond the ticket: a configuration whose entries select no packages at all is now
a warning, which catches reading
!as gitignore's implicit "everything except".