ignore.js 2.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109
  1. import process from 'node:process';
  2. import fs from 'node:fs';
  3. import fsPromises from 'node:fs/promises';
  4. import path from 'node:path';
  5. import fastGlob from 'fast-glob';
  6. import gitIgnore from 'ignore';
  7. import slash from 'slash';
  8. import {toPath} from 'unicorn-magic';
  9. import {isNegativePattern} from './utilities.js';
  10. const defaultIgnoredDirectories = [
  11. '**/node_modules',
  12. '**/flow-typed',
  13. '**/coverage',
  14. '**/.git',
  15. ];
  16. const ignoreFilesGlobOptions = {
  17. absolute: true,
  18. dot: true,
  19. };
  20. export const GITIGNORE_FILES_PATTERN = '**/.gitignore';
  21. const applyBaseToPattern = (pattern, base) => isNegativePattern(pattern)
  22. ? '!' + path.posix.join(base, pattern.slice(1))
  23. : path.posix.join(base, pattern);
  24. const parseIgnoreFile = (file, cwd) => {
  25. const base = slash(path.relative(cwd, path.dirname(file.filePath)));
  26. return file.content
  27. .split(/\r?\n/)
  28. .filter(line => line && !line.startsWith('#'))
  29. .map(pattern => applyBaseToPattern(pattern, base));
  30. };
  31. const toRelativePath = (fileOrDirectory, cwd) => {
  32. cwd = slash(cwd);
  33. if (path.isAbsolute(fileOrDirectory)) {
  34. if (slash(fileOrDirectory).startsWith(cwd)) {
  35. return path.relative(cwd, fileOrDirectory);
  36. }
  37. throw new Error(`Path ${fileOrDirectory} is not in cwd ${cwd}`);
  38. }
  39. return fileOrDirectory;
  40. };
  41. const getIsIgnoredPredicate = (files, cwd) => {
  42. const patterns = files.flatMap(file => parseIgnoreFile(file, cwd));
  43. const ignores = gitIgnore().add(patterns);
  44. return fileOrDirectory => {
  45. fileOrDirectory = toPath(fileOrDirectory);
  46. fileOrDirectory = toRelativePath(fileOrDirectory, cwd);
  47. return fileOrDirectory ? ignores.ignores(slash(fileOrDirectory)) : false;
  48. };
  49. };
  50. const normalizeOptions = (options = {}) => ({
  51. cwd: toPath(options.cwd) ?? process.cwd(),
  52. suppressErrors: Boolean(options.suppressErrors),
  53. deep: typeof options.deep === 'number' ? options.deep : Number.POSITIVE_INFINITY,
  54. ignore: [...options.ignore ?? [], ...defaultIgnoredDirectories],
  55. });
  56. export const isIgnoredByIgnoreFiles = async (patterns, options) => {
  57. const {cwd, suppressErrors, deep, ignore} = normalizeOptions(options);
  58. const paths = await fastGlob(patterns, {
  59. cwd,
  60. suppressErrors,
  61. deep,
  62. ignore,
  63. ...ignoreFilesGlobOptions,
  64. });
  65. const files = await Promise.all(
  66. paths.map(async filePath => ({
  67. filePath,
  68. content: await fsPromises.readFile(filePath, 'utf8'),
  69. })),
  70. );
  71. return getIsIgnoredPredicate(files, cwd);
  72. };
  73. export const isIgnoredByIgnoreFilesSync = (patterns, options) => {
  74. const {cwd, suppressErrors, deep, ignore} = normalizeOptions(options);
  75. const paths = fastGlob.sync(patterns, {
  76. cwd,
  77. suppressErrors,
  78. deep,
  79. ignore,
  80. ...ignoreFilesGlobOptions,
  81. });
  82. const files = paths.map(filePath => ({
  83. filePath,
  84. content: fs.readFileSync(filePath, 'utf8'),
  85. }));
  86. return getIsIgnoredPredicate(files, cwd);
  87. };
  88. export const isGitIgnored = options => isIgnoredByIgnoreFiles(GITIGNORE_FILES_PATTERN, options);
  89. export const isGitIgnoredSync = options => isIgnoredByIgnoreFilesSync(GITIGNORE_FILES_PATTERN, options);