@@ -5,9 +5,10 @@ const { minimatch } = require('minimatch')
55const rpj = require ( 'read-package-json-fast' )
66const { glob } = require ( 'glob' )
77
8- function appendNegatedPatterns ( patterns ) {
9- const results = [ ]
10- for ( let pattern of patterns ) {
8+ function appendNegatedPatterns ( allPatterns ) {
9+ const patterns = [ ]
10+ const negatedPatterns = [ ]
11+ for ( let pattern of allPatterns ) {
1112 const excl = pattern . match ( / ^ ! + / )
1213 if ( excl ) {
1314 pattern = pattern . slice ( excl [ 0 ] . length )
@@ -18,10 +19,35 @@ function appendNegatedPatterns (patterns) {
1819
1920 // an odd number of ! means a negated pattern. !!foo ==> foo
2021 const negate = excl && excl [ 0 ] . length % 2 === 1
21- results . push ( { pattern, negate } )
22+ if ( negate ) {
23+ negatedPatterns . push ( pattern )
24+ } else {
25+ // remove negated patterns that appeared before this pattern to avoid
26+ // ignoring paths that were matched afterwards
27+ // e.g: ['packages/**', '!packages/b/**', 'packages/b/a']
28+ // in the above list, the last pattern overrides the negated pattern
29+ // right before it. In effect, the above list would become:
30+ // ['packages/**', 'packages/b/a']
31+ // The order matters here which is why we must do it inside the loop
32+ // as opposed to doing it all together at the end.
33+ for ( let i = 0 ; i < negatedPatterns . length ; ++ i ) {
34+ const negatedPattern = negatedPatterns [ i ]
35+ if ( minimatch ( pattern , negatedPattern ) ) {
36+ negatedPatterns . splice ( i , 1 )
37+ }
38+ }
39+ patterns . push ( pattern )
40+ }
2241 }
2342
24- return results
43+ // use the negated patterns to eagerly remove all the patterns that
44+ // can be removed to avoid unnecessary crawling
45+ for ( const negated of negatedPatterns ) {
46+ for ( const pattern of minimatch . match ( patterns , negated ) ) {
47+ patterns . splice ( patterns . indexOf ( pattern ) , 1 )
48+ }
49+ }
50+ return { patterns, negatedPatterns }
2551}
2652
2753function getPatterns ( workspaces ) {
@@ -77,64 +103,66 @@ async function mapWorkspaces (opts = {}) {
77103 }
78104
79105 const { workspaces = [ ] } = opts . pkg
80- const patterns = getPatterns ( workspaces )
106+ const { patterns, negatedPatterns } = getPatterns ( workspaces )
81107 const results = new Map ( )
82108 const seen = new Map ( )
83109
84- if ( ! patterns . length ) {
110+ if ( ! patterns . length && ! negatedPatterns . length ) {
85111 return results
86112 }
87113
88114 const getGlobOpts = ( ) => ( {
89115 ...opts ,
90116 ignore : [
91117 ...opts . ignore || [ ] ,
92- ...[ '**/node_modules/**' ] ,
118+ '**/node_modules/**' ,
119+ // just ignore the negated patterns to avoid unnecessary crawling
120+ ...negatedPatterns ,
93121 ] ,
94122 } )
95123
96124 const getPackagePathname = pkgPathmame ( opts )
97125
98- for ( const item of patterns ) {
99- let matches = await glob ( getGlobPattern ( item . pattern ) , getGlobOpts ( ) )
100- // preserves glob@8 behavior
101- matches = matches . sort ( ( a , b ) => a . localeCompare ( b , 'en' ) )
102-
103- for ( const match of matches ) {
104- let pkg
105- const packageJsonPathname = getPackagePathname ( match , 'package.json' )
106- const packagePathname = path . dirname ( packageJsonPathname )
107-
108- try {
109- pkg = await rpj ( packageJsonPathname )
110- } catch ( err ) {
111- if ( err . code === 'ENOENT' ) {
112- continue
113- } else {
114- throw err
115- }
116- }
126+ let matches = await glob ( patterns . map ( ( p ) => getGlobPattern ( p ) ) , getGlobOpts ( ) )
127+ // preserves glob@8 behavior
128+ matches = matches . sort ( ( a , b ) => a . localeCompare ( b , 'en' ) )
129+
130+ // we must preserve the order of results according to the given list of
131+ // workspace patterns
132+ const orderedMatches = [ ]
133+ for ( const pattern of patterns ) {
134+ orderedMatches . push ( ...matches . filter ( ( m ) => {
135+ return minimatch ( m , pattern , { partial : true , windowsPathsNoEscape : true } )
136+ } ) )
137+ }
117138
118- const name = getPackageName ( pkg , packagePathname )
139+ for ( const match of orderedMatches ) {
140+ let pkg
141+ const packageJsonPathname = getPackagePathname ( match , 'package.json' )
119142
120- let seenPackagePathnames = seen . get ( name )
121- if ( ! seenPackagePathnames ) {
122- seenPackagePathnames = new Set ( )
123- seen . set ( name , seenPackagePathnames )
124- }
125- if ( item . negate ) {
126- seenPackagePathnames . delete ( packagePathname )
143+ try {
144+ pkg = await rpj ( packageJsonPathname )
145+ } catch ( err ) {
146+ if ( err . code === 'ENOENT' ) {
147+ continue
127148 } else {
128- seenPackagePathnames . add ( packagePathname )
149+ throw err
129150 }
130151 }
152+
153+ const packagePathname = path . dirname ( packageJsonPathname )
154+ const name = getPackageName ( pkg , packagePathname )
155+
156+ let seenPackagePathnames = seen . get ( name )
157+ if ( ! seenPackagePathnames ) {
158+ seenPackagePathnames = new Set ( )
159+ seen . set ( name , seenPackagePathnames )
160+ }
161+ seenPackagePathnames . add ( packagePathname )
131162 }
132163
133164 const errorMessageArray = [ 'must not have multiple workspaces with the same name' ]
134165 for ( const [ packageName , seenPackagePathnames ] of seen ) {
135- if ( seenPackagePathnames . size === 0 ) {
136- continue
137- }
138166 if ( seenPackagePathnames . size > 1 ) {
139167 addDuplicateErrorMessages ( errorMessageArray , packageName , seenPackagePathnames )
140168 } else {
@@ -177,30 +205,25 @@ mapWorkspaces.virtual = function (opts = {}) {
177205 const { workspaces = [ ] } = packages [ '' ] || { }
178206 // uses a pathname-keyed map in order to negate the exact items
179207 const results = new Map ( )
180- const patterns = getPatterns ( workspaces )
181- if ( ! patterns . length ) {
208+ const { patterns, negatedPatterns } = getPatterns ( workspaces )
209+ if ( ! patterns . length && ! negatedPatterns . length ) {
182210 return results
183211 }
184- patterns . push ( { pattern : '**/node_modules/**' , negate : true } )
185-
186- const getPackagePathname = pkgPathmame ( opts )
212+ negatedPatterns . push ( '**/node_modules/**' )
187213
188- for ( const packageKey of Object . keys ( packages ) ) {
189- if ( packageKey === '' ) {
190- continue
214+ const packageKeys = Object . keys ( packages )
215+ for ( const pattern of negatedPatterns ) {
216+ for ( const packageKey of minimatch . match ( packageKeys , pattern ) ) {
217+ packageKeys . splice ( packageKeys . indexOf ( packageKey ) , 1 )
191218 }
219+ }
192220
193- for ( const item of patterns ) {
194- if ( minimatch ( packageKey , item . pattern ) ) {
195- const packagePathname = getPackagePathname ( packageKey )
196- const name = getPackageName ( packages [ packageKey ] , packagePathname )
197-
198- if ( item . negate ) {
199- results . delete ( packagePathname )
200- } else {
201- results . set ( packagePathname , name )
202- }
203- }
221+ const getPackagePathname = pkgPathmame ( opts )
222+ for ( const pattern of patterns ) {
223+ for ( const packageKey of minimatch . match ( packageKeys , pattern ) ) {
224+ const packagePathname = getPackagePathname ( packageKey )
225+ const name = getPackageName ( packages [ packageKey ] , packagePathname )
226+ results . set ( packagePathname , name )
204227 }
205228 }
206229
0 commit comments