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 |
|
13 | const 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 |
|
19 | const toPatternsArray = patterns => {
|
20 | patterns = [...new Set([patterns].flat())];
|
21 | assertPatternsInput(patterns);
|
22 | return patterns;
|
23 | };
|
24 |
|
25 | const 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 |
|
42 | const 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 |
|
55 | const normalizeArguments = fn => async (patterns, options) => fn(toPatternsArray(patterns), normalizeOptions(options));
|
56 | const normalizeArgumentsSync = fn => (patterns, options) => fn(toPatternsArray(patterns), normalizeOptions(options));
|
57 |
|
58 | const 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 |
|
69 | const getFilter = async options => {
|
70 | const ignoreFilesPatterns = getIgnoreFilesPatterns(options);
|
71 | return createFilterFunction(
|
72 | ignoreFilesPatterns.length > 0 && await isIgnoredByIgnoreFiles(ignoreFilesPatterns, options),
|
73 | );
|
74 | };
|
75 |
|
76 | const getFilterSync = options => {
|
77 | const ignoreFilesPatterns = getIgnoreFilesPatterns(options);
|
78 | return createFilterFunction(
|
79 | ignoreFilesPatterns.length > 0 && isIgnoredByIgnoreFilesSync(ignoreFilesPatterns, options),
|
80 | );
|
81 | };
|
82 |
|
83 | const 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 |
|
95 | const unionFastGlobResults = (results, filter) => results.flat().filter(fastGlobResult => filter(fastGlobResult));
|
96 | const unionFastGlobStreams = (streams, filter) => merge2(streams).pipe(new FilterStream(fastGlobResult => filter(fastGlobResult)));
|
97 |
|
98 | const 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 |
|
134 | const getDirGlobOptions = (options, cwd) => ({
|
135 | ...(cwd ? {cwd} : {}),
|
136 | ...(Array.isArray(options) ? {files: options} : options),
|
137 | });
|
138 |
|
139 | const 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 |
|
168 | const 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 |
|
188 | export 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 |
|
201 | export 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 |
|
209 | export 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 |
|
217 | export const isDynamicPattern = normalizeArgumentsSync(
|
218 | (patterns, options) => patterns.some(pattern => fastGlob.isDynamicPattern(pattern, options)),
|
219 | );
|
220 |
|
221 | export const generateGlobTasks = normalizeArguments(generateTasks);
|
222 | export const generateGlobTasksSync = normalizeArgumentsSync(generateTasksSync);
|
223 |
|
224 | export {
|
225 | isGitIgnored,
|
226 | isGitIgnoredSync,
|
227 | } from './ignore.js';
|