index.js 7.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266
  1. import process from 'node:process';
  2. import fs from 'node:fs';
  3. import nodePath from 'node:path';
  4. import mergeStreams from '@sindresorhus/merge-streams';
  5. import fastGlob from 'fast-glob';
  6. import {isDirectory, isDirectorySync} from 'path-type';
  7. import {toPath} from 'unicorn-magic';
  8. import {
  9. GITIGNORE_FILES_PATTERN,
  10. isIgnoredByIgnoreFiles,
  11. isIgnoredByIgnoreFilesSync,
  12. } from './ignore.js';
  13. import {isNegativePattern} from './utilities.js';
  14. const assertPatternsInput = patterns => {
  15. if (patterns.some(pattern => typeof pattern !== 'string')) {
  16. throw new TypeError('Patterns must be a string or an array of strings');
  17. }
  18. };
  19. const normalizePathForDirectoryGlob = (filePath, cwd) => {
  20. const path = isNegativePattern(filePath) ? filePath.slice(1) : filePath;
  21. return nodePath.isAbsolute(path) ? path : nodePath.join(cwd, path);
  22. };
  23. const getDirectoryGlob = ({directoryPath, files, extensions}) => {
  24. const extensionGlob = extensions?.length > 0 ? `.${extensions.length > 1 ? `{${extensions.join(',')}}` : extensions[0]}` : '';
  25. return files
  26. ? files.map(file => nodePath.posix.join(directoryPath, `**/${nodePath.extname(file) ? file : `${file}${extensionGlob}`}`))
  27. : [nodePath.posix.join(directoryPath, `**${extensionGlob ? `/*${extensionGlob}` : ''}`)];
  28. };
  29. const directoryToGlob = async (directoryPaths, {
  30. cwd = process.cwd(),
  31. files,
  32. extensions,
  33. } = {}) => {
  34. const globs = await Promise.all(directoryPaths.map(async directoryPath =>
  35. (await isDirectory(normalizePathForDirectoryGlob(directoryPath, cwd))) ? getDirectoryGlob({directoryPath, files, extensions}) : directoryPath),
  36. );
  37. return globs.flat();
  38. };
  39. const directoryToGlobSync = (directoryPaths, {
  40. cwd = process.cwd(),
  41. files,
  42. extensions,
  43. } = {}) => directoryPaths.flatMap(directoryPath => isDirectorySync(normalizePathForDirectoryGlob(directoryPath, cwd)) ? getDirectoryGlob({directoryPath, files, extensions}) : directoryPath);
  44. const toPatternsArray = patterns => {
  45. patterns = [...new Set([patterns].flat())];
  46. assertPatternsInput(patterns);
  47. return patterns;
  48. };
  49. const checkCwdOption = cwd => {
  50. if (!cwd) {
  51. return;
  52. }
  53. let stat;
  54. try {
  55. stat = fs.statSync(cwd);
  56. } catch {
  57. return;
  58. }
  59. if (!stat.isDirectory()) {
  60. throw new Error('The `cwd` option must be a path to a directory');
  61. }
  62. };
  63. const normalizeOptions = (options = {}) => {
  64. options = {
  65. ...options,
  66. ignore: options.ignore ?? [],
  67. expandDirectories: options.expandDirectories ?? true,
  68. cwd: toPath(options.cwd),
  69. };
  70. checkCwdOption(options.cwd);
  71. return options;
  72. };
  73. const normalizeArguments = function_ => async (patterns, options) => function_(toPatternsArray(patterns), normalizeOptions(options));
  74. const normalizeArgumentsSync = function_ => (patterns, options) => function_(toPatternsArray(patterns), normalizeOptions(options));
  75. const getIgnoreFilesPatterns = options => {
  76. const {ignoreFiles, gitignore} = options;
  77. const patterns = ignoreFiles ? toPatternsArray(ignoreFiles) : [];
  78. if (gitignore) {
  79. patterns.push(GITIGNORE_FILES_PATTERN);
  80. }
  81. return patterns;
  82. };
  83. const getFilter = async options => {
  84. const ignoreFilesPatterns = getIgnoreFilesPatterns(options);
  85. return createFilterFunction(
  86. ignoreFilesPatterns.length > 0 && await isIgnoredByIgnoreFiles(ignoreFilesPatterns, options),
  87. );
  88. };
  89. const getFilterSync = options => {
  90. const ignoreFilesPatterns = getIgnoreFilesPatterns(options);
  91. return createFilterFunction(
  92. ignoreFilesPatterns.length > 0 && isIgnoredByIgnoreFilesSync(ignoreFilesPatterns, options),
  93. );
  94. };
  95. const createFilterFunction = isIgnored => {
  96. const seen = new Set();
  97. return fastGlobResult => {
  98. const pathKey = nodePath.normalize(fastGlobResult.path ?? fastGlobResult);
  99. if (seen.has(pathKey) || (isIgnored && isIgnored(pathKey))) {
  100. return false;
  101. }
  102. seen.add(pathKey);
  103. return true;
  104. };
  105. };
  106. const unionFastGlobResults = (results, filter) => results.flat().filter(fastGlobResult => filter(fastGlobResult));
  107. const convertNegativePatterns = (patterns, options) => {
  108. const tasks = [];
  109. while (patterns.length > 0) {
  110. const index = patterns.findIndex(pattern => isNegativePattern(pattern));
  111. if (index === -1) {
  112. tasks.push({patterns, options});
  113. break;
  114. }
  115. const ignorePattern = patterns[index].slice(1);
  116. for (const task of tasks) {
  117. task.options.ignore.push(ignorePattern);
  118. }
  119. if (index !== 0) {
  120. tasks.push({
  121. patterns: patterns.slice(0, index),
  122. options: {
  123. ...options,
  124. ignore: [
  125. ...options.ignore,
  126. ignorePattern,
  127. ],
  128. },
  129. });
  130. }
  131. patterns = patterns.slice(index + 1);
  132. }
  133. return tasks;
  134. };
  135. const normalizeExpandDirectoriesOption = (options, cwd) => ({
  136. ...(cwd ? {cwd} : {}),
  137. ...(Array.isArray(options) ? {files: options} : options),
  138. });
  139. const generateTasks = async (patterns, options) => {
  140. const globTasks = convertNegativePatterns(patterns, options);
  141. const {cwd, expandDirectories} = options;
  142. if (!expandDirectories) {
  143. return globTasks;
  144. }
  145. const directoryToGlobOptions = normalizeExpandDirectoriesOption(expandDirectories, cwd);
  146. return Promise.all(
  147. globTasks.map(async task => {
  148. let {patterns, options} = task;
  149. [
  150. patterns,
  151. options.ignore,
  152. ] = await Promise.all([
  153. directoryToGlob(patterns, directoryToGlobOptions),
  154. directoryToGlob(options.ignore, {cwd}),
  155. ]);
  156. return {patterns, options};
  157. }),
  158. );
  159. };
  160. const generateTasksSync = (patterns, options) => {
  161. const globTasks = convertNegativePatterns(patterns, options);
  162. const {cwd, expandDirectories} = options;
  163. if (!expandDirectories) {
  164. return globTasks;
  165. }
  166. const directoryToGlobSyncOptions = normalizeExpandDirectoriesOption(expandDirectories, cwd);
  167. return globTasks.map(task => {
  168. let {patterns, options} = task;
  169. patterns = directoryToGlobSync(patterns, directoryToGlobSyncOptions);
  170. options.ignore = directoryToGlobSync(options.ignore, {cwd});
  171. return {patterns, options};
  172. });
  173. };
  174. export const globby = normalizeArguments(async (patterns, options) => {
  175. const [
  176. tasks,
  177. filter,
  178. ] = await Promise.all([
  179. generateTasks(patterns, options),
  180. getFilter(options),
  181. ]);
  182. const results = await Promise.all(tasks.map(task => fastGlob(task.patterns, task.options)));
  183. return unionFastGlobResults(results, filter);
  184. });
  185. export const globbySync = normalizeArgumentsSync((patterns, options) => {
  186. const tasks = generateTasksSync(patterns, options);
  187. const filter = getFilterSync(options);
  188. const results = tasks.map(task => fastGlob.sync(task.patterns, task.options));
  189. return unionFastGlobResults(results, filter);
  190. });
  191. export const globbyStream = normalizeArgumentsSync((patterns, options) => {
  192. const tasks = generateTasksSync(patterns, options);
  193. const filter = getFilterSync(options);
  194. const streams = tasks.map(task => fastGlob.stream(task.patterns, task.options));
  195. const stream = mergeStreams(streams).filter(fastGlobResult => filter(fastGlobResult));
  196. // TODO: Make it return a web stream at some point.
  197. // return Readable.toWeb(stream);
  198. return stream;
  199. });
  200. export const isDynamicPattern = normalizeArgumentsSync(
  201. (patterns, options) => patterns.some(pattern => fastGlob.isDynamicPattern(pattern, options)),
  202. );
  203. export const generateGlobTasks = normalizeArguments(generateTasks);
  204. export const generateGlobTasksSync = normalizeArgumentsSync(generateTasksSync);
  205. export {
  206. isGitIgnored,
  207. isGitIgnoredSync,
  208. isIgnoredByIgnoreFiles,
  209. isIgnoredByIgnoreFilesSync,
  210. } from './ignore.js';
  211. export const {convertPathToPattern} = fastGlob;