index.js 5.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227
  1. import fs from 'node:fs';
  2. import nodePath from 'node:path';
  3. import merge2 from 'merge2';
  4. import fastGlob from 'fast-glob';
  5. import dirGlob from 'dir-glob';
  6. import {
  7. GITIGNORE_FILES_PATTERN,
  8. isIgnoredByIgnoreFiles,
  9. isIgnoredByIgnoreFilesSync,
  10. } from './ignore.js';
  11. import {FilterStream, toPath, isNegativePattern} from './utilities.js';
  12. const assertPatternsInput = patterns => {
  13. if (patterns.some(pattern => typeof pattern !== 'string')) {
  14. throw new TypeError('Patterns must be a string or an array of strings');
  15. }
  16. };
  17. const toPatternsArray = patterns => {
  18. patterns = [...new Set([patterns].flat())];
  19. assertPatternsInput(patterns);
  20. return patterns;
  21. };
  22. const checkCwdOption = options => {
  23. if (!options.cwd) {
  24. return;
  25. }
  26. let stat;
  27. try {
  28. stat = fs.statSync(options.cwd);
  29. } catch {
  30. return;
  31. }
  32. if (!stat.isDirectory()) {
  33. throw new Error('The `cwd` option must be a path to a directory');
  34. }
  35. };
  36. const normalizeOptions = (options = {}) => {
  37. options = {
  38. ignore: [],
  39. expandDirectories: true,
  40. ...options,
  41. cwd: toPath(options.cwd),
  42. };
  43. checkCwdOption(options);
  44. return options;
  45. };
  46. const normalizeArguments = fn => async (patterns, options) => fn(toPatternsArray(patterns), normalizeOptions(options));
  47. const normalizeArgumentsSync = fn => (patterns, options) => fn(toPatternsArray(patterns), normalizeOptions(options));
  48. const getIgnoreFilesPatterns = options => {
  49. const {ignoreFiles, gitignore} = options;
  50. const patterns = ignoreFiles ? toPatternsArray(ignoreFiles) : [];
  51. if (gitignore) {
  52. patterns.push(GITIGNORE_FILES_PATTERN);
  53. }
  54. return patterns;
  55. };
  56. const getFilter = async options => {
  57. const ignoreFilesPatterns = getIgnoreFilesPatterns(options);
  58. return createFilterFunction(
  59. ignoreFilesPatterns.length > 0 && await isIgnoredByIgnoreFiles(ignoreFilesPatterns, options),
  60. );
  61. };
  62. const getFilterSync = options => {
  63. const ignoreFilesPatterns = getIgnoreFilesPatterns(options);
  64. return createFilterFunction(
  65. ignoreFilesPatterns.length > 0 && isIgnoredByIgnoreFilesSync(ignoreFilesPatterns, options),
  66. );
  67. };
  68. const createFilterFunction = isIgnored => {
  69. const seen = new Set();
  70. return fastGlobResult => {
  71. const path = fastGlobResult.path || fastGlobResult;
  72. const pathKey = nodePath.normalize(path);
  73. const seenOrIgnored = seen.has(pathKey) || (isIgnored && isIgnored(path));
  74. seen.add(pathKey);
  75. return !seenOrIgnored;
  76. };
  77. };
  78. const unionFastGlobResults = (results, filter) => results.flat().filter(fastGlobResult => filter(fastGlobResult));
  79. const unionFastGlobStreams = (streams, filter) => merge2(streams).pipe(new FilterStream(fastGlobResult => filter(fastGlobResult)));
  80. const convertNegativePatterns = (patterns, options) => {
  81. const tasks = [];
  82. while (patterns.length > 0) {
  83. const index = patterns.findIndex(pattern => isNegativePattern(pattern));
  84. if (index === -1) {
  85. tasks.push({patterns, options});
  86. break;
  87. }
  88. const ignorePattern = patterns[index].slice(1);
  89. for (const task of tasks) {
  90. task.options.ignore.push(ignorePattern);
  91. }
  92. if (index !== 0) {
  93. tasks.push({
  94. patterns: patterns.slice(0, index),
  95. options: {
  96. ...options,
  97. ignore: [
  98. ...options.ignore,
  99. ignorePattern,
  100. ],
  101. },
  102. });
  103. }
  104. patterns = patterns.slice(index + 1);
  105. }
  106. return tasks;
  107. };
  108. const getDirGlobOptions = (options, cwd) => ({
  109. ...(cwd ? {cwd} : {}),
  110. ...(Array.isArray(options) ? {files: options} : options),
  111. });
  112. const generateTasks = async (patterns, options) => {
  113. const globTasks = convertNegativePatterns(patterns, options);
  114. const {cwd, expandDirectories} = options;
  115. if (!expandDirectories) {
  116. return globTasks;
  117. }
  118. const patternExpandOptions = getDirGlobOptions(expandDirectories, cwd);
  119. const ignoreExpandOptions = cwd ? {cwd} : undefined;
  120. return Promise.all(
  121. globTasks.map(async task => {
  122. let {patterns, options} = task;
  123. [
  124. patterns,
  125. options.ignore,
  126. ] = await Promise.all([
  127. dirGlob(patterns, patternExpandOptions),
  128. dirGlob(options.ignore, ignoreExpandOptions),
  129. ]);
  130. return {patterns, options};
  131. }),
  132. );
  133. };
  134. const generateTasksSync = (patterns, options) => {
  135. const globTasks = convertNegativePatterns(patterns, options);
  136. const {cwd, expandDirectories} = options;
  137. if (!expandDirectories) {
  138. return globTasks;
  139. }
  140. const patternExpandOptions = getDirGlobOptions(expandDirectories, cwd);
  141. const ignoreExpandOptions = cwd ? {cwd} : undefined;
  142. return globTasks.map(task => {
  143. let {patterns, options} = task;
  144. patterns = dirGlob.sync(patterns, patternExpandOptions);
  145. options.ignore = dirGlob.sync(options.ignore, ignoreExpandOptions);
  146. return {patterns, options};
  147. });
  148. };
  149. export const globby = normalizeArguments(async (patterns, options) => {
  150. const [
  151. tasks,
  152. filter,
  153. ] = await Promise.all([
  154. generateTasks(patterns, options),
  155. getFilter(options),
  156. ]);
  157. const results = await Promise.all(tasks.map(task => fastGlob(task.patterns, task.options)));
  158. return unionFastGlobResults(results, filter);
  159. });
  160. export const globbySync = normalizeArgumentsSync((patterns, options) => {
  161. const tasks = generateTasksSync(patterns, options);
  162. const filter = getFilterSync(options);
  163. const results = tasks.map(task => fastGlob.sync(task.patterns, task.options));
  164. return unionFastGlobResults(results, filter);
  165. });
  166. export const globbyStream = normalizeArgumentsSync((patterns, options) => {
  167. const tasks = generateTasksSync(patterns, options);
  168. const filter = getFilterSync(options);
  169. const streams = tasks.map(task => fastGlob.stream(task.patterns, task.options));
  170. return unionFastGlobStreams(streams, filter);
  171. });
  172. export const isDynamicPattern = normalizeArgumentsSync(
  173. (patterns, options) => patterns.some(pattern => fastGlob.isDynamicPattern(pattern, options)),
  174. );
  175. export const generateGlobTasks = normalizeArguments(generateTasks);
  176. export const generateGlobTasksSync = normalizeArgumentsSync(generateTasksSync);
  177. export {
  178. isGitIgnored,
  179. isGitIgnoredSync,
  180. } from './ignore.js';