@@ -5,23 +5,49 @@ const { minimatch } = require('minimatch')
5
5
const rpj = require ( 'read-package-json-fast' )
6
6
const { glob } = require ( 'glob' )
7
7
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 ) {
11
12
const excl = pattern . match ( / ^ ! + / )
12
13
if ( excl ) {
13
14
pattern = pattern . slice ( excl [ 0 ] . length )
14
15
}
15
16
16
- // strip off any / from the start of the pattern. /foo => foo
17
- pattern = pattern . replace ( / ^ \/ + / , '' )
17
+ // strip off any / or ./ from the start of the pattern. /foo => foo
18
+ pattern = pattern . replace ( / ^ \. ? \ /+ / , '' )
18
19
19
20
// an odd number of ! means a negated pattern. !!foo ==> foo
20
21
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
+ }
22
41
}
23
42
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 }
25
51
}
26
52
27
53
function getPatterns ( workspaces ) {
@@ -77,64 +103,66 @@ async function mapWorkspaces (opts = {}) {
77
103
}
78
104
79
105
const { workspaces = [ ] } = opts . pkg
80
- const patterns = getPatterns ( workspaces )
106
+ const { patterns, negatedPatterns } = getPatterns ( workspaces )
81
107
const results = new Map ( )
82
108
const seen = new Map ( )
83
109
84
- if ( ! patterns . length ) {
110
+ if ( ! patterns . length && ! negatedPatterns . length ) {
85
111
return results
86
112
}
87
113
88
114
const getGlobOpts = ( ) => ( {
89
115
...opts ,
90
116
ignore : [
91
117
...opts . ignore || [ ] ,
92
- ...[ '**/node_modules/**' ] ,
118
+ '**/node_modules/**' ,
119
+ // just ignore the negated patterns to avoid unnecessary crawling
120
+ ...negatedPatterns ,
93
121
] ,
94
122
} )
95
123
96
124
const getPackagePathname = pkgPathmame ( opts )
97
125
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
+ }
117
138
118
- const name = getPackageName ( pkg , packagePathname )
139
+ for ( const match of orderedMatches ) {
140
+ let pkg
141
+ const packageJsonPathname = getPackagePathname ( match , 'package.json' )
119
142
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
127
148
} else {
128
- seenPackagePathnames . add ( packagePathname )
149
+ throw err
129
150
}
130
151
}
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 )
131
162
}
132
163
133
164
const errorMessageArray = [ 'must not have multiple workspaces with the same name' ]
134
165
for ( const [ packageName , seenPackagePathnames ] of seen ) {
135
- if ( seenPackagePathnames . size === 0 ) {
136
- continue
137
- }
138
166
if ( seenPackagePathnames . size > 1 ) {
139
167
addDuplicateErrorMessages ( errorMessageArray , packageName , seenPackagePathnames )
140
168
} else {
@@ -177,30 +205,25 @@ mapWorkspaces.virtual = function (opts = {}) {
177
205
const { workspaces = [ ] } = packages [ '' ] || { }
178
206
// uses a pathname-keyed map in order to negate the exact items
179
207
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 ) {
182
210
return results
183
211
}
184
- patterns . push ( { pattern : '**/node_modules/**' , negate : true } )
185
-
186
- const getPackagePathname = pkgPathmame ( opts )
212
+ negatedPatterns . push ( '**/node_modules/**' )
187
213
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 )
191
218
}
219
+ }
192
220
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 )
204
227
}
205
228
}
206
229
0 commit comments