feat: exclude stacks from the selection with ! patterns - #1920
feat: exclude stacks from the selection with ! patterns#1920badmintoncryer wants to merge 7 commits into
! patterns#1920Conversation
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## main #1920 +/- ##
=======================================
Coverage 91.33% 91.33%
=======================================
Files 79 79
Lines 12164 12164
Branches 1721 1721
=======================================
Hits 11110 11110
Misses 1019 1019
Partials 35 35
Flags with carried forward coverage won't be shown. Click here to find out more. ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
8b81835 to
b8146c7
Compare
b8146c7 to
644a5b2
Compare
644a5b2 to
b2fc522
Compare
b2fc522 to
32e71ee
Compare
32e71ee to
735e910
Compare
Stack patterns are matched with picomatch, which reads a leading `!` as a negation. Every pattern was matched on its own and the results unioned, so `!A !B` asked for "not A" and "not B" and got back the union of the two, which is every stack there is. Patterns are now split into the ones that select and the ones that exclude: the selection is the union of the former, minus everything the latter match. picomatch's own `scan()` draws the line, so `!(A|B)` stays the extglob it is and `!!A` stays a positive.
735e910 to
e94875c
Compare
!! patterns
mrgrain
left a comment
There was a problem hiding this comment.
Hmm. Not as easy as I hoped.
| $ cdk deploy 'PipelineStack/Prod/**' '!PipelineStack/Prod/Canary' | ||
| ``` | ||
|
|
||
| The selection is the union of the other patterns, minus everything the exclusions match; `!(...)` is extglob syntax, not an exclusion. When you only pass exclusions, they apply to every stack in the app. Note that `--all` cannot be combined with patterns, so use `**` when you want to spell out the starting point. Stacks that a selected stack depends on are still added to the deployment even if you excluded them; pass `--exclusively` (`-e`) to keep them out. |
There was a problem hiding this comment.
"!(...) is extglob syntax, not an exclusion." this is probably correct, but not sure what it is supposed to mean 😅
"When you only pass exclusions, they apply to every stack in the app." similar here.
But don't worry about it. I'll adjust this before me merge.
There was a problem hiding this comment.
Thank you so much...
e94875c to
f4cfbb5
Compare
f4cfbb5 to
1535c67
Compare
1535c67 to
ea350e8
Compare
Negation status now comes from parse().negated, the flag matching itself uses, instead of counting `!`s in the scan() prefix. Every pattern is compiled raw - a negated matcher accepts the stacks that survive it - so nothing is stripped any more. Slicing the prefix off broke on a bare `!`, which left an empty pattern that picomatch refuses to compile.
ea350e8 to
0aedbf0
Compare
mrgrain
left a comment
There was a problem hiding this comment.
some small performance optimizations please, otherwise great!
| * stack but that one. No patterns at all still selects nothing. | ||
| */ | ||
| function matcherFor(patterns: string[]): (hierarchicalId: string) => boolean { | ||
| const matchers = patterns.map(pattern => ({ negates: picomatch.parse(pattern).negated, matches: picomatch(pattern) })); |
There was a problem hiding this comment.
looks like picomatch(pattern, undefined, true).state is the same result as picomatch.parse(pattern) so we can save a parse here.
…tion-exclusion-patterns
| const positives: picomatch.Matcher[] = []; | ||
| const negatives: picomatch.Matcher[] = []; | ||
| for (const pattern of patterns) { | ||
| (picomatch.parse(pattern).negated ? negatives : positives).push(picomatch(pattern)); |
There was a problem hiding this comment.
| (picomatch.parse(pattern).negated ? negatives : positives).push(picomatch(pattern)); | |
| const parsed = picomatch(pattern, undefined, true); | |
| (parsed.state.negated ? negatives : positives).push(parsed); |
I think this saves a parse?
Fixes #1908
Resolves #758
Implements @mrgrain's snippet from #1909. (That PR could not be reopened after the force push, so this is a new one.)
picomatch.parse().negatedsplits the patterns into positives and negations, and the selection is OR over the positives, AND over the negations. Every pattern is compiled exactly as picomatch defines it - a negated matcher accepts the stacks that survive it - so nothing is ever stripped or rewritten, and!(A|B)stays an extglob.Two changes from the snippet:
scan().negatedreportstruefor!!Stackwhere picomatch matches it as a positive, and slicing the!prefix off turns'!'into an empty pattern that picomatch refuses to compile.parse().negatedis the flag the matcher itself uses, and compiling the raw pattern sidesteps the rest.positives.length === 0meaning "everything" would makepatterns: []select every stack, whichselect behavior: noneincloud-assembly.test.tspins down. Now it needs at least one negation.Also:
!are unchanged and no existing test needed changes. The new selection is always a subset of the old one, so the change only ever narrows: anyone leaning on the union bug, e.g.'Prod/*' '!Prod/Canary', will stop deployingCanary. That is the point of the fix, but say the word if you'd rather have it warn for a release first.does not existwarning compared each raw pattern against the selection it produced. With subtraction in play that turns false ('Stack1' '!Stack1'would warn thatStack1does not exist), so patterns are now checked against the assembly, negations with the!off - which also getsdestroy '!stack1'its casing suggestion.--exclusivelystill does. Test + README.toolkit-lib only, so
deploy/diff/synth/destroy/lsall get this.Checklist
By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license