Skip to content

feat(cli): support '!' exclusion patterns in bundle.packages.embed [RED-892] [ship] - #1445

Merged
sorccu merged 3 commits into
mainfrom
simo/red-892-bundle-package-exclusions
Aug 24, 2026
Merged

feat(cli): support '!' exclusion patterns in bundle.packages.embed [RED-892] [ship]#1445
sorccu merged 3 commits into
mainfrom
simo/red-892-bundle-package-exclusions

Conversation

@sorccu

@sorccu sorccu commented Aug 24, 2026

Copy link
Copy Markdown
Member

Linear: RED-892

Affected Components

  • CLI
  • Test
  • Docs

What changed

bundle.packages.embed entries may now be prefixed with ! to make them
exclusions. Entries apply in order, so an exclusion only subtracts from what
the entries before it selected:

bundle: { packages: { embed: ['@acme/*', '!@acme/legacy'] } }  // whole scope minus @acme/legacy
bundle: { packages: { embed: ['!@acme/legacy', '@acme/*'] } }  // whole scope — nothing selected yet

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

parseEmbeddedPackageSpec strips a leading ! into EmbeddedPackageSpec.exclude
(before the version split, so !@acme/foo still reads as a scoped name), and a
bare ! is rejected.

In the planner, each entry filters its own matches against the ! entries that
follow 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 test
previously 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 the
filtered 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 exclusions
is 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 off
    aborted the deploy with a spec-version-not-found that named the wrong
    versions as all the lockfile had.
  • ['keep', 'bar@9.9.9', '!bar'] where bar also has a git resolution — the
    version-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 a
    registry 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 entry
reached.

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".

sorccu and others added 3 commits August 25, 2026 04:22
…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>
@sorccu sorccu changed the title feat(cli): support '!' exclusion patterns in bundle.packages.embed [RED-892] feat(cli): support '!' exclusion patterns in bundle.packages.embed [RED-892] [ship] Aug 24, 2026

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Auto-approved: ship/show PR from a same-repo branch.

@sorccu
sorccu merged commit b86a53e into main Aug 24, 2026
29 of 31 checks passed
@sorccu
sorccu deleted the simo/red-892-bundle-package-exclusions branch August 24, 2026 20:15
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