'use strict'; const fs = require('fs'); const arrayUnion = require('array-union'); const merge2 = require('merge2'); const fastGlob = require('fast-glob'); const dirGlob = require('dir-glob'); const gitignore = require('./gitignore'); const { FilterStream, UniqueStream } = require('./stream-utils'); const DEFAULT_FILTER = () => false; const isNegative = (pattern) => pattern[0] === '!'; const assertPatternsInput = (patterns) => { if (!patterns.every((pattern) => typeof pattern === 'string')) { throw new TypeError('Patterns must be a string or an array of strings'); } }; const checkCwdOption = (options = {}) => { if (!options.cwd) { return; } let stat; try { stat = fs.statSync(options.cwd); } catch { return; } if (!stat.isDirectory()) { throw new Error('The `cwd` option must be a path to a directory'); } }; const getPathString = (p) => (p.stats instanceof fs.Stats ? p.path : p); const generateGlobTasks = (patterns, taskOptions) => { patterns = arrayUnion([].concat(patterns)); assertPatternsInput(patterns); checkCwdOption(taskOptions); const globTasks = []; taskOptions = { ignore: [], expandDirectories: true, ...taskOptions, }; for (const [index, pattern] of patterns.entries()) { if (isNegative(pattern)) { continue; } const ignore = patterns .slice(index) .filter((pattern) => isNegative(pattern)) .map((pattern) => pattern.slice(1)); const options = { ...taskOptions, ignore: taskOptions.ignore.concat(ignore), }; globTasks.push({ pattern, options }); } return globTasks; }; const globDirs = (task, fn) => { let options = {}; if (task.options.cwd) { options.cwd = task.options.cwd; } if (Array.isArray(task.options.expandDirectories)) { options = { ...options, files: task.options.expandDirectories, }; } else if (typeof task.options.expandDirectories === 'object') { options = { ...options, ...task.options.expandDirectories, }; } return fn(task.pattern, options); }; const getPattern = (task, fn) => task.options.expandDirectories ? globDirs(task, fn) : [task.pattern]; const getFilterSync = (options) => { return options && options.gitignore ? gitignore.sync({ cwd: options.cwd, ignore: options.ignore }) : DEFAULT_FILTER; }; const globToTask = (task) => (glob) => { const { options } = task; if ( options.ignore && Array.isArray(options.ignore) && options.expandDirectories ) { options.ignore = dirGlob.sync(options.ignore); } return { pattern: glob, options, }; }; module.exports = async (patterns, options) => { const globTasks = generateGlobTasks(patterns, options); const getFilter = async () => { return options && options.gitignore ? gitignore({ cwd: options.cwd, ignore: options.ignore }) : DEFAULT_FILTER; }; const getTasks = async () => { const tasks = await Promise.all( globTasks.map(async (task) => { const globs = await getPattern(task, dirGlob); return Promise.all(globs.map(globToTask(task))); }) ); return arrayUnion(...tasks); }; const [filter, tasks] = await Promise.all([getFilter(), getTasks()]); const paths = await Promise.all( tasks.map((task) => fastGlob(task.pattern, task.options)) ); return arrayUnion(...paths).filter((path_) => !filter(getPathString(path_))); }; module.exports.sync = (patterns, options) => { const globTasks = generateGlobTasks(patterns, options); const tasks = []; for (const task of globTasks) { const newTask = getPattern(task, dirGlob.sync).map(globToTask(task)); tasks.push(...newTask); } const filter = getFilterSync(options); let matches = []; for (const task of tasks) { matches = arrayUnion(matches, fastGlob.sync(task.pattern, task.options)); } return matches.filter((path_) => !filter(path_)); }; module.exports.stream = (patterns, options) => { const globTasks = generateGlobTasks(patterns, options); const tasks = []; for (const task of globTasks) { const newTask = getPattern(task, dirGlob.sync).map(globToTask(task)); tasks.push(...newTask); } const filter = getFilterSync(options); const filterStream = new FilterStream((p) => !filter(p)); const uniqueStream = new UniqueStream(); return merge2( tasks.map((task) => fastGlob.stream(task.pattern, task.options)) ) .pipe(filterStream) .pipe(uniqueStream); }; module.exports.generateGlobTasks = generateGlobTasks; module.exports.hasMagic = (patterns, options) => [] .concat(patterns) .some((pattern) => fastGlob.isDynamicPattern(pattern, options)); module.exports.gitignore = gitignore;