@cjser/globby 16.2.2-cjser.2 → 16.2.4-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 +52 -13
- package/index.d.ts +7 -1
- package/index.js +9 -2
- package/package.json +5 -3
- package/readme.md +6 -0
- package/utilities.js +82 -7
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-
|
|
29
|
+
// packages/@cjser/globby.tmp-25-1788279925204/index.js
|
|
30
30
|
var index_exports = {};
|
|
31
31
|
__export(index_exports, {
|
|
32
32
|
convertPathToPattern: () => convertPathToPattern,
|
|
@@ -50,7 +50,7 @@ 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-
|
|
53
|
+
// packages/@cjser/globby.tmp-25-1788279925204/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);
|
|
@@ -62,13 +62,14 @@ var import_is_path_inside2 = __toESM(require("@cjser/is-path-inside"), 1);
|
|
|
62
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-
|
|
65
|
+
// packages/@cjser/globby.tmp-25-1788279925204/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
70
|
var import_ignore = __toESM(require("ignore"), 1);
|
|
71
71
|
var import_is_path_inside = __toESM(require("@cjser/is-path-inside"), 1);
|
|
72
|
+
var import_micromatch = __toESM(require("micromatch"), 1);
|
|
72
73
|
var import_slash = __toESM(require("@cjser/slash"), 1);
|
|
73
74
|
var isNegativePattern = (pattern) => pattern[0] === "!";
|
|
74
75
|
var normalizeAbsolutePatternToRelative = (pattern) => {
|
|
@@ -274,8 +275,10 @@ var GITIGNORE_WILDCARDS = /(?<!\\)[*?[]/u;
|
|
|
274
275
|
var hasGitignoreWildcards = (value) => GITIGNORE_WILDCARDS.test(value);
|
|
275
276
|
var MICROMATCH_ONLY_SYNTAX = /[(){}|\\]/u;
|
|
276
277
|
var unescapeGitignorePattern = (value) => value.replaceAll(/\\(.)/gu, "$1");
|
|
278
|
+
var normalizeGitignorePatternForIgnore = (value) => value.replaceAll(/\\(.)/gu, (match, character) => "*[]\\".includes(character) ? match : character);
|
|
277
279
|
var toLiteralPattern = (value) => import_fast_glob.default.escapePath(unescapeGitignorePattern(value));
|
|
278
280
|
var finalSegment = (value) => value.replace(/\/+$/u, "").split("/").pop();
|
|
281
|
+
var toStandaloneRule = (value) => value.replace(/^([#!])/u, String.raw`\$1`);
|
|
279
282
|
var isInsideCwd = (relativePath) => relativePath !== "" && !relativePath.startsWith("..") && !import_node_path.default.isAbsolute(relativePath);
|
|
280
283
|
var anchorToCwd = (directory, body, cwd) => {
|
|
281
284
|
const relativePath = (0, import_slash.default)(import_node_path.default.relative(cwd, import_node_path.default.join(directory, body)));
|
|
@@ -284,12 +287,17 @@ var anchorToCwd = (directory, body, cwd) => {
|
|
|
284
287
|
var createNameComparer = () => {
|
|
285
288
|
const nameMatchers = /* @__PURE__ */ new Map();
|
|
286
289
|
const matchesName = (pattern, name) => {
|
|
287
|
-
|
|
290
|
+
const namePath = unescapeGitignorePattern(name);
|
|
291
|
+
if (!(0, import_ignore.isPathValid)(namePath)) {
|
|
292
|
+
return true;
|
|
293
|
+
}
|
|
294
|
+
const normalizedPattern = normalizeGitignorePatternForIgnore(pattern);
|
|
295
|
+
let nameMatcher = nameMatchers.get(normalizedPattern);
|
|
288
296
|
if (!nameMatcher) {
|
|
289
|
-
nameMatcher = (0, import_ignore.default)().add([
|
|
290
|
-
nameMatchers.set(
|
|
297
|
+
nameMatcher = (0, import_ignore.default)().add([toStandaloneRule(normalizedPattern)]);
|
|
298
|
+
nameMatchers.set(normalizedPattern, nameMatcher);
|
|
291
299
|
}
|
|
292
|
-
return nameMatcher.ignores(
|
|
300
|
+
return nameMatcher.ignores(namePath);
|
|
293
301
|
};
|
|
294
302
|
return (pattern, name) => {
|
|
295
303
|
if (hasGitignoreWildcards(pattern) && hasGitignoreWildcards(name)) {
|
|
@@ -306,6 +314,32 @@ var negationsCouldRescue = (rules, names) => {
|
|
|
306
314
|
const couldNameTheSamePath = createNameComparer();
|
|
307
315
|
return getNegationFinalSegments(rules).some((negation) => names.some((name) => couldNameTheSamePath(name, negation)));
|
|
308
316
|
};
|
|
317
|
+
var expandBraceGroups = (pattern) => {
|
|
318
|
+
if (!pattern.includes("{")) {
|
|
319
|
+
return [pattern];
|
|
320
|
+
}
|
|
321
|
+
const expandedPatterns = import_fast_glob.default.generateTasks(pattern).flatMap((task) => task.patterns);
|
|
322
|
+
return expandedPatterns.length > 0 ? expandedPatterns : [pattern];
|
|
323
|
+
};
|
|
324
|
+
var convertIgnorePatternsForIgnoreFileSearch = (ignorePatterns, searchPatterns) => {
|
|
325
|
+
if (ignorePatterns.length === 0) {
|
|
326
|
+
return ignorePatterns;
|
|
327
|
+
}
|
|
328
|
+
const couldNameTheSamePath = createNameComparer();
|
|
329
|
+
const expandedSearchPatterns = searchPatterns.flatMap((pattern) => expandBraceGroups(pattern));
|
|
330
|
+
if (expandedSearchPatterns.some((pattern) => MICROMATCH_ONLY_SYNTAX.test(pattern.slice(0, pattern.lastIndexOf("/") + 1)))) {
|
|
331
|
+
return [];
|
|
332
|
+
}
|
|
333
|
+
const ignoreFileNames = expandedSearchPatterns.map((pattern) => finalSegment(pattern)).filter(Boolean);
|
|
334
|
+
const couldNameAnIgnoreFile = (pattern) => {
|
|
335
|
+
const name = finalSegment(pattern.replace(/\/\*\*$/u, ""));
|
|
336
|
+
if (!name || MICROMATCH_ONLY_SYNTAX.test(name)) {
|
|
337
|
+
return true;
|
|
338
|
+
}
|
|
339
|
+
return ignoreFileNames.some((ignoreFileName) => MICROMATCH_ONLY_SYNTAX.test(ignoreFileName) ? hasGitignoreWildcards(name) || import_micromatch.default.isMatch(unescapeGitignorePattern(name), ignoreFileName, { dot: true, nocase: true }) : couldNameTheSamePath(name, ignoreFileName));
|
|
340
|
+
};
|
|
341
|
+
return ignorePatterns.filter((pattern) => !expandBraceGroups(pattern).some((expanded) => couldNameAnIgnoreFile(expanded)));
|
|
342
|
+
};
|
|
309
343
|
var getRulePrune = ({ pattern, directory }, { cwd, matcher, hasNegations, canSkipAtAnyDepth, canMatchIgnoreFile, gitignoreOnlySearch }) => {
|
|
310
344
|
if (isNegativePattern(pattern)) {
|
|
311
345
|
return void 0;
|
|
@@ -334,8 +368,9 @@ var getRulePrune = ({ pattern, directory }, { cwd, matcher, hasNegations, canSki
|
|
|
334
368
|
if (target === void 0) {
|
|
335
369
|
return void 0;
|
|
336
370
|
}
|
|
371
|
+
const guardName = finalSegment(anchoredBody);
|
|
337
372
|
if (isGlob) {
|
|
338
|
-
return hasNegations ? void 0 : { pattern: toFastGlob(target), guardName
|
|
373
|
+
return hasNegations ? void 0 : { pattern: toFastGlob(target), guardName };
|
|
339
374
|
}
|
|
340
375
|
if (!matcher(import_node_path.default.resolve(cwd, target) + import_node_path.default.sep).ignored) {
|
|
341
376
|
return void 0;
|
|
@@ -343,7 +378,7 @@ var getRulePrune = ({ pattern, directory }, { cwd, matcher, hasNegations, canSki
|
|
|
343
378
|
const needsGuard = !gitignoreOnlySearch || target.includes("/");
|
|
344
379
|
return {
|
|
345
380
|
pattern: toFastGlob(import_fast_glob.default.escapePath(target)),
|
|
346
|
-
guardName: needsGuard ?
|
|
381
|
+
guardName: needsGuard ? guardName : void 0
|
|
347
382
|
};
|
|
348
383
|
};
|
|
349
384
|
var buildPrunePatternsAndGuards = (rules, matcher, cwd, { gitignoreOnlySearch = false, searchesForGitignoreFiles = false } = {}) => {
|
|
@@ -376,7 +411,7 @@ var buildPrunePatternsAndGuards = (rules, matcher, cwd, { gitignoreOnlySearch =
|
|
|
376
411
|
};
|
|
377
412
|
var convertPatternsForFastGlob = (rules, matcher, cwd) => buildPrunePatternsAndGuards(rules, matcher, cwd).patterns;
|
|
378
413
|
|
|
379
|
-
// packages/@cjser/globby.tmp-
|
|
414
|
+
// packages/@cjser/globby.tmp-25-1788279925204/ignore.js
|
|
380
415
|
var defaultIgnoredDirectories = [
|
|
381
416
|
"**/node_modules",
|
|
382
417
|
"**/flow-typed",
|
|
@@ -1064,7 +1099,7 @@ var getIgnorePatternsAndPredicateSync = (patterns, options, includeParentIgnoreF
|
|
|
1064
1099
|
var isGitIgnored = (options) => isIgnoredByIgnoreFiles(GITIGNORE_FILES_PATTERN, options);
|
|
1065
1100
|
var isGitIgnoredSync = (options) => isIgnoredByIgnoreFilesSync(GITIGNORE_FILES_PATTERN, options);
|
|
1066
1101
|
|
|
1067
|
-
// packages/@cjser/globby.tmp-
|
|
1102
|
+
// packages/@cjser/globby.tmp-25-1788279925204/index.js
|
|
1068
1103
|
var assertPatternsInput = (patterns) => {
|
|
1069
1104
|
if (patterns.some((pattern) => typeof pattern !== "string")) {
|
|
1070
1105
|
throw new TypeError("Patterns must be a string or an array of strings");
|
|
@@ -1223,6 +1258,10 @@ var buildIgnoreFilterResult = ({ options, cwd, ignoreResult: { rules, matcher },
|
|
|
1223
1258
|
filter: createFilter(finalPredicate, cwd, options.fs)
|
|
1224
1259
|
};
|
|
1225
1260
|
};
|
|
1261
|
+
var getIgnoreFileSearchOptions = (options, searchPatterns) => ({
|
|
1262
|
+
...options,
|
|
1263
|
+
ignore: convertIgnorePatternsForIgnoreFileSearch(options.ignore, searchPatterns)
|
|
1264
|
+
});
|
|
1226
1265
|
var applyIgnoreFilesAndGetFilter = async (options) => {
|
|
1227
1266
|
const cwd = options.cwd ?? import_node_process2.default.cwd();
|
|
1228
1267
|
const ignoreFilesPatterns = getIgnoreFilesPatterns(options);
|
|
@@ -1235,7 +1274,7 @@ var applyIgnoreFilesAndGetFilter = async (options) => {
|
|
|
1235
1274
|
};
|
|
1236
1275
|
}
|
|
1237
1276
|
const includeParentIgnoreFiles = options.gitignore === true;
|
|
1238
|
-
const ignoreResult = ignoreFilesPatterns.length > 0 ? await getIgnorePatternsAndPredicate(ignoreFilesPatterns, options, includeParentIgnoreFiles) : { rules: [], matcher: false };
|
|
1277
|
+
const ignoreResult = ignoreFilesPatterns.length > 0 ? await getIgnorePatternsAndPredicate(ignoreFilesPatterns, getIgnoreFileSearchOptions(options, ignoreFilesPatterns), includeParentIgnoreFiles) : { rules: [], matcher: false };
|
|
1239
1278
|
const globalGitRoot = globalIgnoreFile ? await findGitRoot(cwd, options.fs) : void 0;
|
|
1240
1279
|
const globalMatcher = globalIgnoreFile ? buildGlobalMatcher(globalIgnoreFile, cwd, globalGitRoot ?? cwd) : void 0;
|
|
1241
1280
|
return buildIgnoreFilterResult({
|
|
@@ -1258,7 +1297,7 @@ var applyIgnoreFilesAndGetFilterSync = (options) => {
|
|
|
1258
1297
|
};
|
|
1259
1298
|
}
|
|
1260
1299
|
const includeParentIgnoreFiles = options.gitignore === true;
|
|
1261
|
-
const ignoreResult = ignoreFilesPatterns.length > 0 ? getIgnorePatternsAndPredicateSync(ignoreFilesPatterns, options, includeParentIgnoreFiles) : { rules: [], matcher: false };
|
|
1300
|
+
const ignoreResult = ignoreFilesPatterns.length > 0 ? getIgnorePatternsAndPredicateSync(ignoreFilesPatterns, getIgnoreFileSearchOptions(options, ignoreFilesPatterns), includeParentIgnoreFiles) : { rules: [], matcher: false };
|
|
1262
1301
|
const globalGitRoot = globalIgnoreFile ? findGitRootSync(cwd, options.fs) : void 0;
|
|
1263
1302
|
const globalMatcher = globalIgnoreFile ? buildGlobalMatcher(globalIgnoreFile, cwd, globalGitRoot ?? cwd) : void 0;
|
|
1264
1303
|
return buildIgnoreFilterResult({
|
package/index.d.ts
CHANGED
|
@@ -46,7 +46,9 @@ export type Options = {
|
|
|
46
46
|
|
|
47
47
|
Gitignore patterns take priority over user patterns, matching Git's behavior. To include gitignored files, set this to `false`.
|
|
48
48
|
|
|
49
|
-
|
|
49
|
+
The `ignore` option applies to the results only. A pattern that could name an ignore file, such as `'**\/.gitignore'`, is not used when searching for the ignore files, so it hides them from the results without disabling this option.
|
|
50
|
+
|
|
51
|
+
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.
|
|
50
52
|
|
|
51
53
|
@default false
|
|
52
54
|
*/
|
|
@@ -57,6 +59,8 @@ export type Options = {
|
|
|
57
59
|
|
|
58
60
|
This is a more generic form of the `gitignore` option, allowing you to find ignore files with a [compatible syntax](http://git-scm.com/docs/gitignore). For instance, this works with Babel's `.babelignore`, Prettier's `.prettierignore`, or ESLint's `.eslintignore` files.
|
|
59
61
|
|
|
62
|
+
The `ignore` option applies to the results only, as with the `gitignore` option.
|
|
63
|
+
|
|
60
64
|
Performance tip: Using a specific path like `'.gitignore'` is much faster than recursive patterns.
|
|
61
65
|
|
|
62
66
|
@default undefined
|
|
@@ -137,6 +141,8 @@ export type GitignoreOptions = {
|
|
|
137
141
|
/**
|
|
138
142
|
Glob patterns to exclude from ignore file search.
|
|
139
143
|
|
|
144
|
+
Unlike the `ignore` option of `globby()`, which applies to the results only, this one excludes files from the search itself, so a pattern that names an ignore file stops that file from being read.
|
|
145
|
+
|
|
140
146
|
@default []
|
|
141
147
|
*/
|
|
142
148
|
readonly ignore?: string | readonly string[];
|
package/index.js
CHANGED
|
@@ -21,6 +21,7 @@ import {
|
|
|
21
21
|
normalizeNegativePattern,
|
|
22
22
|
adjustIgnorePatternsForParentDirectories,
|
|
23
23
|
convertPatternsForFastGlob,
|
|
24
|
+
convertIgnorePatternsForIgnoreFileSearch,
|
|
24
25
|
findGitRoot,
|
|
25
26
|
findGitRootSync,
|
|
26
27
|
} from './utilities.js';
|
|
@@ -245,6 +246,12 @@ const buildIgnoreFilterResult = ({options, cwd, ignoreResult: {rules, matcher},
|
|
|
245
246
|
};
|
|
246
247
|
};
|
|
247
248
|
|
|
249
|
+
// The ignore files are searched for with the same options as the glob itself, except for `ignore`, which excludes paths from the results and must not decide which ignore files are read. The patterns that cannot hide one are still passed on, so the search keeps skipping the directories they name.
|
|
250
|
+
const getIgnoreFileSearchOptions = (options, searchPatterns) => ({
|
|
251
|
+
...options,
|
|
252
|
+
ignore: convertIgnorePatternsForIgnoreFileSearch(options.ignore, searchPatterns),
|
|
253
|
+
});
|
|
254
|
+
|
|
248
255
|
/**
|
|
249
256
|
Apply ignore files to options and return the filter predicate.
|
|
250
257
|
|
|
@@ -272,7 +279,7 @@ const applyIgnoreFilesAndGetFilter = async options => {
|
|
|
272
279
|
// Enable parent .gitignore search when using gitignore option
|
|
273
280
|
const includeParentIgnoreFiles = options.gitignore === true;
|
|
274
281
|
const ignoreResult = ignoreFilesPatterns.length > 0
|
|
275
|
-
? await getIgnorePatternsAndPredicate(ignoreFilesPatterns, options, includeParentIgnoreFiles)
|
|
282
|
+
? await getIgnorePatternsAndPredicate(ignoreFilesPatterns, getIgnoreFileSearchOptions(options, ignoreFilesPatterns), includeParentIgnoreFiles)
|
|
276
283
|
: {rules: [], matcher: false};
|
|
277
284
|
|
|
278
285
|
const globalGitRoot = globalIgnoreFile ? await findGitRoot(cwd, options.fs) : undefined;
|
|
@@ -309,7 +316,7 @@ const applyIgnoreFilesAndGetFilterSync = options => {
|
|
|
309
316
|
// Enable parent .gitignore search when using gitignore option
|
|
310
317
|
const includeParentIgnoreFiles = options.gitignore === true;
|
|
311
318
|
const ignoreResult = ignoreFilesPatterns.length > 0
|
|
312
|
-
? getIgnorePatternsAndPredicateSync(ignoreFilesPatterns, options, includeParentIgnoreFiles)
|
|
319
|
+
? getIgnorePatternsAndPredicateSync(ignoreFilesPatterns, getIgnoreFileSearchOptions(options, ignoreFilesPatterns), includeParentIgnoreFiles)
|
|
313
320
|
: {rules: [], matcher: false};
|
|
314
321
|
|
|
315
322
|
const globalGitRoot = globalIgnoreFile ? findGitRootSync(cwd, options.fs) : undefined;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cjser/globby",
|
|
3
|
-
"version": "16.2.
|
|
3
|
+
"version": "16.2.4-cjser.2",
|
|
4
4
|
"description": "User-friendly glob matching",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
@@ -70,6 +70,7 @@
|
|
|
70
70
|
"dependencies": {
|
|
71
71
|
"fast-glob": "^3.3.3",
|
|
72
72
|
"ignore": "^7.0.5",
|
|
73
|
+
"micromatch": "^4.0.8",
|
|
73
74
|
"@cjser/sindresorhus__merge-streams": "4.0.0-cjser.2",
|
|
74
75
|
"@cjser/is-path-inside": "4.0.0-cjser.2",
|
|
75
76
|
"@cjser/slash": "5.1.0-cjser.2",
|
|
@@ -99,11 +100,11 @@
|
|
|
99
100
|
"types": "./index.d.ts",
|
|
100
101
|
"main": "./dist-cjser/index.cjs",
|
|
101
102
|
"cjser": {
|
|
102
|
-
"sourceVersion": "16.2.
|
|
103
|
+
"sourceVersion": "16.2.4",
|
|
103
104
|
"cjserVersion": 2,
|
|
104
105
|
"original": {
|
|
105
106
|
"name": "globby",
|
|
106
|
-
"version": "16.2.
|
|
107
|
+
"version": "16.2.4",
|
|
107
108
|
"exports": {
|
|
108
109
|
"types": "./index.d.ts",
|
|
109
110
|
"default": "./index.js"
|
|
@@ -114,6 +115,7 @@
|
|
|
114
115
|
"fast-glob": "^3.3.3",
|
|
115
116
|
"ignore": "^7.0.5",
|
|
116
117
|
"is-path-inside": "^4.0.0",
|
|
118
|
+
"micromatch": "^4.0.8",
|
|
117
119
|
"slash": "^5.1.0",
|
|
118
120
|
"unicorn-magic": "^0.4.0"
|
|
119
121
|
},
|
package/readme.md
CHANGED
|
@@ -93,6 +93,8 @@ 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
|
+
The `ignore` option applies to the results only. A pattern that could name an ignore file, such as `'**/.gitignore'`, is not used when searching for the ignore files, so it hides them from the results without disabling this option.
|
|
97
|
+
|
|
96
98
|
**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
99
|
|
|
98
100
|
##### globalGitignore
|
|
@@ -117,6 +119,8 @@ Glob patterns to look for ignore files, which are then used to ignore globbed fi
|
|
|
117
119
|
|
|
118
120
|
This is a more generic form of the `gitignore` option, allowing you to find ignore files with a [compatible syntax](http://git-scm.com/docs/gitignore). For instance, this works with Babel's `.babelignore`, Prettier's `.prettierignore`, or ESLint's `.eslintignore` files.
|
|
119
121
|
|
|
122
|
+
The `ignore` option applies to the results only, as with the [`gitignore`](#gitignore) option.
|
|
123
|
+
|
|
120
124
|
**Performance tip:** Using a specific path like `'.gitignore'` is much faster than recursive patterns.
|
|
121
125
|
|
|
122
126
|
##### expandNegationOnlyPatterns
|
|
@@ -248,6 +252,8 @@ Default: `[]`
|
|
|
248
252
|
|
|
249
253
|
Glob patterns to exclude from `.gitignore` file search.
|
|
250
254
|
|
|
255
|
+
Unlike the `ignore` option of [`globby()`](#options), which applies to the results only, this one excludes files from the search itself, so a pattern that names an ignore file stops that file from being read.
|
|
256
|
+
|
|
251
257
|
##### followSymbolicLinks
|
|
252
258
|
|
|
253
259
|
Type: `boolean`\
|
package/utilities.js
CHANGED
|
@@ -2,8 +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
|
+
import gitIgnore, {isPathValid} from 'ignore';
|
|
6
6
|
import isPathInside from '@cjser/is-path-inside';
|
|
7
|
+
import micromatch from 'micromatch';
|
|
7
8
|
import slash from '@cjser/slash';
|
|
8
9
|
|
|
9
10
|
export const isNegativePattern = pattern => pattern[0] === '!';
|
|
@@ -359,12 +360,18 @@ const MICROMATCH_ONLY_SYNTAX = /[(){}|\\]/u;
|
|
|
359
360
|
// In gitignore, `\x` means the literal character x.
|
|
360
361
|
const unescapeGitignorePattern = value => value.replaceAll(/\\(.)/gu, '$1');
|
|
361
362
|
|
|
363
|
+
// Normalize ordinary escaped characters for the ignore package, while preserving escapes that it handles as literals. An escaped question mark is widened to a wildcard because the ignore package does not match it literally, and a possible match is safer here than pruning too much.
|
|
364
|
+
const normalizeGitignorePatternForIgnore = value => value.replaceAll(/\\(.)/gu, (match, character) => '*[]\\'.includes(character) ? match : character);
|
|
365
|
+
|
|
362
366
|
// Turn gitignore-literal text into fast-glob-literal text, so characters like `+(` cannot be
|
|
363
367
|
// misread as micromatch syntax.
|
|
364
368
|
const toLiteralPattern = value => fastGlob.escapePath(unescapeGitignorePattern(value));
|
|
365
369
|
|
|
366
370
|
const finalSegment = value => value.replace(/\/+$/u, '').split('/').pop();
|
|
367
371
|
|
|
372
|
+
// A fragment of rule text is not a rule on its own: `#name` would open a comment and `!name` a negation, both of which stop naming anything. Escape the leading character so the fragment keeps naming what it did inside the rule it came from.
|
|
373
|
+
const toStandaloneRule = value => value.replace(/^([#!])/u, String.raw`\$1`);
|
|
374
|
+
|
|
368
375
|
const isInsideCwd = relativePath => relativePath !== '' && !relativePath.startsWith('..') && !path.isAbsolute(relativePath);
|
|
369
376
|
|
|
370
377
|
/**
|
|
@@ -385,13 +392,20 @@ const anchorToCwd = (directory, body, cwd) => {
|
|
|
385
392
|
const createNameComparer = () => {
|
|
386
393
|
const nameMatchers = new Map();
|
|
387
394
|
const matchesName = (pattern, name) => {
|
|
388
|
-
|
|
395
|
+
// The name is rule text, not a path: in gitignore `\#foo` names the file `#foo`. `Ignore#ignores()` only accepts a `path.relative()`d string and throws otherwise, so unescape first and treat whatever it still rejects (`.`, `..`, anything anchored) as a possible match.
|
|
396
|
+
const namePath = unescapeGitignorePattern(name);
|
|
397
|
+
if (!isPathValid(namePath)) {
|
|
398
|
+
return true;
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
const normalizedPattern = normalizeGitignorePatternForIgnore(pattern);
|
|
402
|
+
let nameMatcher = nameMatchers.get(normalizedPattern);
|
|
389
403
|
if (!nameMatcher) {
|
|
390
|
-
nameMatcher = gitIgnore().add([
|
|
391
|
-
nameMatchers.set(
|
|
404
|
+
nameMatcher = gitIgnore().add([toStandaloneRule(normalizedPattern)]);
|
|
405
|
+
nameMatchers.set(normalizedPattern, nameMatcher);
|
|
392
406
|
}
|
|
393
407
|
|
|
394
|
-
return nameMatcher.ignores(
|
|
408
|
+
return nameMatcher.ignores(namePath);
|
|
395
409
|
};
|
|
396
410
|
|
|
397
411
|
// 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.
|
|
@@ -427,6 +441,64 @@ export const negationsCouldRescue = (rules, names) => {
|
|
|
427
441
|
return getNegationFinalSegments(rules).some(negation => names.some(name => couldNameTheSamePath(name, negation)));
|
|
428
442
|
};
|
|
429
443
|
|
|
444
|
+
// Brace groups are expanded before the patterns are matched, so every alternative has to be checked on its own: `{**/.gitignore,dist}` names an ignore file even though the text it ends in, `dist}`, names nothing. `generateTasks()` is the only public fast-glob API that hands the expanded alternatives back as text, and it also leaves escaped braces alone.
|
|
445
|
+
const expandBraceGroups = pattern => {
|
|
446
|
+
// Skipping the call also keeps the patterns `generateTasks()` rejects, such as an empty one, out of it.
|
|
447
|
+
if (!pattern.includes('{')) {
|
|
448
|
+
return [pattern];
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
// A pattern that yields no task, such as a negated one, has no alternatives to check, so fall back to the pattern as written rather than to nothing.
|
|
452
|
+
const expandedPatterns = fastGlob.generateTasks(pattern).flatMap(task => task.patterns);
|
|
453
|
+
return expandedPatterns.length > 0 ? expandedPatterns : [pattern];
|
|
454
|
+
};
|
|
455
|
+
|
|
456
|
+
/**
|
|
457
|
+
Drop the `ignore` patterns that could exclude an ignore file from the search that looks for them.
|
|
458
|
+
|
|
459
|
+
`ignore` excludes paths from the results, while the ignore files decide what is ignored, so it must never be able to hide one of them. See https://github.com/sindresorhus/globby/issues/281
|
|
460
|
+
|
|
461
|
+
The rest are normally kept unchanged, so that unreadable or unwanted directories are still skipped while searching. Advanced micromatch syntax in the directory portion makes filename comparison unreliable, so optional pruning is disabled for that rare pattern shape. See https://github.com/sindresorhus/globby/pull/259
|
|
462
|
+
|
|
463
|
+
A pattern that names a directory is safe to keep even when that directory holds an ignore file: nothing inside an excluded directory ends up in the results either. Only the final segment can name the ignore file itself, and a trailing `/**` does not protect it, since fast-glob matches `foo/**` against `foo` as well.
|
|
464
|
+
|
|
465
|
+
@param {string[]} ignorePatterns - The `ignore` option, as an array.
|
|
466
|
+
@param {string[]} searchPatterns - The patterns the ignore files are searched with.
|
|
467
|
+
@returns {string[]} The patterns that are left, to be used as the `ignore` option of that search.
|
|
468
|
+
*/
|
|
469
|
+
export const convertIgnorePatternsForIgnoreFileSearch = (ignorePatterns, searchPatterns) => {
|
|
470
|
+
if (ignorePatterns.length === 0) {
|
|
471
|
+
return ignorePatterns;
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
const couldNameTheSamePath = createNameComparer();
|
|
475
|
+
const expandedSearchPatterns = searchPatterns.flatMap(pattern => expandBraceGroups(pattern));
|
|
476
|
+
|
|
477
|
+
// Filename comparison is not reliable when micromatch-only syntax appears in the directory part, so skip this optional pruning for that rare pattern shape.
|
|
478
|
+
if (expandedSearchPatterns.some(pattern => MICROMATCH_ONLY_SYNTAX.test(pattern.slice(0, pattern.lastIndexOf('/') + 1)))) {
|
|
479
|
+
return [];
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
// The names the search looks for, such as `.gitignore` for the `gitignore` option, or whatever `ignoreFiles` asked for. Only the final segment can name a file; the rest of a search pattern says where to look.
|
|
483
|
+
const ignoreFileNames = expandedSearchPatterns.map(pattern => finalSegment(pattern)).filter(Boolean);
|
|
484
|
+
|
|
485
|
+
const couldNameAnIgnoreFile = pattern => {
|
|
486
|
+
// A trailing `/**` excludes the contents of the directory it names, so only that name is left to compare. A wildcard final segment, as in `src/*`, still compares as one: it does not skip `src`, so the search walks in and would then throw the `.gitignore` it finds away.
|
|
487
|
+
const name = finalSegment(pattern.replace(/\/\*\*$/u, ''));
|
|
488
|
+
|
|
489
|
+
// Nothing left to compare means the pattern names no path of its own, as with `/`. Treat it as a possible match, since keeping it can only cost a missed ignore file. The same goes for micromatch syntax the comparison below cannot read, such as the extglob in `+(.gitignore)`.
|
|
490
|
+
if (!name || MICROMATCH_ONLY_SYNTAX.test(name)) {
|
|
491
|
+
return true;
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
return ignoreFileNames.some(ignoreFileName => MICROMATCH_ONLY_SYNTAX.test(ignoreFileName)
|
|
495
|
+
? hasGitignoreWildcards(name) || micromatch.isMatch(unescapeGitignorePattern(name), ignoreFileName, {dot: true, nocase: true})
|
|
496
|
+
: couldNameTheSamePath(name, ignoreFileName));
|
|
497
|
+
};
|
|
498
|
+
|
|
499
|
+
return ignorePatterns.filter(pattern => !expandBraceGroups(pattern).some(expanded => couldNameAnIgnoreFile(expanded)));
|
|
500
|
+
};
|
|
501
|
+
|
|
430
502
|
// 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
503
|
const getRulePrune = ({pattern, directory}, {cwd, matcher, hasNegations, canSkipAtAnyDepth, canMatchIgnoreFile, gitignoreOnlySearch}) => {
|
|
432
504
|
if (isNegativePattern(pattern)) {
|
|
@@ -472,11 +544,14 @@ const getRulePrune = ({pattern, directory}, {cwd, matcher, hasNegations, canSkip
|
|
|
472
544
|
return undefined;
|
|
473
545
|
}
|
|
474
546
|
|
|
547
|
+
// The guard name is compared against negations as rule text, so it has to keep the escapes the rule was written with; the target has already lost them.
|
|
548
|
+
const guardName = finalSegment(anchoredBody);
|
|
549
|
+
|
|
475
550
|
if (isGlob) {
|
|
476
551
|
// A glob does not name a concrete path, so the matcher cannot confirm it is ignored.
|
|
477
552
|
return hasNegations
|
|
478
553
|
? undefined
|
|
479
|
-
: {pattern: toFastGlob(target), guardName
|
|
554
|
+
: {pattern: toFastGlob(target), guardName};
|
|
480
555
|
}
|
|
481
556
|
|
|
482
557
|
if (!matcher(path.resolve(cwd, target) + path.sep).ignored) {
|
|
@@ -487,7 +562,7 @@ const getRulePrune = ({pattern, directory}, {cwd, matcher, hasNegations, canSkip
|
|
|
487
562
|
const needsGuard = !gitignoreOnlySearch || target.includes('/');
|
|
488
563
|
return {
|
|
489
564
|
pattern: toFastGlob(fastGlob.escapePath(target)),
|
|
490
|
-
guardName: needsGuard ?
|
|
565
|
+
guardName: needsGuard ? guardName : undefined,
|
|
491
566
|
};
|
|
492
567
|
};
|
|
493
568
|
|