@cjser/globby 16.2.3-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.
@@ -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-1786379054379/index.js
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-26-1786379054379/ignore.js
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-26-1786379054379/utilities.js
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) => {
@@ -313,6 +314,32 @@ var negationsCouldRescue = (rules, names) => {
313
314
  const couldNameTheSamePath = createNameComparer();
314
315
  return getNegationFinalSegments(rules).some((negation) => names.some((name) => couldNameTheSamePath(name, negation)));
315
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
+ };
316
343
  var getRulePrune = ({ pattern, directory }, { cwd, matcher, hasNegations, canSkipAtAnyDepth, canMatchIgnoreFile, gitignoreOnlySearch }) => {
317
344
  if (isNegativePattern(pattern)) {
318
345
  return void 0;
@@ -384,7 +411,7 @@ var buildPrunePatternsAndGuards = (rules, matcher, cwd, { gitignoreOnlySearch =
384
411
  };
385
412
  var convertPatternsForFastGlob = (rules, matcher, cwd) => buildPrunePatternsAndGuards(rules, matcher, cwd).patterns;
386
413
 
387
- // packages/@cjser/globby.tmp-26-1786379054379/ignore.js
414
+ // packages/@cjser/globby.tmp-25-1788279925204/ignore.js
388
415
  var defaultIgnoredDirectories = [
389
416
  "**/node_modules",
390
417
  "**/flow-typed",
@@ -1072,7 +1099,7 @@ var getIgnorePatternsAndPredicateSync = (patterns, options, includeParentIgnoreF
1072
1099
  var isGitIgnored = (options) => isIgnoredByIgnoreFiles(GITIGNORE_FILES_PATTERN, options);
1073
1100
  var isGitIgnoredSync = (options) => isIgnoredByIgnoreFilesSync(GITIGNORE_FILES_PATTERN, options);
1074
1101
 
1075
- // packages/@cjser/globby.tmp-26-1786379054379/index.js
1102
+ // packages/@cjser/globby.tmp-25-1788279925204/index.js
1076
1103
  var assertPatternsInput = (patterns) => {
1077
1104
  if (patterns.some((pattern) => typeof pattern !== "string")) {
1078
1105
  throw new TypeError("Patterns must be a string or an array of strings");
@@ -1231,6 +1258,10 @@ var buildIgnoreFilterResult = ({ options, cwd, ignoreResult: { rules, matcher },
1231
1258
  filter: createFilter(finalPredicate, cwd, options.fs)
1232
1259
  };
1233
1260
  };
1261
+ var getIgnoreFileSearchOptions = (options, searchPatterns) => ({
1262
+ ...options,
1263
+ ignore: convertIgnorePatternsForIgnoreFileSearch(options.ignore, searchPatterns)
1264
+ });
1234
1265
  var applyIgnoreFilesAndGetFilter = async (options) => {
1235
1266
  const cwd = options.cwd ?? import_node_process2.default.cwd();
1236
1267
  const ignoreFilesPatterns = getIgnoreFilesPatterns(options);
@@ -1243,7 +1274,7 @@ var applyIgnoreFilesAndGetFilter = async (options) => {
1243
1274
  };
1244
1275
  }
1245
1276
  const includeParentIgnoreFiles = options.gitignore === true;
1246
- 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 };
1247
1278
  const globalGitRoot = globalIgnoreFile ? await findGitRoot(cwd, options.fs) : void 0;
1248
1279
  const globalMatcher = globalIgnoreFile ? buildGlobalMatcher(globalIgnoreFile, cwd, globalGitRoot ?? cwd) : void 0;
1249
1280
  return buildIgnoreFilterResult({
@@ -1266,7 +1297,7 @@ var applyIgnoreFilesAndGetFilterSync = (options) => {
1266
1297
  };
1267
1298
  }
1268
1299
  const includeParentIgnoreFiles = options.gitignore === true;
1269
- 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 };
1270
1301
  const globalGitRoot = globalIgnoreFile ? findGitRootSync(cwd, options.fs) : void 0;
1271
1302
  const globalMatcher = globalIgnoreFile ? buildGlobalMatcher(globalIgnoreFile, cwd, globalGitRoot ?? cwd) : void 0;
1272
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
- Performance: Globby reads `.gitignore` files before globbing. When there are no negation patterns (like `!important.log`) and no parent `.gitignore` files are found, it passes ignore patterns to fast-glob to skip traversing ignored directories entirely, which significantly improves performance for large `node_modules` or build directories. When negation patterns or parent `.gitignore` files are present, all filtering is done after traversal to ensure correct Git-compatible behavior. For optimal performance, prefer specific `.gitignore` patterns without negations, or use `ignoreFiles: '.gitignore'` to target only the root ignore file.
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-cjser.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.3",
103
+ "sourceVersion": "16.2.4",
103
104
  "cjserVersion": 2,
104
105
  "original": {
105
106
  "name": "globby",
106
- "version": "16.2.3",
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
@@ -4,6 +4,7 @@ import {promisify} from 'node:util';
4
4
  import fastGlob from 'fast-glob';
5
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] === '!';
@@ -440,6 +441,64 @@ export const negationsCouldRescue = (rules, names) => {
440
441
  return getNegationFinalSegments(rules).some(negation => names.some(name => couldNameTheSamePath(name, negation)));
441
442
  };
442
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
+
443
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.
444
503
  const getRulePrune = ({pattern, directory}, {cwd, matcher, hasNegations, canSkipAtAnyDepth, canMatchIgnoreFile, gitignoreOnlySearch}) => {
445
504
  if (isNegativePattern(pattern)) {