@cjser/globby 16.2.0-cjser.2 → 16.2.2-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 +270 -61
- package/ignore.js +172 -24
- package/index.js +63 -36
- package/package.json +3 -3
- package/readme.md +1 -1
- package/utilities.js +201 -29
package/dist-cjser/index.cjs
CHANGED
|
@@ -26,7 +26,7 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
|
|
|
26
26
|
));
|
|
27
27
|
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
28
28
|
|
|
29
|
-
// packages/@cjser/globby.tmp-26-
|
|
29
|
+
// packages/@cjser/globby.tmp-26-1784218928215/index.js
|
|
30
30
|
var index_exports = {};
|
|
31
31
|
__export(index_exports, {
|
|
32
32
|
convertPathToPattern: () => convertPathToPattern,
|
|
@@ -50,24 +50,26 @@ var import_sindresorhus_merge_streams = __toESM(require("@cjser/sindresorhus__me
|
|
|
50
50
|
var import_fast_glob3 = __toESM(require("fast-glob"), 1);
|
|
51
51
|
var import_node2 = require("@cjser/unicorn-magic/node");
|
|
52
52
|
|
|
53
|
-
// packages/@cjser/globby.tmp-26-
|
|
53
|
+
// packages/@cjser/globby.tmp-26-1784218928215/ignore.js
|
|
54
54
|
var import_node_process = __toESM(require("node:process"), 1);
|
|
55
55
|
var import_node_fs2 = __toESM(require("node:fs"), 1);
|
|
56
56
|
var import_promises = __toESM(require("node:fs/promises"), 1);
|
|
57
57
|
var import_node_path2 = __toESM(require("node:path"), 1);
|
|
58
58
|
var import_node_os = __toESM(require("node:os"), 1);
|
|
59
59
|
var import_fast_glob2 = __toESM(require("fast-glob"), 1);
|
|
60
|
-
var
|
|
60
|
+
var import_ignore2 = __toESM(require("ignore"), 1);
|
|
61
61
|
var import_is_path_inside2 = __toESM(require("@cjser/is-path-inside"), 1);
|
|
62
|
-
var
|
|
62
|
+
var import_slash2 = __toESM(require("@cjser/slash"), 1);
|
|
63
63
|
var import_node = require("@cjser/unicorn-magic/node");
|
|
64
64
|
|
|
65
|
-
// packages/@cjser/globby.tmp-26-
|
|
65
|
+
// packages/@cjser/globby.tmp-26-1784218928215/utilities.js
|
|
66
66
|
var import_node_fs = __toESM(require("node:fs"), 1);
|
|
67
67
|
var import_node_path = __toESM(require("node:path"), 1);
|
|
68
68
|
var import_node_util = require("node:util");
|
|
69
69
|
var import_fast_glob = __toESM(require("fast-glob"), 1);
|
|
70
|
+
var import_ignore = __toESM(require("ignore"), 1);
|
|
70
71
|
var import_is_path_inside = __toESM(require("@cjser/is-path-inside"), 1);
|
|
72
|
+
var import_slash = __toESM(require("@cjser/slash"), 1);
|
|
71
73
|
var isNegativePattern = (pattern) => pattern[0] === "!";
|
|
72
74
|
var normalizeAbsolutePatternToRelative = (pattern) => {
|
|
73
75
|
if (!pattern.startsWith("/")) {
|
|
@@ -268,23 +270,113 @@ var getParentGitignorePaths = (gitRoot, cwd) => {
|
|
|
268
270
|
const chain = buildPathChain(import_node_path.default.resolve(cwd), import_node_path.default.resolve(gitRoot));
|
|
269
271
|
return [...chain].reverse().map((directory) => import_node_path.default.join(directory, ".gitignore"));
|
|
270
272
|
};
|
|
271
|
-
var
|
|
272
|
-
|
|
273
|
-
|
|
273
|
+
var GITIGNORE_WILDCARDS = /(?<!\\)[*?[]/u;
|
|
274
|
+
var hasGitignoreWildcards = (value) => GITIGNORE_WILDCARDS.test(value);
|
|
275
|
+
var MICROMATCH_ONLY_SYNTAX = /[(){}|\\]/u;
|
|
276
|
+
var unescapeGitignorePattern = (value) => value.replaceAll(/\\(.)/gu, "$1");
|
|
277
|
+
var toLiteralPattern = (value) => import_fast_glob.default.escapePath(unescapeGitignorePattern(value));
|
|
278
|
+
var finalSegment = (value) => value.replace(/\/+$/u, "").split("/").pop();
|
|
279
|
+
var isInsideCwd = (relativePath) => relativePath !== "" && !relativePath.startsWith("..") && !import_node_path.default.isAbsolute(relativePath);
|
|
280
|
+
var anchorToCwd = (directory, body, cwd) => {
|
|
281
|
+
const relativePath = (0, import_slash.default)(import_node_path.default.relative(cwd, import_node_path.default.join(directory, body)));
|
|
282
|
+
return isInsideCwd(relativePath) ? relativePath : void 0;
|
|
283
|
+
};
|
|
284
|
+
var createNameComparer = () => {
|
|
285
|
+
const nameMatchers = /* @__PURE__ */ new Map();
|
|
286
|
+
const matchesName = (pattern, name) => {
|
|
287
|
+
let nameMatcher = nameMatchers.get(pattern);
|
|
288
|
+
if (!nameMatcher) {
|
|
289
|
+
nameMatcher = (0, import_ignore.default)().add([pattern]);
|
|
290
|
+
nameMatchers.set(pattern, nameMatcher);
|
|
291
|
+
}
|
|
292
|
+
return nameMatcher.ignores(name);
|
|
293
|
+
};
|
|
294
|
+
return (pattern, name) => {
|
|
295
|
+
if (hasGitignoreWildcards(pattern) && hasGitignoreWildcards(name)) {
|
|
296
|
+
return true;
|
|
297
|
+
}
|
|
298
|
+
return hasGitignoreWildcards(name) ? matchesName(name, pattern) : matchesName(pattern, name);
|
|
299
|
+
};
|
|
300
|
+
};
|
|
301
|
+
var getNegationFinalSegments = (rules) => rules.filter((rule) => isNegativePattern(rule.pattern)).map((rule) => finalSegment(rule.pattern.slice(1))).filter(Boolean);
|
|
302
|
+
var negationsCouldRescue = (rules, names) => {
|
|
303
|
+
if (names.length === 0) {
|
|
304
|
+
return false;
|
|
274
305
|
}
|
|
275
|
-
const
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
306
|
+
const couldNameTheSamePath = createNameComparer();
|
|
307
|
+
return getNegationFinalSegments(rules).some((negation) => names.some((name) => couldNameTheSamePath(name, negation)));
|
|
308
|
+
};
|
|
309
|
+
var getRulePrune = ({ pattern, directory }, { cwd, matcher, hasNegations, canSkipAtAnyDepth, canMatchIgnoreFile, gitignoreOnlySearch }) => {
|
|
310
|
+
if (isNegativePattern(pattern)) {
|
|
311
|
+
return void 0;
|
|
312
|
+
}
|
|
313
|
+
const isDirectoryPattern = pattern.endsWith("/");
|
|
314
|
+
const clean = pattern.replace(/\/+$/u, "");
|
|
315
|
+
if (!clean) {
|
|
316
|
+
return void 0;
|
|
317
|
+
}
|
|
318
|
+
const body = clean.startsWith("**/") && !clean.slice(3).includes("/") ? clean.slice(3) : clean;
|
|
319
|
+
if (canMatchIgnoreFile(finalSegment(body))) {
|
|
320
|
+
return void 0;
|
|
321
|
+
}
|
|
322
|
+
const isGlob = hasGitignoreWildcards(body);
|
|
323
|
+
if (isGlob && MICROMATCH_ONLY_SYNTAX.test(body)) {
|
|
324
|
+
return void 0;
|
|
325
|
+
}
|
|
326
|
+
const toFastGlob = (value) => normalizeDirectoryPatternForFastGlob(`/${value}${isDirectoryPattern ? "/" : ""}`).replace(/^\//u, "");
|
|
327
|
+
if (!body.includes("/") && canSkipAtAnyDepth(body)) {
|
|
328
|
+
const relativeDirectory = (0, import_slash.default)(import_node_path.default.relative(cwd, directory));
|
|
329
|
+
const prefix = isInsideCwd(relativeDirectory) ? `${import_fast_glob.default.escapePath(relativeDirectory)}/` : "";
|
|
330
|
+
return { pattern: toFastGlob(`${prefix}**/${isGlob ? body : toLiteralPattern(body)}`), guardName: body };
|
|
331
|
+
}
|
|
332
|
+
const anchoredBody = body.replace(/^\//u, "");
|
|
333
|
+
const target = anchorToCwd(directory, isGlob ? anchoredBody : unescapeGitignorePattern(anchoredBody), cwd);
|
|
334
|
+
if (target === void 0) {
|
|
335
|
+
return void 0;
|
|
336
|
+
}
|
|
337
|
+
if (isGlob) {
|
|
338
|
+
return hasNegations ? void 0 : { pattern: toFastGlob(target), guardName: finalSegment(target) };
|
|
339
|
+
}
|
|
340
|
+
if (!matcher(import_node_path.default.resolve(cwd, target) + import_node_path.default.sep).ignored) {
|
|
341
|
+
return void 0;
|
|
342
|
+
}
|
|
343
|
+
const needsGuard = !gitignoreOnlySearch || target.includes("/");
|
|
344
|
+
return {
|
|
345
|
+
pattern: toFastGlob(import_fast_glob.default.escapePath(target)),
|
|
346
|
+
guardName: needsGuard ? finalSegment(target) : void 0
|
|
347
|
+
};
|
|
348
|
+
};
|
|
349
|
+
var buildPrunePatternsAndGuards = (rules, matcher, cwd, { gitignoreOnlySearch = false, searchesForGitignoreFiles = false } = {}) => {
|
|
350
|
+
if (!matcher || !cwd || !rules || rules.length === 0) {
|
|
351
|
+
return { patterns: [], guardNames: [] };
|
|
352
|
+
}
|
|
353
|
+
const negationNames = getNegationFinalSegments(rules);
|
|
354
|
+
const couldNameTheSamePath = createNameComparer();
|
|
355
|
+
const context = {
|
|
356
|
+
cwd,
|
|
357
|
+
matcher,
|
|
358
|
+
hasNegations: negationNames.length > 0,
|
|
359
|
+
canSkipAtAnyDepth: (pattern) => !negationNames.some((name) => couldNameTheSamePath(pattern, name)),
|
|
360
|
+
canMatchIgnoreFile: (pattern) => searchesForGitignoreFiles && couldNameTheSamePath(pattern, ".gitignore"),
|
|
361
|
+
gitignoreOnlySearch
|
|
362
|
+
};
|
|
363
|
+
const patterns = [];
|
|
364
|
+
const guardNames = [];
|
|
365
|
+
for (const rule of rules) {
|
|
366
|
+
const prune = getRulePrune(rule, context);
|
|
367
|
+
if (!prune) {
|
|
368
|
+
continue;
|
|
369
|
+
}
|
|
370
|
+
patterns.push(prune.pattern);
|
|
371
|
+
if (prune.guardName !== void 0) {
|
|
372
|
+
guardNames.push(prune.guardName);
|
|
281
373
|
}
|
|
282
|
-
result.push(normalizeDirectoryPatternForFastGlob2(pattern));
|
|
283
374
|
}
|
|
284
|
-
return
|
|
375
|
+
return { patterns, guardNames };
|
|
285
376
|
};
|
|
377
|
+
var convertPatternsForFastGlob = (rules, matcher, cwd) => buildPrunePatternsAndGuards(rules, matcher, cwd).patterns;
|
|
286
378
|
|
|
287
|
-
// packages/@cjser/globby.tmp-26-
|
|
379
|
+
// packages/@cjser/globby.tmp-26-1784218928215/ignore.js
|
|
288
380
|
var defaultIgnoredDirectories = [
|
|
289
381
|
"**/node_modules",
|
|
290
382
|
"**/flow-typed",
|
|
@@ -358,17 +450,33 @@ var globIgnoreFiles = (globFunction, patterns, normalizedOptions) => globFunctio
|
|
|
358
450
|
...ignoreFilesGlobOptions
|
|
359
451
|
// Must be last to ensure absolute/dot flags stick
|
|
360
452
|
});
|
|
361
|
-
var
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
453
|
+
var normalizeIgnoreFileLine = (line) => {
|
|
454
|
+
line = line.replace(/^\uFEFF/u, "");
|
|
455
|
+
let whitespaceStart = line.length;
|
|
456
|
+
while (whitespaceStart > 0 && /\s/u.test(line[whitespaceStart - 1])) {
|
|
457
|
+
whitespaceStart--;
|
|
458
|
+
}
|
|
459
|
+
if (whitespaceStart === line.length) {
|
|
460
|
+
return line;
|
|
461
|
+
}
|
|
462
|
+
let backslashCount = 0;
|
|
463
|
+
for (let index = whitespaceStart - 1; index >= 0 && line[index] === "\\"; index--) {
|
|
464
|
+
backslashCount++;
|
|
465
|
+
}
|
|
466
|
+
return backslashCount % 2 === 1 ? line.slice(0, whitespaceStart) + " " : line.slice(0, whitespaceStart);
|
|
467
|
+
};
|
|
468
|
+
var readIgnoreFileLines = (content) => content.split(/\r?\n/).map((line) => normalizeIgnoreFileLine(line)).filter((line) => line && !line.startsWith("#"));
|
|
469
|
+
var getIgnoreRules = (files) => files.flatMap((file) => {
|
|
470
|
+
const directory = import_node_path2.default.dirname(file.filePath);
|
|
471
|
+
return readIgnoreFileLines(file.content).map((pattern) => ({ pattern, directory }));
|
|
472
|
+
});
|
|
366
473
|
var buildIgnoreResult = (files, normalizedOptions, gitRoot) => {
|
|
367
474
|
const baseDir = gitRoot || normalizedOptions.cwd;
|
|
368
475
|
const patterns = getPatternsFromIgnoreFiles(files, baseDir);
|
|
369
476
|
const matcher = createIgnoreMatcher(patterns, normalizedOptions.cwd, baseDir);
|
|
370
477
|
return {
|
|
371
478
|
patterns,
|
|
479
|
+
rules: getIgnoreRules(files),
|
|
372
480
|
matcher,
|
|
373
481
|
predicate: (fileOrDirectory) => matcher(fileOrDirectory).ignored,
|
|
374
482
|
usingGitRoot: Boolean(gitRoot && gitRoot !== normalizedOptions.cwd)
|
|
@@ -393,8 +501,8 @@ var applyBaseToPattern = (pattern, base) => {
|
|
|
393
501
|
return isNegative ? "!" + result : result;
|
|
394
502
|
};
|
|
395
503
|
var parseIgnoreFile = (file, cwd) => {
|
|
396
|
-
const base = (0,
|
|
397
|
-
return file.content
|
|
504
|
+
const base = (0, import_slash2.default)(import_node_path2.default.relative(cwd, import_node_path2.default.dirname(file.filePath)));
|
|
505
|
+
return readIgnoreFileLines(file.content).map((pattern) => applyBaseToPattern(pattern, base));
|
|
398
506
|
};
|
|
399
507
|
var toRelativePath = (fileOrDirectory, cwd) => {
|
|
400
508
|
if (import_node_path2.default.isAbsolute(fileOrDirectory)) {
|
|
@@ -414,7 +522,7 @@ var toRelativePath = (fileOrDirectory, cwd) => {
|
|
|
414
522
|
};
|
|
415
523
|
var notIgnored = { ignored: false, unignored: false };
|
|
416
524
|
var createIgnoreMatcher = (patterns, cwd, baseDir) => {
|
|
417
|
-
const ignores = (0,
|
|
525
|
+
const ignores = (0, import_ignore2.default)().add(patterns);
|
|
418
526
|
const resolvedCwd = import_node_path2.default.normalize(import_node_path2.default.resolve(cwd));
|
|
419
527
|
const resolvedBaseDir = import_node_path2.default.normalize(import_node_path2.default.resolve(baseDir));
|
|
420
528
|
return (fileOrDirectory) => {
|
|
@@ -434,7 +542,7 @@ var createIgnoreMatcher = (patterns, cwd, baseDir) => {
|
|
|
434
542
|
if (hasTrailingSeparator && !relativePath.endsWith(import_node_path2.default.sep)) {
|
|
435
543
|
relativePath += import_node_path2.default.sep;
|
|
436
544
|
}
|
|
437
|
-
return ignores.test((0,
|
|
545
|
+
return ignores.test((0, import_slash2.default)(relativePath));
|
|
438
546
|
};
|
|
439
547
|
};
|
|
440
548
|
var normalizeOptions = (options = {}) => {
|
|
@@ -560,7 +668,7 @@ var normalizeGitConfigConditionPattern = (pattern, configFilePath) => {
|
|
|
560
668
|
if (pattern.endsWith("/")) {
|
|
561
669
|
pattern += "**";
|
|
562
670
|
}
|
|
563
|
-
return (0,
|
|
671
|
+
return (0, import_slash2.default)(pattern);
|
|
564
672
|
};
|
|
565
673
|
var gitConfigGlobToRegex = (pattern, flags) => {
|
|
566
674
|
let regex = "";
|
|
@@ -618,7 +726,7 @@ var matchesIncludeIfCondition = (condition, gitDirectory, configFilePath) => {
|
|
|
618
726
|
const pattern = normalizeGitConfigConditionPattern(rawPattern.trim(), configFilePath);
|
|
619
727
|
const isCaseInsensitive = keyword.toLowerCase() === "gitdir/i";
|
|
620
728
|
const regularExpression = gitConfigGlobToRegex(pattern, isCaseInsensitive ? "i" : void 0);
|
|
621
|
-
const normalizedGitDirectory = (0,
|
|
729
|
+
const normalizedGitDirectory = (0, import_slash2.default)(import_node_path2.default.resolve(gitDirectory));
|
|
622
730
|
return regularExpression.test(normalizedGitDirectory);
|
|
623
731
|
};
|
|
624
732
|
var shouldIncludeConfigSection = (section, gitDirectory, configFilePath) => {
|
|
@@ -830,23 +938,103 @@ var buildGlobalMatcher = (globalIgnoreFile, cwd, rootDirectory = cwd) => {
|
|
|
830
938
|
const patterns = parseIgnoreFile(globalIgnoreFile, import_node_path2.default.dirname(globalIgnoreFile.filePath));
|
|
831
939
|
return createIgnoreMatcher(patterns, cwd, rootDirectory);
|
|
832
940
|
};
|
|
941
|
+
var getKnownIgnoreFilePaths = (patterns, normalizedOptions, gitRoot) => {
|
|
942
|
+
const searchPatterns = [patterns].flat();
|
|
943
|
+
const isGitignoreSearch = searchPatterns.includes(GITIGNORE_FILES_PATTERN);
|
|
944
|
+
if (!isGitignoreSearch) {
|
|
945
|
+
return [];
|
|
946
|
+
}
|
|
947
|
+
return gitRoot ? getParentGitignorePaths(gitRoot, normalizedOptions.cwd) : [import_node_path2.default.join(normalizedOptions.cwd, ".gitignore")];
|
|
948
|
+
};
|
|
949
|
+
var getKnownIgnoreFileSearchOptions = (patterns, normalizedOptions) => ({
|
|
950
|
+
...normalizedOptions,
|
|
951
|
+
ignore: [
|
|
952
|
+
...normalizedOptions.ignore,
|
|
953
|
+
// Keep negative search patterns active when the known candidates are matched independently.
|
|
954
|
+
...[patterns].flat().filter((pattern) => isNegativePattern(pattern)).map((pattern) => pattern.slice(1))
|
|
955
|
+
]
|
|
956
|
+
});
|
|
957
|
+
var getKnownIgnoreFilePattern = (filePath, cwd) => {
|
|
958
|
+
const pattern = (0, import_is_path_inside2.default)(filePath, cwd) ? import_node_path2.default.relative(cwd, filePath) : filePath;
|
|
959
|
+
return import_fast_glob2.default.convertPathToPattern(pattern);
|
|
960
|
+
};
|
|
961
|
+
var getMatchingKnownIgnoreFilePaths = (knownPaths, matchingPaths) => {
|
|
962
|
+
const matchingPathSet = new Set(matchingPaths.map((filePath) => import_node_path2.default.resolve(filePath)));
|
|
963
|
+
return knownPaths.filter((filePath) => matchingPathSet.has(import_node_path2.default.resolve(filePath)));
|
|
964
|
+
};
|
|
965
|
+
var globKnownIgnoreFilePaths = (globFunction, knownPaths, patterns, normalizedOptions) => {
|
|
966
|
+
if (knownPaths.length === 0) {
|
|
967
|
+
return [];
|
|
968
|
+
}
|
|
969
|
+
return globIgnoreFiles(
|
|
970
|
+
globFunction,
|
|
971
|
+
knownPaths.map((filePath) => getKnownIgnoreFilePattern(filePath, normalizedOptions.cwd)),
|
|
972
|
+
getKnownIgnoreFileSearchOptions(patterns, normalizedOptions)
|
|
973
|
+
);
|
|
974
|
+
};
|
|
975
|
+
var filterKnownIgnoreFilePathsAsync = async (knownPaths, patterns, normalizedOptions) => {
|
|
976
|
+
const matchingPaths = await globKnownIgnoreFilePaths(import_fast_glob2.default, knownPaths, patterns, normalizedOptions);
|
|
977
|
+
return getMatchingKnownIgnoreFilePaths(knownPaths, matchingPaths);
|
|
978
|
+
};
|
|
979
|
+
var filterKnownIgnoreFilePathsSync = (knownPaths, patterns, normalizedOptions) => {
|
|
980
|
+
const matchingPaths = globKnownIgnoreFilePaths(import_fast_glob2.default.sync, knownPaths, patterns, normalizedOptions);
|
|
981
|
+
return getMatchingKnownIgnoreFilePaths(knownPaths, matchingPaths);
|
|
982
|
+
};
|
|
983
|
+
var getIgnoreFileSearchPrune = (searchPatterns, files, normalizedOptions, gitRoot) => {
|
|
984
|
+
if (files.length === 0) {
|
|
985
|
+
return { patterns: [], guardNames: [] };
|
|
986
|
+
}
|
|
987
|
+
const { cwd } = normalizedOptions;
|
|
988
|
+
const baseDir = gitRoot || cwd;
|
|
989
|
+
const ignorePatterns = getPatternsFromIgnoreFiles(files, baseDir);
|
|
990
|
+
const matcher = createIgnoreMatcher(ignorePatterns, cwd, baseDir);
|
|
991
|
+
const searchPatternsArray = [searchPatterns].flat();
|
|
992
|
+
const gitignoreOnlySearch = searchPatternsArray.every((pattern) => pattern === GITIGNORE_FILES_PATTERN);
|
|
993
|
+
const searchesForGitignoreFiles = searchPatternsArray.includes(GITIGNORE_FILES_PATTERN);
|
|
994
|
+
return buildPrunePatternsAndGuards(getIgnoreRules(files), matcher, cwd, { gitignoreOnlySearch, searchesForGitignoreFiles });
|
|
995
|
+
};
|
|
996
|
+
var withPrunedSearch = (normalizedOptions, prunePatterns) => prunePatterns.length === 0 ? normalizedOptions : { ...normalizedOptions, ignore: [...normalizedOptions.ignore, ...prunePatterns] };
|
|
997
|
+
var getUnreadPaths = (childPaths, knownPaths) => {
|
|
998
|
+
const alreadyRead = new Set(knownPaths.map((filePath) => import_node_path2.default.resolve(filePath)));
|
|
999
|
+
return dedupePaths(childPaths).filter((filePath) => !alreadyRead.has(import_node_path2.default.resolve(filePath)));
|
|
1000
|
+
};
|
|
833
1001
|
var collectIgnoreFileArtifactsAsync = async (patterns, options, includeParentIgnoreFiles) => {
|
|
834
1002
|
const normalizedOptions = normalizeOptions(options);
|
|
835
|
-
const childPaths = await globIgnoreFiles(import_fast_glob2.default, patterns, normalizedOptions);
|
|
836
|
-
const gitRoot = includeParentIgnoreFiles ? await findGitRoot(normalizedOptions.cwd, normalizedOptions.fs) : void 0;
|
|
837
|
-
const allPaths = combineIgnoreFilePaths(gitRoot, normalizedOptions, childPaths);
|
|
838
1003
|
const readFileMethod = getReadFileMethod(normalizedOptions.fs);
|
|
839
|
-
const
|
|
840
|
-
|
|
1004
|
+
const gitRoot = includeParentIgnoreFiles ? await findGitRoot(normalizedOptions.cwd, normalizedOptions.fs) : void 0;
|
|
1005
|
+
const knownPaths = await filterKnownIgnoreFilePathsAsync(
|
|
1006
|
+
getKnownIgnoreFilePaths(patterns, normalizedOptions, gitRoot),
|
|
1007
|
+
patterns,
|
|
1008
|
+
normalizedOptions
|
|
1009
|
+
);
|
|
1010
|
+
const knownFiles = await readIgnoreFilesSafely(knownPaths, readFileMethod, normalizedOptions.suppressErrors);
|
|
1011
|
+
const { patterns: prunePatterns, guardNames } = getIgnoreFileSearchPrune(patterns, knownFiles, normalizedOptions, gitRoot);
|
|
1012
|
+
const childPaths = await globIgnoreFiles(import_fast_glob2.default, patterns, withPrunedSearch(normalizedOptions, prunePatterns));
|
|
1013
|
+
let childFiles = await readIgnoreFilesSafely(getUnreadPaths(childPaths, knownPaths), readFileMethod, normalizedOptions.suppressErrors);
|
|
1014
|
+
if (negationsCouldRescue(getIgnoreRules(childFiles), guardNames)) {
|
|
1015
|
+
const allPaths = await globIgnoreFiles(import_fast_glob2.default, patterns, normalizedOptions);
|
|
1016
|
+
childFiles = await readIgnoreFilesSafely(getUnreadPaths(allPaths, knownPaths), readFileMethod, normalizedOptions.suppressErrors);
|
|
1017
|
+
}
|
|
1018
|
+
return { files: [...knownFiles, ...childFiles], normalizedOptions, gitRoot };
|
|
841
1019
|
};
|
|
842
1020
|
var collectIgnoreFileArtifactsSync = (patterns, options, includeParentIgnoreFiles) => {
|
|
843
1021
|
const normalizedOptions = normalizeOptions(options);
|
|
844
|
-
const childPaths = globIgnoreFiles(import_fast_glob2.default.sync, patterns, normalizedOptions);
|
|
845
|
-
const gitRoot = includeParentIgnoreFiles ? findGitRootSync(normalizedOptions.cwd, normalizedOptions.fs) : void 0;
|
|
846
|
-
const allPaths = combineIgnoreFilePaths(gitRoot, normalizedOptions, childPaths);
|
|
847
1022
|
const readFileSyncMethod = getReadFileSyncMethod(normalizedOptions.fs);
|
|
848
|
-
const
|
|
849
|
-
|
|
1023
|
+
const gitRoot = includeParentIgnoreFiles ? findGitRootSync(normalizedOptions.cwd, normalizedOptions.fs) : void 0;
|
|
1024
|
+
const knownPaths = filterKnownIgnoreFilePathsSync(
|
|
1025
|
+
getKnownIgnoreFilePaths(patterns, normalizedOptions, gitRoot),
|
|
1026
|
+
patterns,
|
|
1027
|
+
normalizedOptions
|
|
1028
|
+
);
|
|
1029
|
+
const knownFiles = readIgnoreFilesSafelySync(knownPaths, readFileSyncMethod, normalizedOptions.suppressErrors);
|
|
1030
|
+
const { patterns: prunePatterns, guardNames } = getIgnoreFileSearchPrune(patterns, knownFiles, normalizedOptions, gitRoot);
|
|
1031
|
+
const childPaths = globIgnoreFiles(import_fast_glob2.default.sync, patterns, withPrunedSearch(normalizedOptions, prunePatterns));
|
|
1032
|
+
let childFiles = readIgnoreFilesSafelySync(getUnreadPaths(childPaths, knownPaths), readFileSyncMethod, normalizedOptions.suppressErrors);
|
|
1033
|
+
if (negationsCouldRescue(getIgnoreRules(childFiles), guardNames)) {
|
|
1034
|
+
const allPaths = globIgnoreFiles(import_fast_glob2.default.sync, patterns, normalizedOptions);
|
|
1035
|
+
childFiles = readIgnoreFilesSafelySync(getUnreadPaths(allPaths, knownPaths), readFileSyncMethod, normalizedOptions.suppressErrors);
|
|
1036
|
+
}
|
|
1037
|
+
return { files: [...knownFiles, ...childFiles], normalizedOptions, gitRoot };
|
|
850
1038
|
};
|
|
851
1039
|
var isIgnoredByIgnoreFiles = async (patterns, options) => {
|
|
852
1040
|
const { files, normalizedOptions, gitRoot } = await collectIgnoreFileArtifactsAsync(patterns, options, false);
|
|
@@ -876,7 +1064,7 @@ var getIgnorePatternsAndPredicateSync = (patterns, options, includeParentIgnoreF
|
|
|
876
1064
|
var isGitIgnored = (options) => isIgnoredByIgnoreFiles(GITIGNORE_FILES_PATTERN, options);
|
|
877
1065
|
var isGitIgnoredSync = (options) => isIgnoredByIgnoreFilesSync(GITIGNORE_FILES_PATTERN, options);
|
|
878
1066
|
|
|
879
|
-
// packages/@cjser/globby.tmp-26-
|
|
1067
|
+
// packages/@cjser/globby.tmp-26-1784218928215/index.js
|
|
880
1068
|
var assertPatternsInput = (patterns) => {
|
|
881
1069
|
if (patterns.some((pattern) => typeof pattern !== "string")) {
|
|
882
1070
|
throw new TypeError("Patterns must be a string or an array of strings");
|
|
@@ -1026,14 +1214,12 @@ var combinePredicate = (matcher, globalMatcher) => {
|
|
|
1026
1214
|
return isPathIgnored(matcher, globalMatcher, file);
|
|
1027
1215
|
};
|
|
1028
1216
|
};
|
|
1029
|
-
var buildIgnoreFilterResult = (options, cwd, {
|
|
1217
|
+
var buildIgnoreFilterResult = ({ options, cwd, ignoreResult: { rules, matcher }, globalMatcher, createFilter }) => {
|
|
1030
1218
|
const finalPredicate = combinePredicate(matcher, globalMatcher);
|
|
1031
|
-
const
|
|
1219
|
+
const pruneIgnorePatterns = convertPatternsForFastGlob(rules, matcher, cwd);
|
|
1032
1220
|
return {
|
|
1033
|
-
options
|
|
1034
|
-
|
|
1035
|
-
ignore: [...options.ignore, ...patternsForFastGlob]
|
|
1036
|
-
},
|
|
1221
|
+
options,
|
|
1222
|
+
pruneIgnorePatterns,
|
|
1037
1223
|
filter: createFilter(finalPredicate, cwd, options.fs)
|
|
1038
1224
|
};
|
|
1039
1225
|
};
|
|
@@ -1044,14 +1230,21 @@ var applyIgnoreFilesAndGetFilter = async (options) => {
|
|
|
1044
1230
|
if (ignoreFilesPatterns.length === 0 && !globalIgnoreFile) {
|
|
1045
1231
|
return {
|
|
1046
1232
|
options,
|
|
1233
|
+
pruneIgnorePatterns: [],
|
|
1047
1234
|
filter: createFilterFunctionAsync(false, cwd, options.fs)
|
|
1048
1235
|
};
|
|
1049
1236
|
}
|
|
1050
1237
|
const includeParentIgnoreFiles = options.gitignore === true;
|
|
1051
|
-
const ignoreResult = ignoreFilesPatterns.length > 0 ? await getIgnorePatternsAndPredicate(ignoreFilesPatterns, options, includeParentIgnoreFiles) : {
|
|
1238
|
+
const ignoreResult = ignoreFilesPatterns.length > 0 ? await getIgnorePatternsAndPredicate(ignoreFilesPatterns, options, includeParentIgnoreFiles) : { rules: [], matcher: false };
|
|
1052
1239
|
const globalGitRoot = globalIgnoreFile ? await findGitRoot(cwd, options.fs) : void 0;
|
|
1053
1240
|
const globalMatcher = globalIgnoreFile ? buildGlobalMatcher(globalIgnoreFile, cwd, globalGitRoot ?? cwd) : void 0;
|
|
1054
|
-
return buildIgnoreFilterResult(
|
|
1241
|
+
return buildIgnoreFilterResult({
|
|
1242
|
+
options,
|
|
1243
|
+
cwd,
|
|
1244
|
+
ignoreResult,
|
|
1245
|
+
globalMatcher,
|
|
1246
|
+
createFilter: createFilterFunctionAsync
|
|
1247
|
+
});
|
|
1055
1248
|
};
|
|
1056
1249
|
var applyIgnoreFilesAndGetFilterSync = (options) => {
|
|
1057
1250
|
const cwd = options.cwd ?? import_node_process2.default.cwd();
|
|
@@ -1060,14 +1253,21 @@ var applyIgnoreFilesAndGetFilterSync = (options) => {
|
|
|
1060
1253
|
if (ignoreFilesPatterns.length === 0 && !globalIgnoreFile) {
|
|
1061
1254
|
return {
|
|
1062
1255
|
options,
|
|
1256
|
+
pruneIgnorePatterns: [],
|
|
1063
1257
|
filter: createFilterFunction(false, cwd, options.fs)
|
|
1064
1258
|
};
|
|
1065
1259
|
}
|
|
1066
1260
|
const includeParentIgnoreFiles = options.gitignore === true;
|
|
1067
|
-
const ignoreResult = ignoreFilesPatterns.length > 0 ? getIgnorePatternsAndPredicateSync(ignoreFilesPatterns, options, includeParentIgnoreFiles) : {
|
|
1261
|
+
const ignoreResult = ignoreFilesPatterns.length > 0 ? getIgnorePatternsAndPredicateSync(ignoreFilesPatterns, options, includeParentIgnoreFiles) : { rules: [], matcher: false };
|
|
1068
1262
|
const globalGitRoot = globalIgnoreFile ? findGitRootSync(cwd, options.fs) : void 0;
|
|
1069
1263
|
const globalMatcher = globalIgnoreFile ? buildGlobalMatcher(globalIgnoreFile, cwd, globalGitRoot ?? cwd) : void 0;
|
|
1070
|
-
return buildIgnoreFilterResult(
|
|
1264
|
+
return buildIgnoreFilterResult({
|
|
1265
|
+
options,
|
|
1266
|
+
cwd,
|
|
1267
|
+
ignoreResult,
|
|
1268
|
+
globalMatcher,
|
|
1269
|
+
createFilter: createFilterFunction
|
|
1270
|
+
});
|
|
1071
1271
|
};
|
|
1072
1272
|
var assertGlobalGitignoreSyncSupport = (options) => {
|
|
1073
1273
|
if (options.globalGitignore && options.fs && !options.fs.statSync) {
|
|
@@ -1250,21 +1450,28 @@ var applyParentDirectoryIgnoreAdjustments = (tasks) => tasks.map((task) => ({
|
|
|
1250
1450
|
ignore: adjustIgnorePatternsForParentDirectories(task.patterns, task.options.ignore)
|
|
1251
1451
|
}
|
|
1252
1452
|
}));
|
|
1453
|
+
var appendPruneIgnorePatterns = (tasks, pruneIgnorePatterns) => pruneIgnorePatterns.length === 0 ? tasks : tasks.map((task) => ({
|
|
1454
|
+
patterns: task.patterns,
|
|
1455
|
+
options: {
|
|
1456
|
+
...task.options,
|
|
1457
|
+
ignore: [...task.options.ignore, ...pruneIgnorePatterns]
|
|
1458
|
+
}
|
|
1459
|
+
}));
|
|
1253
1460
|
var normalizeExpandDirectoriesOption = (options, cwd) => ({
|
|
1254
1461
|
...cwd ? { cwd } : {},
|
|
1255
1462
|
...Array.isArray(options) ? { files: options } : options
|
|
1256
1463
|
});
|
|
1257
|
-
var generateTasks = async (patterns, options) => {
|
|
1464
|
+
var generateTasks = async (patterns, options, pruneIgnorePatterns = []) => {
|
|
1258
1465
|
const globTasks = convertNegativePatterns(patterns, options);
|
|
1259
1466
|
const { cwd, expandDirectories, fs: fsImplementation } = options;
|
|
1260
1467
|
if (!expandDirectories) {
|
|
1261
|
-
return applyParentDirectoryIgnoreAdjustments(globTasks);
|
|
1468
|
+
return appendPruneIgnorePatterns(applyParentDirectoryIgnoreAdjustments(globTasks), pruneIgnorePatterns);
|
|
1262
1469
|
}
|
|
1263
1470
|
const directoryToGlobOptions = {
|
|
1264
1471
|
...normalizeExpandDirectoriesOption(expandDirectories, cwd),
|
|
1265
1472
|
fs: fsImplementation
|
|
1266
1473
|
};
|
|
1267
|
-
|
|
1474
|
+
const tasks = await Promise.all(globTasks.map(async (task) => {
|
|
1268
1475
|
let { patterns: patterns2, options: options2 } = task;
|
|
1269
1476
|
[
|
|
1270
1477
|
patterns2,
|
|
@@ -1276,36 +1483,38 @@ var generateTasks = async (patterns, options) => {
|
|
|
1276
1483
|
options2.ignore = adjustIgnorePatternsForParentDirectories(patterns2, options2.ignore);
|
|
1277
1484
|
return { patterns: patterns2, options: options2 };
|
|
1278
1485
|
}));
|
|
1486
|
+
return appendPruneIgnorePatterns(tasks, pruneIgnorePatterns);
|
|
1279
1487
|
};
|
|
1280
|
-
var generateTasksSync = (patterns, options) => {
|
|
1488
|
+
var generateTasksSync = (patterns, options, pruneIgnorePatterns = []) => {
|
|
1281
1489
|
const globTasks = convertNegativePatterns(patterns, options);
|
|
1282
1490
|
const { cwd, expandDirectories, fs: fsImplementation } = options;
|
|
1283
1491
|
if (!expandDirectories) {
|
|
1284
|
-
return applyParentDirectoryIgnoreAdjustments(globTasks);
|
|
1492
|
+
return appendPruneIgnorePatterns(applyParentDirectoryIgnoreAdjustments(globTasks), pruneIgnorePatterns);
|
|
1285
1493
|
}
|
|
1286
1494
|
const directoryToGlobSyncOptions = {
|
|
1287
1495
|
...normalizeExpandDirectoriesOption(expandDirectories, cwd),
|
|
1288
1496
|
fs: fsImplementation
|
|
1289
1497
|
};
|
|
1290
|
-
|
|
1498
|
+
const tasks = globTasks.map((task) => {
|
|
1291
1499
|
let { patterns: patterns2, options: options2 } = task;
|
|
1292
1500
|
patterns2 = directoryToGlobSync(patterns2, directoryToGlobSyncOptions);
|
|
1293
1501
|
options2.ignore = directoryToGlobSync(options2.ignore, { cwd, fs: fsImplementation });
|
|
1294
1502
|
options2.ignore = adjustIgnorePatternsForParentDirectories(patterns2, options2.ignore);
|
|
1295
1503
|
return { patterns: patterns2, options: options2 };
|
|
1296
1504
|
});
|
|
1505
|
+
return appendPruneIgnorePatterns(tasks, pruneIgnorePatterns);
|
|
1297
1506
|
};
|
|
1298
1507
|
var globby = normalizeArguments(async (patterns, options) => {
|
|
1299
1508
|
assertGlobalGitignoreAsyncSupport(options);
|
|
1300
|
-
const { options: modifiedOptions, filter } = await applyIgnoreFilesAndGetFilter(options);
|
|
1301
|
-
const tasks = await generateTasks(patterns, modifiedOptions);
|
|
1509
|
+
const { options: modifiedOptions, pruneIgnorePatterns, filter } = await applyIgnoreFilesAndGetFilter(options);
|
|
1510
|
+
const tasks = await generateTasks(patterns, modifiedOptions, pruneIgnorePatterns);
|
|
1302
1511
|
const results = await Promise.all(tasks.map((task) => (0, import_fast_glob3.default)(task.patterns, task.options)));
|
|
1303
1512
|
return unionFastGlobResultsAsync(results, filter);
|
|
1304
1513
|
});
|
|
1305
1514
|
var globbySync = normalizeArgumentsSync((patterns, options) => {
|
|
1306
1515
|
assertGlobalGitignoreSyncSupport(options);
|
|
1307
|
-
const { options: modifiedOptions, filter } = applyIgnoreFilesAndGetFilterSync(options);
|
|
1308
|
-
const tasks = generateTasksSync(patterns, modifiedOptions);
|
|
1516
|
+
const { options: modifiedOptions, pruneIgnorePatterns, filter } = applyIgnoreFilesAndGetFilterSync(options);
|
|
1517
|
+
const tasks = generateTasksSync(patterns, modifiedOptions, pruneIgnorePatterns);
|
|
1309
1518
|
const results = tasks.map((task) => import_fast_glob3.default.sync(task.patterns, task.options));
|
|
1310
1519
|
return unionFastGlobResults(results, filter);
|
|
1311
1520
|
});
|
|
@@ -1313,8 +1522,8 @@ var globbyStream = normalizeArgumentsSync((patterns, options) => {
|
|
|
1313
1522
|
assertGlobalGitignoreAsyncSupport(options);
|
|
1314
1523
|
const seen = /* @__PURE__ */ new Set();
|
|
1315
1524
|
const stream = import_node_stream.Readable.from((async function* () {
|
|
1316
|
-
const { options: modifiedOptions, filter } = await applyIgnoreFilesAndGetFilter(options);
|
|
1317
|
-
const tasks = await generateTasks(patterns, modifiedOptions);
|
|
1525
|
+
const { options: modifiedOptions, pruneIgnorePatterns, filter } = await applyIgnoreFilesAndGetFilter(options);
|
|
1526
|
+
const tasks = await generateTasks(patterns, modifiedOptions, pruneIgnorePatterns);
|
|
1318
1527
|
if (tasks.length === 0) {
|
|
1319
1528
|
return;
|
|
1320
1529
|
}
|
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.2-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.2",
|
|
103
103
|
"cjserVersion": 2,
|
|
104
104
|
"original": {
|
|
105
105
|
"name": "globby",
|
|
106
|
-
"version": "16.2.
|
|
106
|
+
"version": "16.2.2",
|
|
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
|
|
package/utilities.js
CHANGED
|
@@ -2,7 +2,9 @@ import fs from 'node:fs';
|
|
|
2
2
|
import path from 'node:path';
|
|
3
3
|
import {promisify} from 'node:util';
|
|
4
4
|
import fastGlob from 'fast-glob';
|
|
5
|
+
import gitIgnore from 'ignore';
|
|
5
6
|
import isPathInside from '@cjser/is-path-inside';
|
|
7
|
+
import slash from '@cjser/slash';
|
|
6
8
|
|
|
7
9
|
export const isNegativePattern = pattern => pattern[0] === '!';
|
|
8
10
|
|
|
@@ -343,40 +345,210 @@ export const getParentGitignorePaths = (gitRoot, cwd) => {
|
|
|
343
345
|
.map(directory => path.join(directory, '.gitignore'));
|
|
344
346
|
};
|
|
345
347
|
|
|
348
|
+
// The wildcards gitignore itself understands, when not escaped. Other characters micromatch
|
|
349
|
+
// treats as syntax ((){}, extglobs, alternation) are literal in gitignore.
|
|
350
|
+
const GITIGNORE_WILDCARDS = /(?<!\\)[*?[]/u;
|
|
351
|
+
|
|
352
|
+
const hasGitignoreWildcards = value => GITIGNORE_WILDCARDS.test(value);
|
|
353
|
+
|
|
354
|
+
// Characters micromatch reads as syntax where gitignore does not. A glob rule containing them
|
|
355
|
+
// cannot be translated, and backslash escapes inside a glob cannot be safely carried through
|
|
356
|
+
// the path handling below, so such rules are left to the predicate.
|
|
357
|
+
const MICROMATCH_ONLY_SYNTAX = /[(){}|\\]/u;
|
|
358
|
+
|
|
359
|
+
// In gitignore, `\x` means the literal character x.
|
|
360
|
+
const unescapeGitignorePattern = value => value.replaceAll(/\\(.)/gu, '$1');
|
|
361
|
+
|
|
362
|
+
// Turn gitignore-literal text into fast-glob-literal text, so characters like `+(` cannot be
|
|
363
|
+
// misread as micromatch syntax.
|
|
364
|
+
const toLiteralPattern = value => fastGlob.escapePath(unescapeGitignorePattern(value));
|
|
365
|
+
|
|
366
|
+
const finalSegment = value => value.replace(/\/+$/u, '').split('/').pop();
|
|
367
|
+
|
|
368
|
+
const isInsideCwd = relativePath => relativePath !== '' && !relativePath.startsWith('..') && !path.isAbsolute(relativePath);
|
|
369
|
+
|
|
346
370
|
/**
|
|
347
|
-
|
|
348
|
-
|
|
371
|
+
Resolve a pattern anchored at the directory of its ignore file into a cwd-relative one.
|
|
372
|
+
|
|
373
|
+
@param {string} directory - Directory of the ignore file that declared the rule.
|
|
374
|
+
@param {string} body - The rule body, relative to that directory.
|
|
375
|
+
@param {string} cwd - Directory the glob runs from.
|
|
376
|
+
@returns {string|undefined} The cwd-relative pattern, or undefined when it targets something outside the cwd.
|
|
377
|
+
*/
|
|
378
|
+
const anchorToCwd = (directory, body, cwd) => {
|
|
379
|
+
const relativePath = slash(path.relative(cwd, path.join(directory, body)));
|
|
380
|
+
return isInsideCwd(relativePath) ? relativePath : undefined;
|
|
381
|
+
};
|
|
382
|
+
|
|
383
|
+
// Compare names with the `ignore` package instead of guessing from the syntax, since it is the
|
|
384
|
+
// same engine the predicate uses for the real decision.
|
|
385
|
+
const createNameComparer = () => {
|
|
386
|
+
const nameMatchers = new Map();
|
|
387
|
+
const matchesName = (pattern, name) => {
|
|
388
|
+
let nameMatcher = nameMatchers.get(pattern);
|
|
389
|
+
if (!nameMatcher) {
|
|
390
|
+
nameMatcher = gitIgnore().add([pattern]);
|
|
391
|
+
nameMatchers.set(pattern, nameMatcher);
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
return nameMatcher.ignores(name);
|
|
395
|
+
};
|
|
396
|
+
|
|
397
|
+
// A negation can only re-include the excluded path itself; nothing below it can be re-included once the directory is excluded. Two globs cannot be compared this way, so treat them as a possible match.
|
|
398
|
+
return (pattern, name) => {
|
|
399
|
+
if (hasGitignoreWildcards(pattern) && hasGitignoreWildcards(name)) {
|
|
400
|
+
return true;
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
return hasGitignoreWildcards(name) ? matchesName(name, pattern) : matchesName(pattern, name);
|
|
404
|
+
};
|
|
405
|
+
};
|
|
406
|
+
|
|
407
|
+
const getNegationFinalSegments = rules => rules
|
|
408
|
+
.filter(rule => isNegativePattern(rule.pattern))
|
|
409
|
+
.map(rule => finalSegment(rule.pattern.slice(1)))
|
|
410
|
+
.filter(Boolean);
|
|
411
|
+
|
|
412
|
+
/**
|
|
413
|
+
Check whether any negation in the given rules could re-include a path with one of the given names.
|
|
414
|
+
|
|
415
|
+
Used after the pruned ignore-file search: a negation found by that search can re-include a directory the prune patterns skipped, which means ignore files inside it were never discovered.
|
|
416
|
+
|
|
417
|
+
@param {Array<{pattern: string, directory: string}>} rules - Raw ignore-file lines and the directory of the ignore file that declared them.
|
|
418
|
+
@param {string[]} names - The guard names returned by `buildPrunePatternsAndGuards`.
|
|
419
|
+
@returns {boolean} Whether a negation could name one of them.
|
|
420
|
+
*/
|
|
421
|
+
export const negationsCouldRescue = (rules, names) => {
|
|
422
|
+
if (names.length === 0) {
|
|
423
|
+
return false;
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
const couldNameTheSamePath = createNameComparer();
|
|
427
|
+
return getNegationFinalSegments(rules).some(negation => names.some(name => couldNameTheSamePath(name, negation)));
|
|
428
|
+
};
|
|
429
|
+
|
|
430
|
+
// Compute the prune pattern for a single rule, or undefined when the rule cannot be skipped safely. The returned object also carries the guard name (if any) whose skipping relies on the rule set being complete.
|
|
431
|
+
const getRulePrune = ({pattern, directory}, {cwd, matcher, hasNegations, canSkipAtAnyDepth, canMatchIgnoreFile, gitignoreOnlySearch}) => {
|
|
432
|
+
if (isNegativePattern(pattern)) {
|
|
433
|
+
return undefined;
|
|
434
|
+
}
|
|
349
435
|
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
436
|
+
const isDirectoryPattern = pattern.endsWith('/');
|
|
437
|
+
const clean = pattern.replace(/\/+$/u, '');
|
|
438
|
+
if (!clean) {
|
|
439
|
+
return undefined;
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
// A leading `**/` is gitignore's explicit spelling of "match at any depth"; for a single trailing segment it is identical to the bare name (`**/foo` == `foo`). Drop it so the rule takes the any-depth branch below instead of being treated as an anchored glob.
|
|
443
|
+
const body = clean.startsWith('**/') && !clean.slice(3).includes('/')
|
|
444
|
+
? clean.slice(3)
|
|
445
|
+
: clean;
|
|
446
|
+
|
|
447
|
+
if (canMatchIgnoreFile(finalSegment(body))) {
|
|
448
|
+
// Contents-only rules such as `foo/*` still allow traversal to `foo/.gitignore`, so the ignore-file search must read it before pruning foo's contents.
|
|
449
|
+
return undefined;
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
const isGlob = hasGitignoreWildcards(body);
|
|
453
|
+
if (isGlob && MICROMATCH_ONLY_SYNTAX.test(body)) {
|
|
454
|
+
return undefined;
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
// The leading slash stops the normalizer from prefixing `**/`; the passed value already encodes the depth, and an extra `**/` would un-anchor an anchored rule.
|
|
458
|
+
const toFastGlob = value =>
|
|
459
|
+
normalizeDirectoryPatternForFastGlob(`/${value}${isDirectoryPattern ? '/' : ''}`).replace(/^\//u, '');
|
|
460
|
+
|
|
461
|
+
// No separator: matches at any depth below the ignore file that declared it.
|
|
462
|
+
if (!body.includes('/') && canSkipAtAnyDepth(body)) {
|
|
463
|
+
const relativeDirectory = slash(path.relative(cwd, directory));
|
|
464
|
+
const prefix = isInsideCwd(relativeDirectory) ? `${fastGlob.escapePath(relativeDirectory)}/` : '';
|
|
465
|
+
return {pattern: toFastGlob(`${prefix}**/${isGlob ? body : toLiteralPattern(body)}`), guardName: body};
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
// Otherwise fall back to the single occurrence beside the ignore file, which names a concrete path that the matcher can verify directly.
|
|
469
|
+
const anchoredBody = body.replace(/^\//u, '');
|
|
470
|
+
const target = anchorToCwd(directory, isGlob ? anchoredBody : unescapeGitignorePattern(anchoredBody), cwd);
|
|
471
|
+
if (target === undefined) {
|
|
472
|
+
return undefined;
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
if (isGlob) {
|
|
476
|
+
// A glob does not name a concrete path, so the matcher cannot confirm it is ignored.
|
|
477
|
+
return hasNegations
|
|
478
|
+
? undefined
|
|
479
|
+
: {pattern: toFastGlob(target), guardName: finalSegment(target)};
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
if (!matcher(path.resolve(cwd, target) + path.sep).ignored) {
|
|
483
|
+
return undefined;
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
// A direct child of the cwd can only be re-included by a rule at or above the cwd, and in a pure gitignore search those rules are all known already. A deeper target has intermediate directories whose ignore files may not have been read yet.
|
|
487
|
+
const needsGuard = !gitignoreOnlySearch || target.includes('/');
|
|
488
|
+
return {
|
|
489
|
+
pattern: toFastGlob(fastGlob.escapePath(target)),
|
|
490
|
+
guardName: needsGuard ? finalSegment(target) : undefined,
|
|
491
|
+
};
|
|
492
|
+
};
|
|
493
|
+
|
|
494
|
+
/**
|
|
495
|
+
Build the ignore patterns handed to fast-glob so it can skip ignored directories while traversing.
|
|
496
|
+
|
|
497
|
+
The authoritative filter is always the predicate, so these patterns only ever need to be safe:
|
|
498
|
+
skipping something the predicate would have kept loses files, while skipping less than possible
|
|
499
|
+
merely costs time. Two facts from the gitignore spec make aggressive skipping safe anyway:
|
|
500
|
+
|
|
501
|
+
- "It is not possible to re-include a file if a parent directory of that file is excluded."
|
|
502
|
+
So a directory that is still ignored once every negation has been applied can be skipped whole.
|
|
503
|
+
- A pattern with no separator matches at any depth below its own ignore file, and one with a
|
|
504
|
+
separator is anchored to that file's directory. Working from the raw rules - rather than from
|
|
505
|
+
patterns already rebased onto some other directory - keeps that distinction intact, which is
|
|
506
|
+
what lets this work from a subdirectory of the repository too.
|
|
507
|
+
|
|
508
|
+
The returned guard names are the directory names whose skipping relies on the given rules being
|
|
509
|
+
complete. A caller working from a partial rule set (the ignore-file search) must watch for later
|
|
510
|
+
negations that could name one of them; see `negationsCouldRescue`.
|
|
511
|
+
|
|
512
|
+
@param {Array<{pattern: string, directory: string}>} rules - Raw ignore-file lines and the directory of the ignore file that declared them.
|
|
513
|
+
@param {Function} matcher - The authoritative gitignore matcher.
|
|
514
|
+
@param {string} cwd - Directory the glob runs from.
|
|
515
|
+
@param {Object} [options] - Options.
|
|
516
|
+
@param {boolean} [options.gitignoreOnlySearch] - Whether the rule set can only grow through nested `.gitignore` files.
|
|
517
|
+
@param {boolean} [options.searchesForGitignoreFiles] - Whether the search includes `.gitignore` files.
|
|
518
|
+
@returns {{patterns: string[], guardNames: string[]}} Patterns safe to pass to fast-glob, and the names their safety depends on.
|
|
354
519
|
*/
|
|
355
|
-
export const
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
520
|
+
export const buildPrunePatternsAndGuards = (rules, matcher, cwd, {gitignoreOnlySearch = false, searchesForGitignoreFiles = false} = {}) => {
|
|
521
|
+
if (!matcher || !cwd || !rules || rules.length === 0) {
|
|
522
|
+
return {patterns: [], guardNames: []};
|
|
523
|
+
}
|
|
524
|
+
|
|
525
|
+
const negationNames = getNegationFinalSegments(rules);
|
|
526
|
+
const couldNameTheSamePath = createNameComparer();
|
|
527
|
+
const context = {
|
|
528
|
+
cwd,
|
|
529
|
+
matcher,
|
|
530
|
+
hasNegations: negationNames.length > 0,
|
|
531
|
+
canSkipAtAnyDepth: pattern => !negationNames.some(name => couldNameTheSamePath(pattern, name)),
|
|
532
|
+
canMatchIgnoreFile: pattern => searchesForGitignoreFiles && couldNameTheSamePath(pattern, '.gitignore'),
|
|
533
|
+
gitignoreOnlySearch,
|
|
534
|
+
};
|
|
535
|
+
|
|
536
|
+
const patterns = [];
|
|
537
|
+
const guardNames = [];
|
|
538
|
+
|
|
539
|
+
for (const rule of rules) {
|
|
540
|
+
const prune = getRulePrune(rule, context);
|
|
541
|
+
if (!prune) {
|
|
542
|
+
continue;
|
|
376
543
|
}
|
|
377
544
|
|
|
378
|
-
|
|
545
|
+
patterns.push(prune.pattern);
|
|
546
|
+
if (prune.guardName !== undefined) {
|
|
547
|
+
guardNames.push(prune.guardName);
|
|
548
|
+
}
|
|
379
549
|
}
|
|
380
550
|
|
|
381
|
-
return
|
|
551
|
+
return {patterns, guardNames};
|
|
382
552
|
};
|
|
553
|
+
|
|
554
|
+
export const convertPatternsForFastGlob = (rules, matcher, cwd) => buildPrunePatternsAndGuards(rules, matcher, cwd).patterns;
|