@cjser/globby 16.2.0-cjser.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist-cjser/index.cjs +1349 -0
- package/ignore.js +868 -0
- package/index.d.ts +413 -0
- package/index.js +680 -0
- package/license +9 -0
- package/package.json +132 -0
- package/readme.md +406 -0
- package/utilities.js +382 -0
package/index.js
ADDED
|
@@ -0,0 +1,680 @@
|
|
|
1
|
+
import process from 'node:process';
|
|
2
|
+
import fs from 'node:fs';
|
|
3
|
+
import nodePath from 'node:path';
|
|
4
|
+
import {Readable} from 'node:stream';
|
|
5
|
+
import mergeStreams from '@cjser/sindresorhus__merge-streams';
|
|
6
|
+
import fastGlob from 'fast-glob';
|
|
7
|
+
import {toPath} from '@cjser/unicorn-magic/node';
|
|
8
|
+
import {
|
|
9
|
+
GITIGNORE_FILES_PATTERN,
|
|
10
|
+
getIgnorePatternsAndPredicate,
|
|
11
|
+
getIgnorePatternsAndPredicateSync,
|
|
12
|
+
getGlobalGitignoreFile,
|
|
13
|
+
getGlobalGitignoreFileAsync,
|
|
14
|
+
buildGlobalMatcher,
|
|
15
|
+
} from './ignore.js';
|
|
16
|
+
import {
|
|
17
|
+
bindFsMethod,
|
|
18
|
+
promisifyFsMethod,
|
|
19
|
+
isNegativePattern,
|
|
20
|
+
getStaticAbsolutePathPrefix,
|
|
21
|
+
normalizeNegativePattern,
|
|
22
|
+
normalizeDirectoryPatternForFastGlob,
|
|
23
|
+
adjustIgnorePatternsForParentDirectories,
|
|
24
|
+
convertPatternsForFastGlob,
|
|
25
|
+
findGitRoot,
|
|
26
|
+
findGitRootSync,
|
|
27
|
+
} from './utilities.js';
|
|
28
|
+
|
|
29
|
+
const assertPatternsInput = patterns => {
|
|
30
|
+
if (patterns.some(pattern => typeof pattern !== 'string')) {
|
|
31
|
+
throw new TypeError('Patterns must be a string or an array of strings');
|
|
32
|
+
}
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
const getStatMethod = fsImplementation => {
|
|
36
|
+
if (fsImplementation) {
|
|
37
|
+
return bindFsMethod(fsImplementation.promises, 'stat')
|
|
38
|
+
?? promisifyFsMethod(fsImplementation, 'stat');
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
return bindFsMethod(fs.promises, 'stat');
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
const getStatSyncMethod = fsImplementation =>
|
|
45
|
+
bindFsMethod(fsImplementation, 'statSync')
|
|
46
|
+
?? bindFsMethod(fs, 'statSync');
|
|
47
|
+
|
|
48
|
+
const isDirectory = async (path, fsImplementation) => {
|
|
49
|
+
try {
|
|
50
|
+
const stats = await getStatMethod(fsImplementation)(path);
|
|
51
|
+
return stats.isDirectory();
|
|
52
|
+
} catch {
|
|
53
|
+
return false;
|
|
54
|
+
}
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
const isDirectorySync = (path, fsImplementation) => {
|
|
58
|
+
try {
|
|
59
|
+
const stats = getStatSyncMethod(fsImplementation)(path);
|
|
60
|
+
return stats.isDirectory();
|
|
61
|
+
} catch {
|
|
62
|
+
return false;
|
|
63
|
+
}
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
const normalizePathForDirectoryGlob = (filePath, cwd) => {
|
|
67
|
+
const path = isNegativePattern(filePath) ? filePath.slice(1) : filePath;
|
|
68
|
+
return nodePath.isAbsolute(path) ? path : nodePath.join(cwd, path);
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
const shouldExpandGlobstarDirectory = pattern => {
|
|
72
|
+
const match = pattern?.match(/\*\*\/([^/]+)$/);
|
|
73
|
+
if (!match) {
|
|
74
|
+
return false;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
const dirname = match[1];
|
|
78
|
+
const hasWildcards = /[*?[\]{}]/.test(dirname);
|
|
79
|
+
const hasExtension = nodePath.extname(dirname) && !dirname.startsWith('.');
|
|
80
|
+
|
|
81
|
+
return !hasWildcards && !hasExtension;
|
|
82
|
+
};
|
|
83
|
+
|
|
84
|
+
const getDirectoryGlob = ({directoryPath, files, extensions}) => {
|
|
85
|
+
const extensionGlob = extensions?.length > 0 ? `.${extensions.length > 1 ? `{${extensions.join(',')}}` : extensions[0]}` : '';
|
|
86
|
+
return files
|
|
87
|
+
? files.map(file => nodePath.posix.join(directoryPath, `**/${nodePath.extname(file) ? file : `${file}${extensionGlob}`}`))
|
|
88
|
+
: [nodePath.posix.join(directoryPath, `**${extensionGlob ? `/*${extensionGlob}` : ''}`)];
|
|
89
|
+
};
|
|
90
|
+
|
|
91
|
+
const directoryToGlob = async (directoryPaths, {
|
|
92
|
+
cwd = process.cwd(),
|
|
93
|
+
files,
|
|
94
|
+
extensions,
|
|
95
|
+
fs: fsImplementation,
|
|
96
|
+
} = {}) => {
|
|
97
|
+
const globs = await Promise.all(directoryPaths.map(async directoryPath => {
|
|
98
|
+
// Check pattern without negative prefix
|
|
99
|
+
const checkPattern = isNegativePattern(directoryPath) ? directoryPath.slice(1) : directoryPath;
|
|
100
|
+
|
|
101
|
+
// Expand globstar directory patterns like **/dirname to **/dirname/**
|
|
102
|
+
if (shouldExpandGlobstarDirectory(checkPattern)) {
|
|
103
|
+
return getDirectoryGlob({directoryPath, files, extensions});
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
// Original logic for checking actual directories
|
|
107
|
+
const pathToCheck = normalizePathForDirectoryGlob(directoryPath, cwd);
|
|
108
|
+
return (await isDirectory(pathToCheck, fsImplementation)) ? getDirectoryGlob({directoryPath, files, extensions}) : directoryPath;
|
|
109
|
+
}));
|
|
110
|
+
|
|
111
|
+
return globs.flat();
|
|
112
|
+
};
|
|
113
|
+
|
|
114
|
+
const directoryToGlobSync = (directoryPaths, {
|
|
115
|
+
cwd = process.cwd(),
|
|
116
|
+
files,
|
|
117
|
+
extensions,
|
|
118
|
+
fs: fsImplementation,
|
|
119
|
+
} = {}) => directoryPaths.flatMap(directoryPath => {
|
|
120
|
+
// Check pattern without negative prefix
|
|
121
|
+
const checkPattern = isNegativePattern(directoryPath) ? directoryPath.slice(1) : directoryPath;
|
|
122
|
+
|
|
123
|
+
// Expand globstar directory patterns like **/dirname to **/dirname/**
|
|
124
|
+
if (shouldExpandGlobstarDirectory(checkPattern)) {
|
|
125
|
+
return getDirectoryGlob({directoryPath, files, extensions});
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
// Original logic for checking actual directories
|
|
129
|
+
const pathToCheck = normalizePathForDirectoryGlob(directoryPath, cwd);
|
|
130
|
+
return isDirectorySync(pathToCheck, fsImplementation) ? getDirectoryGlob({directoryPath, files, extensions}) : directoryPath;
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
const toPatternsArray = patterns => {
|
|
134
|
+
patterns = [...new Set([patterns].flat())];
|
|
135
|
+
assertPatternsInput(patterns);
|
|
136
|
+
return patterns;
|
|
137
|
+
};
|
|
138
|
+
|
|
139
|
+
const checkCwdOption = (cwd, fsImplementation = fs) => {
|
|
140
|
+
if (!cwd || !fsImplementation.statSync) {
|
|
141
|
+
return;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
let stats;
|
|
145
|
+
try {
|
|
146
|
+
stats = fsImplementation.statSync(cwd);
|
|
147
|
+
} catch {
|
|
148
|
+
// If stat fails (e.g., path doesn't exist), let fast-glob handle it
|
|
149
|
+
return;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
if (!stats.isDirectory()) {
|
|
153
|
+
throw new Error(`The \`cwd\` option must be a path to a directory, got: ${cwd}`);
|
|
154
|
+
}
|
|
155
|
+
};
|
|
156
|
+
|
|
157
|
+
const normalizeOptions = (options = {}) => {
|
|
158
|
+
// Normalize ignore to an array (fast-glob accepts string but we need array internally)
|
|
159
|
+
const ignore = options.ignore
|
|
160
|
+
? (Array.isArray(options.ignore) ? options.ignore : [options.ignore])
|
|
161
|
+
: [];
|
|
162
|
+
|
|
163
|
+
options = {
|
|
164
|
+
...options,
|
|
165
|
+
ignore,
|
|
166
|
+
expandDirectories: options.expandDirectories ?? true,
|
|
167
|
+
cwd: toPath(options.cwd),
|
|
168
|
+
};
|
|
169
|
+
|
|
170
|
+
checkCwdOption(options.cwd, options.fs);
|
|
171
|
+
|
|
172
|
+
return options;
|
|
173
|
+
};
|
|
174
|
+
|
|
175
|
+
const normalizeArguments = function_ => async (patterns, options) => function_(toPatternsArray(patterns), normalizeOptions(options));
|
|
176
|
+
const normalizeArgumentsSync = function_ => (patterns, options) => function_(toPatternsArray(patterns), normalizeOptions(options));
|
|
177
|
+
|
|
178
|
+
const getIgnoreFilesPatterns = options => {
|
|
179
|
+
const {ignoreFiles, gitignore} = options;
|
|
180
|
+
|
|
181
|
+
const patterns = ignoreFiles ? toPatternsArray(ignoreFiles) : [];
|
|
182
|
+
if (gitignore) {
|
|
183
|
+
patterns.push(GITIGNORE_FILES_PATTERN);
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
return patterns;
|
|
187
|
+
};
|
|
188
|
+
|
|
189
|
+
const isPathIgnored = (matcher, globalMatcher, path) => {
|
|
190
|
+
const globalResult = globalMatcher ? globalMatcher(path) : undefined;
|
|
191
|
+
const result = matcher ? matcher(path) : undefined;
|
|
192
|
+
|
|
193
|
+
if (result?.unignored) {
|
|
194
|
+
return false;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
return Boolean(result?.ignored || globalResult?.ignored);
|
|
198
|
+
};
|
|
199
|
+
|
|
200
|
+
const hasIgnoredAncestorDirectory = (matcher, globalMatcher, file) => {
|
|
201
|
+
let currentPath = file;
|
|
202
|
+
|
|
203
|
+
while (true) {
|
|
204
|
+
const parentDirectory = nodePath.dirname(currentPath);
|
|
205
|
+
if (parentDirectory === currentPath) {
|
|
206
|
+
return false;
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
if (isPathIgnored(matcher, globalMatcher, `${parentDirectory}${nodePath.sep}`)) {
|
|
210
|
+
return true;
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
currentPath = parentDirectory;
|
|
214
|
+
}
|
|
215
|
+
};
|
|
216
|
+
|
|
217
|
+
const combinePredicate = (matcher, globalMatcher) => {
|
|
218
|
+
if (!matcher && !globalMatcher) {
|
|
219
|
+
return false;
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
return file => {
|
|
223
|
+
const result = matcher ? matcher(file) : undefined;
|
|
224
|
+
|
|
225
|
+
// A local negation (e.g. `!file`) re-includes the file, unless
|
|
226
|
+
// a parent directory is ignored by either matcher.
|
|
227
|
+
if (result?.unignored) {
|
|
228
|
+
const globalResult = globalMatcher ? globalMatcher(file) : undefined;
|
|
229
|
+
return globalResult?.ignored && hasIgnoredAncestorDirectory(matcher, globalMatcher, file);
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
return isPathIgnored(matcher, globalMatcher, file);
|
|
233
|
+
};
|
|
234
|
+
};
|
|
235
|
+
|
|
236
|
+
const buildIgnoreFilterResult = (options, cwd, {patterns, matcher, usingGitRoot}, globalMatcher, createFilter) => {
|
|
237
|
+
const finalPredicate = combinePredicate(matcher, globalMatcher);
|
|
238
|
+
|
|
239
|
+
// Convert patterns to fast-glob format (may return empty array if predicate should handle everything)
|
|
240
|
+
const patternsForFastGlob = convertPatternsForFastGlob(patterns, usingGitRoot, normalizeDirectoryPatternForFastGlob);
|
|
241
|
+
|
|
242
|
+
return {
|
|
243
|
+
options: {
|
|
244
|
+
...options,
|
|
245
|
+
ignore: [...options.ignore, ...patternsForFastGlob],
|
|
246
|
+
},
|
|
247
|
+
filter: createFilter(finalPredicate, cwd, options.fs),
|
|
248
|
+
};
|
|
249
|
+
};
|
|
250
|
+
|
|
251
|
+
/**
|
|
252
|
+
Apply gitignore patterns to options and return filter predicate.
|
|
253
|
+
|
|
254
|
+
When negation patterns are present (e.g., '!important.log'), we cannot pass positive patterns to fast-glob because it would filter out files before our predicate can re-include them. In this case, we rely entirely on the predicate for filtering, which handles negations correctly.
|
|
255
|
+
|
|
256
|
+
When there are no negations, we optimize by passing patterns to fast-glob's ignore option to skip directories during traversal (performance optimization).
|
|
257
|
+
|
|
258
|
+
All patterns (including negated) are always used in the filter predicate to ensure correct Git-compatible behavior.
|
|
259
|
+
|
|
260
|
+
@returns {Promise<{options: Object, filter: Function}>}
|
|
261
|
+
*/
|
|
262
|
+
const applyIgnoreFilesAndGetFilter = async options => {
|
|
263
|
+
const cwd = options.cwd ?? process.cwd();
|
|
264
|
+
const ignoreFilesPatterns = getIgnoreFilesPatterns(options);
|
|
265
|
+
const globalIgnoreFile = options.globalGitignore ? await getGlobalGitignoreFileAsync(options) : undefined;
|
|
266
|
+
|
|
267
|
+
if (ignoreFilesPatterns.length === 0 && !globalIgnoreFile) {
|
|
268
|
+
return {
|
|
269
|
+
options,
|
|
270
|
+
filter: createFilterFunctionAsync(false, cwd, options.fs),
|
|
271
|
+
};
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
// Read ignore files once and get both patterns and predicate
|
|
275
|
+
// Enable parent .gitignore search when using gitignore option
|
|
276
|
+
const includeParentIgnoreFiles = options.gitignore === true;
|
|
277
|
+
const ignoreResult = ignoreFilesPatterns.length > 0
|
|
278
|
+
? await getIgnorePatternsAndPredicate(ignoreFilesPatterns, options, includeParentIgnoreFiles)
|
|
279
|
+
: {patterns: [], matcher: false, usingGitRoot: false};
|
|
280
|
+
|
|
281
|
+
const globalGitRoot = globalIgnoreFile ? await findGitRoot(cwd, options.fs) : undefined;
|
|
282
|
+
const globalMatcher = globalIgnoreFile ? buildGlobalMatcher(globalIgnoreFile, cwd, globalGitRoot ?? cwd) : undefined;
|
|
283
|
+
|
|
284
|
+
return buildIgnoreFilterResult(options, cwd, ignoreResult, globalMatcher, createFilterFunctionAsync);
|
|
285
|
+
};
|
|
286
|
+
|
|
287
|
+
/**
|
|
288
|
+
Apply gitignore patterns to options and return filter predicate (sync version).
|
|
289
|
+
|
|
290
|
+
@returns {{options: Object, filter: Function}}
|
|
291
|
+
*/
|
|
292
|
+
const applyIgnoreFilesAndGetFilterSync = options => {
|
|
293
|
+
const cwd = options.cwd ?? process.cwd();
|
|
294
|
+
const ignoreFilesPatterns = getIgnoreFilesPatterns(options);
|
|
295
|
+
const globalIgnoreFile = options.globalGitignore ? getGlobalGitignoreFile(options) : undefined;
|
|
296
|
+
|
|
297
|
+
if (ignoreFilesPatterns.length === 0 && !globalIgnoreFile) {
|
|
298
|
+
return {
|
|
299
|
+
options,
|
|
300
|
+
filter: createFilterFunction(false, cwd, options.fs),
|
|
301
|
+
};
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
// Read ignore files once and get both patterns and predicate
|
|
305
|
+
// Enable parent .gitignore search when using gitignore option
|
|
306
|
+
const includeParentIgnoreFiles = options.gitignore === true;
|
|
307
|
+
const ignoreResult = ignoreFilesPatterns.length > 0
|
|
308
|
+
? getIgnorePatternsAndPredicateSync(ignoreFilesPatterns, options, includeParentIgnoreFiles)
|
|
309
|
+
: {patterns: [], matcher: false, usingGitRoot: false};
|
|
310
|
+
|
|
311
|
+
const globalGitRoot = globalIgnoreFile ? findGitRootSync(cwd, options.fs) : undefined;
|
|
312
|
+
const globalMatcher = globalIgnoreFile ? buildGlobalMatcher(globalIgnoreFile, cwd, globalGitRoot ?? cwd) : undefined;
|
|
313
|
+
|
|
314
|
+
return buildIgnoreFilterResult(options, cwd, ignoreResult, globalMatcher, createFilterFunction);
|
|
315
|
+
};
|
|
316
|
+
|
|
317
|
+
const assertGlobalGitignoreSyncSupport = options => {
|
|
318
|
+
if (options.globalGitignore && options.fs && !options.fs.statSync) {
|
|
319
|
+
throw new Error('The `globalGitignore` option in `globbySync()` requires `fs.statSync` when a custom `fs` is provided.');
|
|
320
|
+
}
|
|
321
|
+
};
|
|
322
|
+
|
|
323
|
+
const globalGitignoreAsyncStatErrorMessage = 'The `globalGitignore` option in `globby()` and `globbyStream()` requires `fs.promises.stat` or `fs.stat` when a custom `fs` is provided.';
|
|
324
|
+
|
|
325
|
+
const assertGlobalGitignoreAsyncSupport = options => {
|
|
326
|
+
if (!options.globalGitignore || !options.fs) {
|
|
327
|
+
return;
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
if (!options.fs.promises?.stat && !options.fs.stat) {
|
|
331
|
+
throw new Error(globalGitignoreAsyncStatErrorMessage);
|
|
332
|
+
}
|
|
333
|
+
};
|
|
334
|
+
|
|
335
|
+
const createPathResolver = cwd => {
|
|
336
|
+
const basePath = cwd || process.cwd();
|
|
337
|
+
const pathCache = new Map();
|
|
338
|
+
|
|
339
|
+
return pathKey => {
|
|
340
|
+
let absolutePath = pathCache.get(pathKey);
|
|
341
|
+
if (absolutePath === undefined) {
|
|
342
|
+
if (pathCache.size > 10_000) {
|
|
343
|
+
pathCache.clear();
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
absolutePath = nodePath.isAbsolute(pathKey) ? pathKey : nodePath.resolve(basePath, pathKey);
|
|
347
|
+
pathCache.set(pathKey, absolutePath);
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
return absolutePath;
|
|
351
|
+
};
|
|
352
|
+
};
|
|
353
|
+
|
|
354
|
+
const createAsyncDirectoryCheck = fsMethod => {
|
|
355
|
+
const directoryCache = new Map();
|
|
356
|
+
|
|
357
|
+
return async absolutePath => {
|
|
358
|
+
let isDirectory = directoryCache.get(absolutePath);
|
|
359
|
+
if (isDirectory !== undefined) {
|
|
360
|
+
return isDirectory;
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
try {
|
|
364
|
+
const stats = await fsMethod?.(absolutePath);
|
|
365
|
+
isDirectory = Boolean(stats?.isDirectory());
|
|
366
|
+
} catch {
|
|
367
|
+
isDirectory = false;
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
if (directoryCache.size > 10_000) {
|
|
371
|
+
directoryCache.clear();
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
directoryCache.set(absolutePath, isDirectory);
|
|
375
|
+
return isDirectory;
|
|
376
|
+
};
|
|
377
|
+
};
|
|
378
|
+
|
|
379
|
+
const createDirectoryCheck = fsMethod => {
|
|
380
|
+
const directoryCache = new Map();
|
|
381
|
+
|
|
382
|
+
return absolutePath => {
|
|
383
|
+
let isDirectory = directoryCache.get(absolutePath);
|
|
384
|
+
if (isDirectory !== undefined) {
|
|
385
|
+
return isDirectory;
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
try {
|
|
389
|
+
isDirectory = Boolean(fsMethod?.(absolutePath)?.isDirectory());
|
|
390
|
+
} catch {
|
|
391
|
+
isDirectory = false;
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
if (directoryCache.size > 10_000) {
|
|
395
|
+
directoryCache.clear();
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
directoryCache.set(absolutePath, isDirectory);
|
|
399
|
+
return isDirectory;
|
|
400
|
+
};
|
|
401
|
+
};
|
|
402
|
+
|
|
403
|
+
const createFilterFunctionAsync = (isIgnored, cwd, fsImplementation) => {
|
|
404
|
+
const resolveAbsolutePath = createPathResolver(cwd);
|
|
405
|
+
const isDirectoryEntry = createAsyncDirectoryCheck(getStatMethod(fsImplementation));
|
|
406
|
+
|
|
407
|
+
return async fastGlobResult => {
|
|
408
|
+
if (!isIgnored) {
|
|
409
|
+
return true;
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
const absolutePath = resolveAbsolutePath(nodePath.normalize(fastGlobResult.path ?? fastGlobResult));
|
|
413
|
+
if (isIgnored(absolutePath)) {
|
|
414
|
+
return false;
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
return !(await isDirectoryEntry(absolutePath) && isIgnored(`${absolutePath}${nodePath.sep}`));
|
|
418
|
+
};
|
|
419
|
+
};
|
|
420
|
+
|
|
421
|
+
const createFilterFunction = (isIgnored, cwd, fsImplementation) => {
|
|
422
|
+
const seen = new Set();
|
|
423
|
+
const resolveAbsolutePath = createPathResolver(cwd);
|
|
424
|
+
const isDirectoryEntry = createDirectoryCheck(getStatSyncMethod(fsImplementation));
|
|
425
|
+
|
|
426
|
+
return fastGlobResult => {
|
|
427
|
+
const pathKey = nodePath.normalize(fastGlobResult.path ?? fastGlobResult);
|
|
428
|
+
|
|
429
|
+
if (seen.has(pathKey)) {
|
|
430
|
+
return false;
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
if (isIgnored) {
|
|
434
|
+
const absolutePath = resolveAbsolutePath(pathKey);
|
|
435
|
+
if (isIgnored(absolutePath)) {
|
|
436
|
+
return false;
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
if (isDirectoryEntry(absolutePath) && isIgnored(`${absolutePath}${nodePath.sep}`)) {
|
|
440
|
+
return false;
|
|
441
|
+
}
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
seen.add(pathKey);
|
|
445
|
+
return true;
|
|
446
|
+
};
|
|
447
|
+
};
|
|
448
|
+
|
|
449
|
+
const unionFastGlobResults = (results, filter) => results.flat().filter(fastGlobResult => filter(fastGlobResult));
|
|
450
|
+
const unionFastGlobResultsAsync = async (results, filter) => {
|
|
451
|
+
results = results.flat();
|
|
452
|
+
const matches = await Promise.all(results.map(fastGlobResult => filter(fastGlobResult)));
|
|
453
|
+
const seen = new Set();
|
|
454
|
+
|
|
455
|
+
return results.filter((fastGlobResult, index) => {
|
|
456
|
+
if (!matches[index]) {
|
|
457
|
+
return false;
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
const pathKey = nodePath.normalize(fastGlobResult.path ?? fastGlobResult);
|
|
461
|
+
if (seen.has(pathKey)) {
|
|
462
|
+
return false;
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
seen.add(pathKey);
|
|
466
|
+
return true;
|
|
467
|
+
});
|
|
468
|
+
};
|
|
469
|
+
|
|
470
|
+
const convertNegativePatterns = (patterns, options) => {
|
|
471
|
+
// If all patterns are negative and expandNegationOnlyPatterns is enabled (default),
|
|
472
|
+
// prepend a positive catch-all pattern to make negation-only patterns work intuitively
|
|
473
|
+
// (e.g., '!*.json' matches all files except JSON)
|
|
474
|
+
if (patterns.length > 0 && patterns.every(pattern => isNegativePattern(pattern))) {
|
|
475
|
+
if (options.expandNegationOnlyPatterns === false) {
|
|
476
|
+
return [];
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
patterns = ['**/*', ...patterns];
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
const positiveAbsolutePathPrefixes = [];
|
|
483
|
+
let hasRelativePositivePattern = false;
|
|
484
|
+
const normalizedPatterns = [];
|
|
485
|
+
|
|
486
|
+
for (const pattern of patterns) {
|
|
487
|
+
if (isNegativePattern(pattern)) {
|
|
488
|
+
normalizedPatterns.push(`!${normalizeNegativePattern(pattern.slice(1), positiveAbsolutePathPrefixes, hasRelativePositivePattern)}`);
|
|
489
|
+
continue;
|
|
490
|
+
}
|
|
491
|
+
|
|
492
|
+
normalizedPatterns.push(pattern);
|
|
493
|
+
|
|
494
|
+
const staticAbsolutePathPrefix = getStaticAbsolutePathPrefix(pattern);
|
|
495
|
+
if (staticAbsolutePathPrefix === undefined) {
|
|
496
|
+
hasRelativePositivePattern = true;
|
|
497
|
+
continue;
|
|
498
|
+
}
|
|
499
|
+
|
|
500
|
+
positiveAbsolutePathPrefixes.push(staticAbsolutePathPrefix);
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
patterns = normalizedPatterns;
|
|
504
|
+
|
|
505
|
+
const tasks = [];
|
|
506
|
+
|
|
507
|
+
while (patterns.length > 0) {
|
|
508
|
+
const index = patterns.findIndex(pattern => isNegativePattern(pattern));
|
|
509
|
+
|
|
510
|
+
if (index === -1) {
|
|
511
|
+
tasks.push({patterns, options});
|
|
512
|
+
break;
|
|
513
|
+
}
|
|
514
|
+
|
|
515
|
+
const ignorePattern = patterns[index].slice(1);
|
|
516
|
+
|
|
517
|
+
for (const task of tasks) {
|
|
518
|
+
task.options.ignore.push(ignorePattern);
|
|
519
|
+
}
|
|
520
|
+
|
|
521
|
+
if (index !== 0) {
|
|
522
|
+
tasks.push({
|
|
523
|
+
patterns: patterns.slice(0, index),
|
|
524
|
+
options: {
|
|
525
|
+
...options,
|
|
526
|
+
ignore: [
|
|
527
|
+
...options.ignore,
|
|
528
|
+
ignorePattern,
|
|
529
|
+
],
|
|
530
|
+
},
|
|
531
|
+
});
|
|
532
|
+
}
|
|
533
|
+
|
|
534
|
+
patterns = patterns.slice(index + 1);
|
|
535
|
+
}
|
|
536
|
+
|
|
537
|
+
return tasks;
|
|
538
|
+
};
|
|
539
|
+
|
|
540
|
+
const applyParentDirectoryIgnoreAdjustments = tasks => tasks.map(task => ({
|
|
541
|
+
patterns: task.patterns,
|
|
542
|
+
options: {
|
|
543
|
+
...task.options,
|
|
544
|
+
ignore: adjustIgnorePatternsForParentDirectories(task.patterns, task.options.ignore),
|
|
545
|
+
},
|
|
546
|
+
}));
|
|
547
|
+
|
|
548
|
+
const normalizeExpandDirectoriesOption = (options, cwd) => ({
|
|
549
|
+
...(cwd ? {cwd} : {}),
|
|
550
|
+
...(Array.isArray(options) ? {files: options} : options),
|
|
551
|
+
});
|
|
552
|
+
|
|
553
|
+
const generateTasks = async (patterns, options) => {
|
|
554
|
+
const globTasks = convertNegativePatterns(patterns, options);
|
|
555
|
+
|
|
556
|
+
const {cwd, expandDirectories, fs: fsImplementation} = options;
|
|
557
|
+
|
|
558
|
+
if (!expandDirectories) {
|
|
559
|
+
return applyParentDirectoryIgnoreAdjustments(globTasks);
|
|
560
|
+
}
|
|
561
|
+
|
|
562
|
+
const directoryToGlobOptions = {
|
|
563
|
+
...normalizeExpandDirectoriesOption(expandDirectories, cwd),
|
|
564
|
+
fs: fsImplementation,
|
|
565
|
+
};
|
|
566
|
+
|
|
567
|
+
return Promise.all(globTasks.map(async task => {
|
|
568
|
+
let {patterns, options} = task;
|
|
569
|
+
|
|
570
|
+
[
|
|
571
|
+
patterns,
|
|
572
|
+
options.ignore,
|
|
573
|
+
] = await Promise.all([
|
|
574
|
+
directoryToGlob(patterns, directoryToGlobOptions),
|
|
575
|
+
directoryToGlob(options.ignore, {cwd, fs: fsImplementation}),
|
|
576
|
+
]);
|
|
577
|
+
|
|
578
|
+
// Adjust ignore patterns for parent directory references
|
|
579
|
+
options.ignore = adjustIgnorePatternsForParentDirectories(patterns, options.ignore);
|
|
580
|
+
|
|
581
|
+
return {patterns, options};
|
|
582
|
+
}));
|
|
583
|
+
};
|
|
584
|
+
|
|
585
|
+
const generateTasksSync = (patterns, options) => {
|
|
586
|
+
const globTasks = convertNegativePatterns(patterns, options);
|
|
587
|
+
const {cwd, expandDirectories, fs: fsImplementation} = options;
|
|
588
|
+
|
|
589
|
+
if (!expandDirectories) {
|
|
590
|
+
return applyParentDirectoryIgnoreAdjustments(globTasks);
|
|
591
|
+
}
|
|
592
|
+
|
|
593
|
+
const directoryToGlobSyncOptions = {
|
|
594
|
+
...normalizeExpandDirectoriesOption(expandDirectories, cwd),
|
|
595
|
+
fs: fsImplementation,
|
|
596
|
+
};
|
|
597
|
+
|
|
598
|
+
return globTasks.map(task => {
|
|
599
|
+
let {patterns, options} = task;
|
|
600
|
+
patterns = directoryToGlobSync(patterns, directoryToGlobSyncOptions);
|
|
601
|
+
options.ignore = directoryToGlobSync(options.ignore, {cwd, fs: fsImplementation});
|
|
602
|
+
|
|
603
|
+
// Adjust ignore patterns for parent directory references
|
|
604
|
+
options.ignore = adjustIgnorePatternsForParentDirectories(patterns, options.ignore);
|
|
605
|
+
|
|
606
|
+
return {patterns, options};
|
|
607
|
+
});
|
|
608
|
+
};
|
|
609
|
+
|
|
610
|
+
export const globby = normalizeArguments(async (patterns, options) => {
|
|
611
|
+
assertGlobalGitignoreAsyncSupport(options);
|
|
612
|
+
|
|
613
|
+
// Apply ignore files and get filter (reads .gitignore files once)
|
|
614
|
+
const {options: modifiedOptions, filter} = await applyIgnoreFilesAndGetFilter(options);
|
|
615
|
+
|
|
616
|
+
// Generate tasks with modified options (includes gitignore patterns in ignore option)
|
|
617
|
+
const tasks = await generateTasks(patterns, modifiedOptions);
|
|
618
|
+
|
|
619
|
+
const results = await Promise.all(tasks.map(task => fastGlob(task.patterns, task.options)));
|
|
620
|
+
return unionFastGlobResultsAsync(results, filter);
|
|
621
|
+
});
|
|
622
|
+
|
|
623
|
+
export const globbySync = normalizeArgumentsSync((patterns, options) => {
|
|
624
|
+
assertGlobalGitignoreSyncSupport(options);
|
|
625
|
+
|
|
626
|
+
// Apply ignore files and get filter (reads .gitignore files once)
|
|
627
|
+
const {options: modifiedOptions, filter} = applyIgnoreFilesAndGetFilterSync(options);
|
|
628
|
+
|
|
629
|
+
// Generate tasks with modified options (includes gitignore patterns in ignore option)
|
|
630
|
+
const tasks = generateTasksSync(patterns, modifiedOptions);
|
|
631
|
+
|
|
632
|
+
const results = tasks.map(task => fastGlob.sync(task.patterns, task.options));
|
|
633
|
+
return unionFastGlobResults(results, filter);
|
|
634
|
+
});
|
|
635
|
+
|
|
636
|
+
export const globbyStream = normalizeArgumentsSync((patterns, options) => {
|
|
637
|
+
assertGlobalGitignoreAsyncSupport(options);
|
|
638
|
+
|
|
639
|
+
const seen = new Set();
|
|
640
|
+
const stream = Readable.from((async function * () {
|
|
641
|
+
// Apply ignore files and get filter (reads .gitignore files once)
|
|
642
|
+
const {options: modifiedOptions, filter} = await applyIgnoreFilesAndGetFilter(options);
|
|
643
|
+
|
|
644
|
+
// Generate tasks with modified options (includes gitignore patterns in ignore option)
|
|
645
|
+
const tasks = await generateTasks(patterns, modifiedOptions);
|
|
646
|
+
|
|
647
|
+
if (tasks.length === 0) {
|
|
648
|
+
return;
|
|
649
|
+
}
|
|
650
|
+
|
|
651
|
+
const streams = tasks.map(task => fastGlob.stream(task.patterns, task.options));
|
|
652
|
+
|
|
653
|
+
for await (const fastGlobResult of mergeStreams(streams)) {
|
|
654
|
+
const pathKey = nodePath.normalize(fastGlobResult.path ?? fastGlobResult);
|
|
655
|
+
if (!seen.has(pathKey) && await filter(fastGlobResult)) {
|
|
656
|
+
seen.add(pathKey);
|
|
657
|
+
yield fastGlobResult;
|
|
658
|
+
}
|
|
659
|
+
}
|
|
660
|
+
})());
|
|
661
|
+
|
|
662
|
+
// Returning a web stream will require revisiting once Readable.toWeb integration is viable.
|
|
663
|
+
// return Readable.toWeb(stream);
|
|
664
|
+
|
|
665
|
+
return stream;
|
|
666
|
+
});
|
|
667
|
+
|
|
668
|
+
export const isDynamicPattern = normalizeArgumentsSync((patterns, options) => patterns.some(pattern => fastGlob.isDynamicPattern(pattern, options)));
|
|
669
|
+
|
|
670
|
+
export const generateGlobTasks = normalizeArguments(generateTasks);
|
|
671
|
+
export const generateGlobTasksSync = normalizeArgumentsSync(generateTasksSync);
|
|
672
|
+
|
|
673
|
+
export {
|
|
674
|
+
isGitIgnored,
|
|
675
|
+
isGitIgnoredSync,
|
|
676
|
+
isIgnoredByIgnoreFiles,
|
|
677
|
+
isIgnoredByIgnoreFilesSync,
|
|
678
|
+
} from './ignore.js';
|
|
679
|
+
|
|
680
|
+
export const {convertPathToPattern} = fastGlob;
|
package/license
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) Sindre Sorhus <sindresorhus@gmail.com> (https://sindresorhus.com)
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
|
6
|
+
|
|
7
|
+
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
|
8
|
+
|
|
9
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|