site/node_modules/globby/index.js
2024-10-14 08:09:33 +02:00

265 lines
7.2 KiB
JavaScript

import process from 'node:process';
import fs from 'node:fs';
import nodePath from 'node:path';
import mergeStreams from '@sindresorhus/merge-streams';
import fastGlob from 'fast-glob';
import {isDirectory, isDirectorySync} from 'path-type';
import {toPath} from 'unicorn-magic';
import {
GITIGNORE_FILES_PATTERN,
isIgnoredByIgnoreFiles,
isIgnoredByIgnoreFilesSync,
} from './ignore.js';
import {isNegativePattern} from './utilities.js';
const assertPatternsInput = patterns => {
if (patterns.some(pattern => typeof pattern !== 'string')) {
throw new TypeError('Patterns must be a string or an array of strings');
}
};
const normalizePathForDirectoryGlob = (filePath, cwd) => {
const path = isNegativePattern(filePath) ? filePath.slice(1) : filePath;
return nodePath.isAbsolute(path) ? path : nodePath.join(cwd, path);
};
const getDirectoryGlob = ({directoryPath, files, extensions}) => {
const extensionGlob = extensions?.length > 0 ? `.${extensions.length > 1 ? `{${extensions.join(',')}}` : extensions[0]}` : '';
return files
? files.map(file => nodePath.posix.join(directoryPath, `**/${nodePath.extname(file) ? file : `${file}${extensionGlob}`}`))
: [nodePath.posix.join(directoryPath, `**${extensionGlob ? `/*${extensionGlob}` : ''}`)];
};
const directoryToGlob = async (directoryPaths, {
cwd = process.cwd(),
files,
extensions,
} = {}) => {
const globs = await Promise.all(directoryPaths.map(async directoryPath =>
(await isDirectory(normalizePathForDirectoryGlob(directoryPath, cwd))) ? getDirectoryGlob({directoryPath, files, extensions}) : directoryPath),
);
return globs.flat();
};
const directoryToGlobSync = (directoryPaths, {
cwd = process.cwd(),
files,
extensions,
} = {}) => directoryPaths.flatMap(directoryPath => isDirectorySync(normalizePathForDirectoryGlob(directoryPath, cwd)) ? getDirectoryGlob({directoryPath, files, extensions}) : directoryPath);
const toPatternsArray = patterns => {
patterns = [...new Set([patterns].flat())];
assertPatternsInput(patterns);
return patterns;
};
const checkCwdOption = cwd => {
if (!cwd) {
return;
}
let stat;
try {
stat = fs.statSync(cwd);
} catch {
return;
}
if (!stat.isDirectory()) {
throw new Error('The `cwd` option must be a path to a directory');
}
};
const normalizeOptions = (options = {}) => {
options = {
...options,
ignore: options.ignore ?? [],
expandDirectories: options.expandDirectories ?? true,
cwd: toPath(options.cwd),
};
checkCwdOption(options.cwd);
return options;
};
const normalizeArguments = function_ => async (patterns, options) => function_(toPatternsArray(patterns), normalizeOptions(options));
const normalizeArgumentsSync = function_ => (patterns, options) => function_(toPatternsArray(patterns), normalizeOptions(options));
const getIgnoreFilesPatterns = options => {
const {ignoreFiles, gitignore} = options;
const patterns = ignoreFiles ? toPatternsArray(ignoreFiles) : [];
if (gitignore) {
patterns.push(GITIGNORE_FILES_PATTERN);
}
return patterns;
};
const getFilter = async options => {
const ignoreFilesPatterns = getIgnoreFilesPatterns(options);
return createFilterFunction(
ignoreFilesPatterns.length > 0 && await isIgnoredByIgnoreFiles(ignoreFilesPatterns, options),
);
};
const getFilterSync = options => {
const ignoreFilesPatterns = getIgnoreFilesPatterns(options);
return createFilterFunction(
ignoreFilesPatterns.length > 0 && isIgnoredByIgnoreFilesSync(ignoreFilesPatterns, options),
);
};
const createFilterFunction = isIgnored => {
const seen = new Set();
return fastGlobResult => {
const pathKey = nodePath.normalize(fastGlobResult.path ?? fastGlobResult);
if (seen.has(pathKey) || (isIgnored && isIgnored(pathKey))) {
return false;
}
seen.add(pathKey);
return true;
};
};
const unionFastGlobResults = (results, filter) => results.flat().filter(fastGlobResult => filter(fastGlobResult));
const convertNegativePatterns = (patterns, options) => {
const tasks = [];
while (patterns.length > 0) {
const index = patterns.findIndex(pattern => isNegativePattern(pattern));
if (index === -1) {
tasks.push({patterns, options});
break;
}
const ignorePattern = patterns[index].slice(1);
for (const task of tasks) {
task.options.ignore.push(ignorePattern);
}
if (index !== 0) {
tasks.push({
patterns: patterns.slice(0, index),
options: {
...options,
ignore: [
...options.ignore,
ignorePattern,
],
},
});
}
patterns = patterns.slice(index + 1);
}
return tasks;
};
const normalizeExpandDirectoriesOption = (options, cwd) => ({
...(cwd ? {cwd} : {}),
...(Array.isArray(options) ? {files: options} : options),
});
const generateTasks = async (patterns, options) => {
const globTasks = convertNegativePatterns(patterns, options);
const {cwd, expandDirectories} = options;
if (!expandDirectories) {
return globTasks;
}
const directoryToGlobOptions = normalizeExpandDirectoriesOption(expandDirectories, cwd);
return Promise.all(
globTasks.map(async task => {
let {patterns, options} = task;
[
patterns,
options.ignore,
] = await Promise.all([
directoryToGlob(patterns, directoryToGlobOptions),
directoryToGlob(options.ignore, {cwd}),
]);
return {patterns, options};
}),
);
};
const generateTasksSync = (patterns, options) => {
const globTasks = convertNegativePatterns(patterns, options);
const {cwd, expandDirectories} = options;
if (!expandDirectories) {
return globTasks;
}
const directoryToGlobSyncOptions = normalizeExpandDirectoriesOption(expandDirectories, cwd);
return globTasks.map(task => {
let {patterns, options} = task;
patterns = directoryToGlobSync(patterns, directoryToGlobSyncOptions);
options.ignore = directoryToGlobSync(options.ignore, {cwd});
return {patterns, options};
});
};
export const globby = normalizeArguments(async (patterns, options) => {
const [
tasks,
filter,
] = await Promise.all([
generateTasks(patterns, options),
getFilter(options),
]);
const results = await Promise.all(tasks.map(task => fastGlob(task.patterns, task.options)));
return unionFastGlobResults(results, filter);
});
export const globbySync = normalizeArgumentsSync((patterns, options) => {
const tasks = generateTasksSync(patterns, options);
const filter = getFilterSync(options);
const results = tasks.map(task => fastGlob.sync(task.patterns, task.options));
return unionFastGlobResults(results, filter);
});
export const globbyStream = normalizeArgumentsSync((patterns, options) => {
const tasks = generateTasksSync(patterns, options);
const filter = getFilterSync(options);
const streams = tasks.map(task => fastGlob.stream(task.patterns, task.options));
const stream = mergeStreams(streams).filter(fastGlobResult => filter(fastGlobResult));
// TODO: Make it return a web stream at some point.
// return Readable.toWeb(stream);
return stream;
});
export const isDynamicPattern = normalizeArgumentsSync(
(patterns, options) => patterns.some(pattern => fastGlob.isDynamicPattern(pattern, options)),
);
export const generateGlobTasks = normalizeArguments(generateTasks);
export const generateGlobTasksSync = normalizeArgumentsSync(generateTasksSync);
export {
isGitIgnored,
isGitIgnoredSync,
} from './ignore.js';
export const {convertPathToPattern} = fastGlob;