UNPKG

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