import process from 'node:process'; import fs from 'node:fs'; import fsPromises from 'node:fs/promises'; import path from 'node:path'; import fastGlob from 'fast-glob'; import gitIgnore from 'ignore'; import slash from 'slash'; import {toPath} from 'unicorn-magic'; import {isNegativePattern} from './utilities.js'; const defaultIgnoredDirectories = [ '**/node_modules', '**/flow-typed', '**/coverage', '**/.git', ]; const ignoreFilesGlobOptions = { absolute: true, dot: true, }; export const GITIGNORE_FILES_PATTERN = '**/.gitignore'; const applyBaseToPattern = (pattern, base) => isNegativePattern(pattern) ? '!' + path.posix.join(base, pattern.slice(1)) : path.posix.join(base, pattern); const parseIgnoreFile = (file, cwd) => { const base = slash(path.relative(cwd, path.dirname(file.filePath))); return file.content .split(/\r?\n/) .filter(line => line && !line.startsWith('#')) .map(pattern => applyBaseToPattern(pattern, base)); }; const toRelativePath = (fileOrDirectory, cwd) => { cwd = slash(cwd); if (path.isAbsolute(fileOrDirectory)) { if (slash(fileOrDirectory).startsWith(cwd)) { return path.relative(cwd, fileOrDirectory); } throw new Error(`Path ${fileOrDirectory} is not in cwd ${cwd}`); } return fileOrDirectory; }; const getIsIgnoredPredicate = (files, cwd) => { const patterns = files.flatMap(file => parseIgnoreFile(file, cwd)); const ignores = gitIgnore().add(patterns); return fileOrDirectory => { fileOrDirectory = toPath(fileOrDirectory); fileOrDirectory = toRelativePath(fileOrDirectory, cwd); return fileOrDirectory ? ignores.ignores(slash(fileOrDirectory)) : false; }; }; const normalizeOptions = (options = {}) => ({ cwd: toPath(options.cwd) ?? process.cwd(), suppressErrors: Boolean(options.suppressErrors), deep: typeof options.deep === 'number' ? options.deep : Number.POSITIVE_INFINITY, ignore: [...options.ignore ?? [], ...defaultIgnoredDirectories], }); export const isIgnoredByIgnoreFiles = async (patterns, options) => { const {cwd, suppressErrors, deep, ignore} = normalizeOptions(options); const paths = await fastGlob(patterns, { cwd, suppressErrors, deep, ignore, ...ignoreFilesGlobOptions, }); const files = await Promise.all( paths.map(async filePath => ({ filePath, content: await fsPromises.readFile(filePath, 'utf8'), })), ); return getIsIgnoredPredicate(files, cwd); }; export const isIgnoredByIgnoreFilesSync = (patterns, options) => { const {cwd, suppressErrors, deep, ignore} = normalizeOptions(options); const paths = fastGlob.sync(patterns, { cwd, suppressErrors, deep, ignore, ...ignoreFilesGlobOptions, }); const files = paths.map(filePath => ({ filePath, content: fs.readFileSync(filePath, 'utf8'), })); return getIsIgnoredPredicate(files, cwd); }; export const isGitIgnored = options => isIgnoredByIgnoreFiles(GITIGNORE_FILES_PATTERN, options); export const isGitIgnoredSync = options => isIgnoredByIgnoreFilesSync(GITIGNORE_FILES_PATTERN, options);