@cjser/globby 16.2.1-cjser.2 → 16.2.3-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 +278 -61
- package/ignore.js +172 -24
- package/index.js +63 -36
- package/package.json +3 -3
- package/readme.md +1 -1
- package/utilities.js +217 -35
package/ignore.js
CHANGED
|
@@ -15,6 +15,8 @@ import {
|
|
|
15
15
|
findGitRoot,
|
|
16
16
|
findGitRootSync,
|
|
17
17
|
getParentGitignorePaths,
|
|
18
|
+
buildPrunePatternsAndGuards,
|
|
19
|
+
negationsCouldRescue,
|
|
18
20
|
} from './utilities.js';
|
|
19
21
|
|
|
20
22
|
const defaultIgnoredDirectories = [
|
|
@@ -117,14 +119,43 @@ const globIgnoreFiles = (globFunction, patterns, normalizedOptions) => globFunct
|
|
|
117
119
|
...ignoreFilesGlobOptions, // Must be last to ensure absolute/dot flags stick
|
|
118
120
|
});
|
|
119
121
|
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
122
|
+
// Normalize a raw ignore-file line the way git does: strip a byte order mark and trailing whitespace. "Trailing spaces are ignored unless they are quoted with backslash" - a backslash-escaped trailing space is kept, still escaped, so the line can be re-fed to the `ignore` package without being stripped again.
|
|
123
|
+
const normalizeIgnoreFileLine = line => {
|
|
124
|
+
line = line.replace(/^\uFEFF/u, '');
|
|
123
125
|
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
126
|
+
let whitespaceStart = line.length;
|
|
127
|
+
while (whitespaceStart > 0 && /\s/u.test(line[whitespaceStart - 1])) {
|
|
128
|
+
whitespaceStart--;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
if (whitespaceStart === line.length) {
|
|
132
|
+
return line;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
let backslashCount = 0;
|
|
136
|
+
for (let index = whitespaceStart - 1; index >= 0 && line[index] === '\\'; index--) {
|
|
137
|
+
backslashCount++;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
return backslashCount % 2 === 1
|
|
141
|
+
? line.slice(0, whitespaceStart) + ' '
|
|
142
|
+
: line.slice(0, whitespaceStart);
|
|
143
|
+
};
|
|
144
|
+
|
|
145
|
+
const readIgnoreFileLines = content => content
|
|
146
|
+
.split(/\r?\n/)
|
|
147
|
+
.map(line => normalizeIgnoreFileLine(line))
|
|
148
|
+
.filter(line => line && !line.startsWith('#'));
|
|
149
|
+
|
|
150
|
+
/**
|
|
151
|
+
Get the lines of every ignore file, as git reads them, each paired with the directory of the file that declared them.
|
|
152
|
+
|
|
153
|
+
Anchoring is only meaningful relative to that directory, and the rebased patterns have already lost it, so keep the originals for anything that has to reason about which paths a rule covers.
|
|
154
|
+
*/
|
|
155
|
+
const getIgnoreRules = files => files.flatMap(file => {
|
|
156
|
+
const directory = path.dirname(file.filePath);
|
|
157
|
+
return readIgnoreFileLines(file.content).map(pattern => ({pattern, directory}));
|
|
158
|
+
});
|
|
128
159
|
|
|
129
160
|
const buildIgnoreResult = (files, normalizedOptions, gitRoot) => {
|
|
130
161
|
const baseDir = gitRoot || normalizedOptions.cwd;
|
|
@@ -133,6 +164,7 @@ const buildIgnoreResult = (files, normalizedOptions, gitRoot) => {
|
|
|
133
164
|
|
|
134
165
|
return {
|
|
135
166
|
patterns,
|
|
167
|
+
rules: getIgnoreRules(files),
|
|
136
168
|
matcher,
|
|
137
169
|
predicate: fileOrDirectory => matcher(fileOrDirectory).ignored,
|
|
138
170
|
usingGitRoot: Boolean(gitRoot && gitRoot !== normalizedOptions.cwd),
|
|
@@ -177,11 +209,7 @@ const applyBaseToPattern = (pattern, base) => {
|
|
|
177
209
|
|
|
178
210
|
const parseIgnoreFile = (file, cwd) => {
|
|
179
211
|
const base = slash(path.relative(cwd, path.dirname(file.filePath)));
|
|
180
|
-
|
|
181
|
-
return file.content
|
|
182
|
-
.split(/\r?\n/)
|
|
183
|
-
.filter(line => line && !line.startsWith('#'))
|
|
184
|
-
.map(pattern => applyBaseToPattern(pattern, base));
|
|
212
|
+
return readIgnoreFileLines(file.content).map(pattern => applyBaseToPattern(pattern, base));
|
|
185
213
|
};
|
|
186
214
|
|
|
187
215
|
const toRelativePath = (fileOrDirectory, cwd) => {
|
|
@@ -515,6 +543,7 @@ const createExcludesFileValue = (value, declaringFilePath) => ({
|
|
|
515
543
|
|
|
516
544
|
/**
|
|
517
545
|
Parse git config content and return the excludesFile value and any include paths to recurse into.
|
|
546
|
+
|
|
518
547
|
The caller is responsible for reading files and recursing (sync or async).
|
|
519
548
|
*/
|
|
520
549
|
const parseGitConfigForExcludesFile = (content, normalizedPath, gitDirectory) => {
|
|
@@ -789,30 +818,149 @@ export const buildGlobalPredicate = (globalIgnoreFile, cwd, rootDirectory = cwd)
|
|
|
789
818
|
return fileOrDirectory => matcher(fileOrDirectory).ignored;
|
|
790
819
|
};
|
|
791
820
|
|
|
821
|
+
/**
|
|
822
|
+
Ignore files at or above the cwd can be located without traversing anything.
|
|
823
|
+
|
|
824
|
+
Reading them before searching for the nested ones lets the search itself skip whole ignored directories. Otherwise the recursive search for ignore files walks even the directories that the same `.gitignore` excludes, so a large ignored directory (a mounted share, a build output) is enumerated on every call.
|
|
825
|
+
*/
|
|
826
|
+
const getKnownIgnoreFilePaths = (patterns, normalizedOptions, gitRoot) => {
|
|
827
|
+
const searchPatterns = [patterns].flat();
|
|
828
|
+
const isGitignoreSearch = searchPatterns.includes(GITIGNORE_FILES_PATTERN);
|
|
829
|
+
if (!isGitignoreSearch) {
|
|
830
|
+
return [];
|
|
831
|
+
}
|
|
832
|
+
|
|
833
|
+
return gitRoot
|
|
834
|
+
? getParentGitignorePaths(gitRoot, normalizedOptions.cwd)
|
|
835
|
+
: [path.join(normalizedOptions.cwd, '.gitignore')];
|
|
836
|
+
};
|
|
837
|
+
|
|
838
|
+
const getKnownIgnoreFileSearchOptions = (patterns, normalizedOptions) => ({
|
|
839
|
+
...normalizedOptions,
|
|
840
|
+
ignore: [
|
|
841
|
+
...normalizedOptions.ignore,
|
|
842
|
+
// Keep negative search patterns active when the known candidates are matched independently.
|
|
843
|
+
...[patterns].flat()
|
|
844
|
+
.filter(pattern => isNegativePattern(pattern))
|
|
845
|
+
.map(pattern => pattern.slice(1)),
|
|
846
|
+
],
|
|
847
|
+
});
|
|
848
|
+
|
|
849
|
+
const getKnownIgnoreFilePattern = (filePath, cwd) => {
|
|
850
|
+
// Relative candidates keep cwd-relative exclusions such as `.gitignore` meaningful; parent candidates must remain absolute because they are outside cwd.
|
|
851
|
+
const pattern = isPathInside(filePath, cwd) ? path.relative(cwd, filePath) : filePath;
|
|
852
|
+
return fastGlob.convertPathToPattern(pattern);
|
|
853
|
+
};
|
|
854
|
+
|
|
855
|
+
const getMatchingKnownIgnoreFilePaths = (knownPaths, matchingPaths) => {
|
|
856
|
+
// Fast-glob normalizes its results, so compare resolved paths before returning the original paths for reading.
|
|
857
|
+
const matchingPathSet = new Set(matchingPaths.map(filePath => path.resolve(filePath)));
|
|
858
|
+
|
|
859
|
+
return knownPaths.filter(filePath => matchingPathSet.has(path.resolve(filePath)));
|
|
860
|
+
};
|
|
861
|
+
|
|
862
|
+
const globKnownIgnoreFilePaths = (globFunction, knownPaths, patterns, normalizedOptions) => {
|
|
863
|
+
if (knownPaths.length === 0) {
|
|
864
|
+
return [];
|
|
865
|
+
}
|
|
866
|
+
|
|
867
|
+
return globIgnoreFiles(
|
|
868
|
+
globFunction,
|
|
869
|
+
knownPaths.map(filePath => getKnownIgnoreFilePattern(filePath, normalizedOptions.cwd)),
|
|
870
|
+
getKnownIgnoreFileSearchOptions(patterns, normalizedOptions),
|
|
871
|
+
);
|
|
872
|
+
};
|
|
873
|
+
|
|
874
|
+
const filterKnownIgnoreFilePathsAsync = async (knownPaths, patterns, normalizedOptions) => {
|
|
875
|
+
const matchingPaths = await globKnownIgnoreFilePaths(fastGlob, knownPaths, patterns, normalizedOptions);
|
|
876
|
+
return getMatchingKnownIgnoreFilePaths(knownPaths, matchingPaths);
|
|
877
|
+
};
|
|
878
|
+
|
|
879
|
+
const filterKnownIgnoreFilePathsSync = (knownPaths, patterns, normalizedOptions) => {
|
|
880
|
+
const matchingPaths = globKnownIgnoreFilePaths(fastGlob.sync, knownPaths, patterns, normalizedOptions);
|
|
881
|
+
return getMatchingKnownIgnoreFilePaths(knownPaths, matchingPaths);
|
|
882
|
+
};
|
|
883
|
+
|
|
884
|
+
const getIgnoreFileSearchPrune = (searchPatterns, files, normalizedOptions, gitRoot) => {
|
|
885
|
+
if (files.length === 0) {
|
|
886
|
+
return {patterns: [], guardNames: []};
|
|
887
|
+
}
|
|
888
|
+
|
|
889
|
+
const {cwd} = normalizedOptions;
|
|
890
|
+
const baseDir = gitRoot || cwd;
|
|
891
|
+
const ignorePatterns = getPatternsFromIgnoreFiles(files, baseDir);
|
|
892
|
+
const matcher = createIgnoreMatcher(ignorePatterns, cwd, baseDir);
|
|
893
|
+
|
|
894
|
+
// Custom ignore files can live anywhere, including beside the cwd, so only a pure `.gitignore` search may treat the rules at or above the cwd as complete.
|
|
895
|
+
const searchPatternsArray = [searchPatterns].flat();
|
|
896
|
+
const gitignoreOnlySearch = searchPatternsArray.every(pattern => pattern === GITIGNORE_FILES_PATTERN);
|
|
897
|
+
const searchesForGitignoreFiles = searchPatternsArray.includes(GITIGNORE_FILES_PATTERN);
|
|
898
|
+
|
|
899
|
+
return buildPrunePatternsAndGuards(getIgnoreRules(files), matcher, cwd, {gitignoreOnlySearch, searchesForGitignoreFiles});
|
|
900
|
+
};
|
|
901
|
+
|
|
902
|
+
const withPrunedSearch = (normalizedOptions, prunePatterns) => prunePatterns.length === 0
|
|
903
|
+
? normalizedOptions
|
|
904
|
+
: {...normalizedOptions, ignore: [...normalizedOptions.ignore, ...prunePatterns]};
|
|
905
|
+
|
|
906
|
+
// The known ignore files have already been read; only the newly discovered ones are left.
|
|
907
|
+
const getUnreadPaths = (childPaths, knownPaths) => {
|
|
908
|
+
const alreadyRead = new Set(knownPaths.map(filePath => path.resolve(filePath)));
|
|
909
|
+
return dedupePaths(childPaths).filter(filePath => !alreadyRead.has(path.resolve(filePath)));
|
|
910
|
+
};
|
|
911
|
+
|
|
792
912
|
const collectIgnoreFileArtifactsAsync = async (patterns, options, includeParentIgnoreFiles) => {
|
|
793
913
|
const normalizedOptions = normalizeOptions(options);
|
|
794
|
-
const
|
|
914
|
+
const readFileMethod = getReadFileMethod(normalizedOptions.fs);
|
|
795
915
|
const gitRoot = includeParentIgnoreFiles
|
|
796
916
|
? await findGitRoot(normalizedOptions.cwd, normalizedOptions.fs)
|
|
797
917
|
: undefined;
|
|
798
|
-
const allPaths = combineIgnoreFilePaths(gitRoot, normalizedOptions, childPaths);
|
|
799
|
-
const readFileMethod = getReadFileMethod(normalizedOptions.fs);
|
|
800
|
-
const files = await readIgnoreFilesSafely(allPaths, readFileMethod, normalizedOptions.suppressErrors);
|
|
801
918
|
|
|
802
|
-
|
|
919
|
+
const knownPaths = await filterKnownIgnoreFilePathsAsync(
|
|
920
|
+
getKnownIgnoreFilePaths(patterns, normalizedOptions, gitRoot),
|
|
921
|
+
patterns,
|
|
922
|
+
normalizedOptions,
|
|
923
|
+
);
|
|
924
|
+
const knownFiles = await readIgnoreFilesSafely(knownPaths, readFileMethod, normalizedOptions.suppressErrors);
|
|
925
|
+
const {patterns: prunePatterns, guardNames} = getIgnoreFileSearchPrune(patterns, knownFiles, normalizedOptions, gitRoot);
|
|
926
|
+
|
|
927
|
+
const childPaths = await globIgnoreFiles(fastGlob, patterns, withPrunedSearch(normalizedOptions, prunePatterns));
|
|
928
|
+
let childFiles = await readIgnoreFilesSafely(getUnreadPaths(childPaths, knownPaths), readFileMethod, normalizedOptions.suppressErrors);
|
|
929
|
+
|
|
930
|
+
// A negation found by the pruned search can re-include a directory the prune patterns skipped, hiding the ignore files inside it. Repeat the search without pruning when that happens; a single unpruned pass finds everything, so once is enough.
|
|
931
|
+
if (negationsCouldRescue(getIgnoreRules(childFiles), guardNames)) {
|
|
932
|
+
const allPaths = await globIgnoreFiles(fastGlob, patterns, normalizedOptions);
|
|
933
|
+
childFiles = await readIgnoreFilesSafely(getUnreadPaths(allPaths, knownPaths), readFileMethod, normalizedOptions.suppressErrors);
|
|
934
|
+
}
|
|
935
|
+
|
|
936
|
+
return {files: [...knownFiles, ...childFiles], normalizedOptions, gitRoot};
|
|
803
937
|
};
|
|
804
938
|
|
|
805
939
|
const collectIgnoreFileArtifactsSync = (patterns, options, includeParentIgnoreFiles) => {
|
|
806
940
|
const normalizedOptions = normalizeOptions(options);
|
|
807
|
-
const
|
|
941
|
+
const readFileSyncMethod = getReadFileSyncMethod(normalizedOptions.fs);
|
|
808
942
|
const gitRoot = includeParentIgnoreFiles
|
|
809
943
|
? findGitRootSync(normalizedOptions.cwd, normalizedOptions.fs)
|
|
810
944
|
: undefined;
|
|
811
|
-
const allPaths = combineIgnoreFilePaths(gitRoot, normalizedOptions, childPaths);
|
|
812
|
-
const readFileSyncMethod = getReadFileSyncMethod(normalizedOptions.fs);
|
|
813
|
-
const files = readIgnoreFilesSafelySync(allPaths, readFileSyncMethod, normalizedOptions.suppressErrors);
|
|
814
945
|
|
|
815
|
-
|
|
946
|
+
const knownPaths = filterKnownIgnoreFilePathsSync(
|
|
947
|
+
getKnownIgnoreFilePaths(patterns, normalizedOptions, gitRoot),
|
|
948
|
+
patterns,
|
|
949
|
+
normalizedOptions,
|
|
950
|
+
);
|
|
951
|
+
const knownFiles = readIgnoreFilesSafelySync(knownPaths, readFileSyncMethod, normalizedOptions.suppressErrors);
|
|
952
|
+
const {patterns: prunePatterns, guardNames} = getIgnoreFileSearchPrune(patterns, knownFiles, normalizedOptions, gitRoot);
|
|
953
|
+
|
|
954
|
+
const childPaths = globIgnoreFiles(fastGlob.sync, patterns, withPrunedSearch(normalizedOptions, prunePatterns));
|
|
955
|
+
let childFiles = readIgnoreFilesSafelySync(getUnreadPaths(childPaths, knownPaths), readFileSyncMethod, normalizedOptions.suppressErrors);
|
|
956
|
+
|
|
957
|
+
// See `collectIgnoreFileArtifactsAsync`: a rescuing negation means the pruned search may have missed files.
|
|
958
|
+
if (negationsCouldRescue(getIgnoreRules(childFiles), guardNames)) {
|
|
959
|
+
const allPaths = globIgnoreFiles(fastGlob.sync, patterns, normalizedOptions);
|
|
960
|
+
childFiles = readIgnoreFilesSafelySync(getUnreadPaths(allPaths, knownPaths), readFileSyncMethod, normalizedOptions.suppressErrors);
|
|
961
|
+
}
|
|
962
|
+
|
|
963
|
+
return {files: [...knownFiles, ...childFiles], normalizedOptions, gitRoot};
|
|
816
964
|
};
|
|
817
965
|
|
|
818
966
|
export const isIgnoredByIgnoreFiles = async (patterns, options) => {
|
|
@@ -834,7 +982,7 @@ This avoids reading the same files twice (once for patterns, once for filtering)
|
|
|
834
982
|
@param {string[]} patterns - Patterns to find ignore files
|
|
835
983
|
@param {Object} options - Options object
|
|
836
984
|
@param {boolean} [includeParentIgnoreFiles=false] - Whether to search for parent .gitignore files
|
|
837
|
-
@returns {Promise<{patterns: string[], matcher: Function, predicate: Function, usingGitRoot: boolean}>}
|
|
985
|
+
@returns {Promise<{patterns: string[], rules: Array<{pattern: string, directory: string}>, matcher: Function, predicate: Function, usingGitRoot: boolean}>}
|
|
838
986
|
*/
|
|
839
987
|
export const getIgnorePatternsAndPredicate = async (patterns, options, includeParentIgnoreFiles = false) => {
|
|
840
988
|
const {files, normalizedOptions, gitRoot} = await collectIgnoreFileArtifactsAsync(
|
|
@@ -852,7 +1000,7 @@ Read ignore files and return both patterns and predicate (sync version).
|
|
|
852
1000
|
@param {string[]} patterns - Patterns to find ignore files
|
|
853
1001
|
@param {Object} options - Options object
|
|
854
1002
|
@param {boolean} [includeParentIgnoreFiles=false] - Whether to search for parent .gitignore files
|
|
855
|
-
@returns {{patterns: string[], matcher: Function, predicate: Function, usingGitRoot: boolean}}
|
|
1003
|
+
@returns {{patterns: string[], rules: Array<{pattern: string, directory: string}>, matcher: Function, predicate: Function, usingGitRoot: boolean}}
|
|
856
1004
|
*/
|
|
857
1005
|
export const getIgnorePatternsAndPredicateSync = (patterns, options, includeParentIgnoreFiles = false) => {
|
|
858
1006
|
const {files, normalizedOptions, gitRoot} = collectIgnoreFileArtifactsSync(
|
package/index.js
CHANGED
|
@@ -19,7 +19,6 @@ import {
|
|
|
19
19
|
isNegativePattern,
|
|
20
20
|
getStaticAbsolutePathPrefix,
|
|
21
21
|
normalizeNegativePattern,
|
|
22
|
-
normalizeDirectoryPatternForFastGlob,
|
|
23
22
|
adjustIgnorePatternsForParentDirectories,
|
|
24
23
|
convertPatternsForFastGlob,
|
|
25
24
|
findGitRoot,
|
|
@@ -233,31 +232,28 @@ const combinePredicate = (matcher, globalMatcher) => {
|
|
|
233
232
|
};
|
|
234
233
|
};
|
|
235
234
|
|
|
236
|
-
const buildIgnoreFilterResult = (options, cwd, {
|
|
235
|
+
const buildIgnoreFilterResult = ({options, cwd, ignoreResult: {rules, matcher}, globalMatcher, createFilter}) => {
|
|
237
236
|
const finalPredicate = combinePredicate(matcher, globalMatcher);
|
|
238
237
|
|
|
239
|
-
//
|
|
240
|
-
const
|
|
238
|
+
// Patterns fast-glob can use to skip ignored directories while traversing. The predicate below stays authoritative, so this only ever needs to be safe, never exhaustive. They are returned separately from `options.ignore` so they skip directory expansion and the parent-directory adjustment, which would rebase them outside the scope their ignore files govern.
|
|
239
|
+
const pruneIgnorePatterns = convertPatternsForFastGlob(rules, matcher, cwd);
|
|
241
240
|
|
|
242
241
|
return {
|
|
243
|
-
options
|
|
244
|
-
|
|
245
|
-
ignore: [...options.ignore, ...patternsForFastGlob],
|
|
246
|
-
},
|
|
242
|
+
options,
|
|
243
|
+
pruneIgnorePatterns,
|
|
247
244
|
filter: createFilter(finalPredicate, cwd, options.fs),
|
|
248
245
|
};
|
|
249
246
|
};
|
|
250
247
|
|
|
251
248
|
/**
|
|
252
|
-
Apply
|
|
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.
|
|
249
|
+
Apply ignore files to options and return the filter predicate.
|
|
255
250
|
|
|
256
|
-
|
|
251
|
+
The predicate handles every rule, including negations, and is the authoritative filter applied
|
|
252
|
+
to fast-glob's results. Rules that provably cannot be affected by negations are additionally
|
|
253
|
+
translated into fast-glob `ignore` patterns, so whole ignored directories are skipped during
|
|
254
|
+
traversal; see `convertPatternsForFastGlob`.
|
|
257
255
|
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
@returns {Promise<{options: Object, filter: Function}>}
|
|
256
|
+
@returns {Promise<{options: Object, pruneIgnorePatterns: string[], filter: Function}>}
|
|
261
257
|
*/
|
|
262
258
|
const applyIgnoreFilesAndGetFilter = async options => {
|
|
263
259
|
const cwd = options.cwd ?? process.cwd();
|
|
@@ -267,6 +263,7 @@ const applyIgnoreFilesAndGetFilter = async options => {
|
|
|
267
263
|
if (ignoreFilesPatterns.length === 0 && !globalIgnoreFile) {
|
|
268
264
|
return {
|
|
269
265
|
options,
|
|
266
|
+
pruneIgnorePatterns: [],
|
|
270
267
|
filter: createFilterFunctionAsync(false, cwd, options.fs),
|
|
271
268
|
};
|
|
272
269
|
}
|
|
@@ -276,18 +273,24 @@ const applyIgnoreFilesAndGetFilter = async options => {
|
|
|
276
273
|
const includeParentIgnoreFiles = options.gitignore === true;
|
|
277
274
|
const ignoreResult = ignoreFilesPatterns.length > 0
|
|
278
275
|
? await getIgnorePatternsAndPredicate(ignoreFilesPatterns, options, includeParentIgnoreFiles)
|
|
279
|
-
: {
|
|
276
|
+
: {rules: [], matcher: false};
|
|
280
277
|
|
|
281
278
|
const globalGitRoot = globalIgnoreFile ? await findGitRoot(cwd, options.fs) : undefined;
|
|
282
279
|
const globalMatcher = globalIgnoreFile ? buildGlobalMatcher(globalIgnoreFile, cwd, globalGitRoot ?? cwd) : undefined;
|
|
283
280
|
|
|
284
|
-
return buildIgnoreFilterResult(
|
|
281
|
+
return buildIgnoreFilterResult({
|
|
282
|
+
options,
|
|
283
|
+
cwd,
|
|
284
|
+
ignoreResult,
|
|
285
|
+
globalMatcher,
|
|
286
|
+
createFilter: createFilterFunctionAsync,
|
|
287
|
+
});
|
|
285
288
|
};
|
|
286
289
|
|
|
287
290
|
/**
|
|
288
|
-
Apply
|
|
291
|
+
Apply ignore files to options and return the filter predicate (sync version).
|
|
289
292
|
|
|
290
|
-
@returns {{options: Object, filter: Function}}
|
|
293
|
+
@returns {{options: Object, pruneIgnorePatterns: string[], filter: Function}}
|
|
291
294
|
*/
|
|
292
295
|
const applyIgnoreFilesAndGetFilterSync = options => {
|
|
293
296
|
const cwd = options.cwd ?? process.cwd();
|
|
@@ -297,6 +300,7 @@ const applyIgnoreFilesAndGetFilterSync = options => {
|
|
|
297
300
|
if (ignoreFilesPatterns.length === 0 && !globalIgnoreFile) {
|
|
298
301
|
return {
|
|
299
302
|
options,
|
|
303
|
+
pruneIgnorePatterns: [],
|
|
300
304
|
filter: createFilterFunction(false, cwd, options.fs),
|
|
301
305
|
};
|
|
302
306
|
}
|
|
@@ -306,12 +310,18 @@ const applyIgnoreFilesAndGetFilterSync = options => {
|
|
|
306
310
|
const includeParentIgnoreFiles = options.gitignore === true;
|
|
307
311
|
const ignoreResult = ignoreFilesPatterns.length > 0
|
|
308
312
|
? getIgnorePatternsAndPredicateSync(ignoreFilesPatterns, options, includeParentIgnoreFiles)
|
|
309
|
-
: {
|
|
313
|
+
: {rules: [], matcher: false};
|
|
310
314
|
|
|
311
315
|
const globalGitRoot = globalIgnoreFile ? findGitRootSync(cwd, options.fs) : undefined;
|
|
312
316
|
const globalMatcher = globalIgnoreFile ? buildGlobalMatcher(globalIgnoreFile, cwd, globalGitRoot ?? cwd) : undefined;
|
|
313
317
|
|
|
314
|
-
return buildIgnoreFilterResult(
|
|
318
|
+
return buildIgnoreFilterResult({
|
|
319
|
+
options,
|
|
320
|
+
cwd,
|
|
321
|
+
ignoreResult,
|
|
322
|
+
globalMatcher,
|
|
323
|
+
createFilter: createFilterFunction,
|
|
324
|
+
});
|
|
315
325
|
};
|
|
316
326
|
|
|
317
327
|
const assertGlobalGitignoreSyncSupport = options => {
|
|
@@ -545,18 +555,31 @@ const applyParentDirectoryIgnoreAdjustments = tasks => tasks.map(task => ({
|
|
|
545
555
|
},
|
|
546
556
|
}));
|
|
547
557
|
|
|
558
|
+
// Prune patterns are appended after directory expansion and the parent-directory adjustment on
|
|
559
|
+
// purpose: they are already glob-shaped, and rebasing them onto a `../` prefix would let them
|
|
560
|
+
// ignore paths outside the scope of the ignore files they came from.
|
|
561
|
+
const appendPruneIgnorePatterns = (tasks, pruneIgnorePatterns) => pruneIgnorePatterns.length === 0
|
|
562
|
+
? tasks
|
|
563
|
+
: tasks.map(task => ({
|
|
564
|
+
patterns: task.patterns,
|
|
565
|
+
options: {
|
|
566
|
+
...task.options,
|
|
567
|
+
ignore: [...task.options.ignore, ...pruneIgnorePatterns],
|
|
568
|
+
},
|
|
569
|
+
}));
|
|
570
|
+
|
|
548
571
|
const normalizeExpandDirectoriesOption = (options, cwd) => ({
|
|
549
572
|
...(cwd ? {cwd} : {}),
|
|
550
573
|
...(Array.isArray(options) ? {files: options} : options),
|
|
551
574
|
});
|
|
552
575
|
|
|
553
|
-
const generateTasks = async (patterns, options) => {
|
|
576
|
+
const generateTasks = async (patterns, options, pruneIgnorePatterns = []) => {
|
|
554
577
|
const globTasks = convertNegativePatterns(patterns, options);
|
|
555
578
|
|
|
556
579
|
const {cwd, expandDirectories, fs: fsImplementation} = options;
|
|
557
580
|
|
|
558
581
|
if (!expandDirectories) {
|
|
559
|
-
return applyParentDirectoryIgnoreAdjustments(globTasks);
|
|
582
|
+
return appendPruneIgnorePatterns(applyParentDirectoryIgnoreAdjustments(globTasks), pruneIgnorePatterns);
|
|
560
583
|
}
|
|
561
584
|
|
|
562
585
|
const directoryToGlobOptions = {
|
|
@@ -564,7 +587,7 @@ const generateTasks = async (patterns, options) => {
|
|
|
564
587
|
fs: fsImplementation,
|
|
565
588
|
};
|
|
566
589
|
|
|
567
|
-
|
|
590
|
+
const tasks = await Promise.all(globTasks.map(async task => {
|
|
568
591
|
let {patterns, options} = task;
|
|
569
592
|
|
|
570
593
|
[
|
|
@@ -580,14 +603,16 @@ const generateTasks = async (patterns, options) => {
|
|
|
580
603
|
|
|
581
604
|
return {patterns, options};
|
|
582
605
|
}));
|
|
606
|
+
|
|
607
|
+
return appendPruneIgnorePatterns(tasks, pruneIgnorePatterns);
|
|
583
608
|
};
|
|
584
609
|
|
|
585
|
-
const generateTasksSync = (patterns, options) => {
|
|
610
|
+
const generateTasksSync = (patterns, options, pruneIgnorePatterns = []) => {
|
|
586
611
|
const globTasks = convertNegativePatterns(patterns, options);
|
|
587
612
|
const {cwd, expandDirectories, fs: fsImplementation} = options;
|
|
588
613
|
|
|
589
614
|
if (!expandDirectories) {
|
|
590
|
-
return applyParentDirectoryIgnoreAdjustments(globTasks);
|
|
615
|
+
return appendPruneIgnorePatterns(applyParentDirectoryIgnoreAdjustments(globTasks), pruneIgnorePatterns);
|
|
591
616
|
}
|
|
592
617
|
|
|
593
618
|
const directoryToGlobSyncOptions = {
|
|
@@ -595,7 +620,7 @@ const generateTasksSync = (patterns, options) => {
|
|
|
595
620
|
fs: fsImplementation,
|
|
596
621
|
};
|
|
597
622
|
|
|
598
|
-
|
|
623
|
+
const tasks = globTasks.map(task => {
|
|
599
624
|
let {patterns, options} = task;
|
|
600
625
|
patterns = directoryToGlobSync(patterns, directoryToGlobSyncOptions);
|
|
601
626
|
options.ignore = directoryToGlobSync(options.ignore, {cwd, fs: fsImplementation});
|
|
@@ -605,16 +630,18 @@ const generateTasksSync = (patterns, options) => {
|
|
|
605
630
|
|
|
606
631
|
return {patterns, options};
|
|
607
632
|
});
|
|
633
|
+
|
|
634
|
+
return appendPruneIgnorePatterns(tasks, pruneIgnorePatterns);
|
|
608
635
|
};
|
|
609
636
|
|
|
610
637
|
export const globby = normalizeArguments(async (patterns, options) => {
|
|
611
638
|
assertGlobalGitignoreAsyncSupport(options);
|
|
612
639
|
|
|
613
640
|
// Apply ignore files and get filter (reads .gitignore files once)
|
|
614
|
-
const {options: modifiedOptions, filter} = await applyIgnoreFilesAndGetFilter(options);
|
|
641
|
+
const {options: modifiedOptions, pruneIgnorePatterns, filter} = await applyIgnoreFilesAndGetFilter(options);
|
|
615
642
|
|
|
616
|
-
// Generate tasks
|
|
617
|
-
const tasks = await generateTasks(patterns, modifiedOptions);
|
|
643
|
+
// Generate tasks, attaching the prune patterns so fast-glob skips ignored directories
|
|
644
|
+
const tasks = await generateTasks(patterns, modifiedOptions, pruneIgnorePatterns);
|
|
618
645
|
|
|
619
646
|
const results = await Promise.all(tasks.map(task => fastGlob(task.patterns, task.options)));
|
|
620
647
|
return unionFastGlobResultsAsync(results, filter);
|
|
@@ -624,10 +651,10 @@ export const globbySync = normalizeArgumentsSync((patterns, options) => {
|
|
|
624
651
|
assertGlobalGitignoreSyncSupport(options);
|
|
625
652
|
|
|
626
653
|
// Apply ignore files and get filter (reads .gitignore files once)
|
|
627
|
-
const {options: modifiedOptions, filter} = applyIgnoreFilesAndGetFilterSync(options);
|
|
654
|
+
const {options: modifiedOptions, pruneIgnorePatterns, filter} = applyIgnoreFilesAndGetFilterSync(options);
|
|
628
655
|
|
|
629
|
-
// Generate tasks
|
|
630
|
-
const tasks = generateTasksSync(patterns, modifiedOptions);
|
|
656
|
+
// Generate tasks, attaching the prune patterns so fast-glob skips ignored directories
|
|
657
|
+
const tasks = generateTasksSync(patterns, modifiedOptions, pruneIgnorePatterns);
|
|
631
658
|
|
|
632
659
|
const results = tasks.map(task => fastGlob.sync(task.patterns, task.options));
|
|
633
660
|
return unionFastGlobResults(results, filter);
|
|
@@ -639,10 +666,10 @@ export const globbyStream = normalizeArgumentsSync((patterns, options) => {
|
|
|
639
666
|
const seen = new Set();
|
|
640
667
|
const stream = Readable.from((async function * () {
|
|
641
668
|
// Apply ignore files and get filter (reads .gitignore files once)
|
|
642
|
-
const {options: modifiedOptions, filter} = await applyIgnoreFilesAndGetFilter(options);
|
|
669
|
+
const {options: modifiedOptions, pruneIgnorePatterns, filter} = await applyIgnoreFilesAndGetFilter(options);
|
|
643
670
|
|
|
644
|
-
// Generate tasks
|
|
645
|
-
const tasks = await generateTasks(patterns, modifiedOptions);
|
|
671
|
+
// Generate tasks, attaching the prune patterns so fast-glob skips ignored directories
|
|
672
|
+
const tasks = await generateTasks(patterns, modifiedOptions, pruneIgnorePatterns);
|
|
646
673
|
|
|
647
674
|
if (tasks.length === 0) {
|
|
648
675
|
return;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cjser/globby",
|
|
3
|
-
"version": "16.2.
|
|
3
|
+
"version": "16.2.3-cjser.2",
|
|
4
4
|
"description": "User-friendly glob matching",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
@@ -99,11 +99,11 @@
|
|
|
99
99
|
"types": "./index.d.ts",
|
|
100
100
|
"main": "./dist-cjser/index.cjs",
|
|
101
101
|
"cjser": {
|
|
102
|
-
"sourceVersion": "16.2.
|
|
102
|
+
"sourceVersion": "16.2.3",
|
|
103
103
|
"cjserVersion": 2,
|
|
104
104
|
"original": {
|
|
105
105
|
"name": "globby",
|
|
106
|
-
"version": "16.2.
|
|
106
|
+
"version": "16.2.3",
|
|
107
107
|
"exports": {
|
|
108
108
|
"types": "./index.d.ts",
|
|
109
109
|
"default": "./index.js"
|
package/readme.md
CHANGED
|
@@ -93,7 +93,7 @@ When enabled, globby searches for `.gitignore` files from the current working di
|
|
|
93
93
|
|
|
94
94
|
Gitignore patterns take priority over user patterns, matching Git's behavior. To include gitignored files, set this to `false`.
|
|
95
95
|
|
|
96
|
-
**Performance:** Globby reads `.gitignore` files before globbing
|
|
96
|
+
**Performance:** Globby reads `.gitignore` files before globbing and hands fast-glob patterns for the directories it can prove are ignored, so whole ignored directories (like large `node_modules` or build outputs) are skipped during traversal instead of being enumerated and filtered afterwards. This holds even when negation patterns (like `!important.log`) or parent `.gitignore` files are present: only the rules that provably cannot be re-included by a negation are used to skip directories, while the final filtering always matches Git's behavior. To read fewer ignore files, use `ignoreFiles: '.gitignore'` to target only the root ignore file.
|
|
97
97
|
|
|
98
98
|
##### globalGitignore
|
|
99
99
|
|