gitignore.js 2.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117
  1. 'use strict';
  2. const {promisify} = require('util');
  3. const fs = require('fs');
  4. const path = require('path');
  5. const fastGlob = require('fast-glob');
  6. const gitIgnore = require('ignore');
  7. const slash = require('slash');
  8. const DEFAULT_IGNORE = [
  9. '**/node_modules/**',
  10. '**/flow-typed/**',
  11. '**/coverage/**',
  12. '**/.git'
  13. ];
  14. const readFileP = promisify(fs.readFile);
  15. const mapGitIgnorePatternTo = base => ignore => {
  16. if (ignore.startsWith('!')) {
  17. return '!' + path.posix.join(base, ignore.slice(1));
  18. }
  19. return path.posix.join(base, ignore);
  20. };
  21. const parseGitIgnore = (content, options) => {
  22. const base = slash(path.relative(options.cwd, path.dirname(options.fileName)));
  23. return content
  24. .split(/\r?\n/)
  25. .filter(Boolean)
  26. .filter(line => !line.startsWith('#'))
  27. .map(mapGitIgnorePatternTo(base));
  28. };
  29. const reduceIgnore = files => {
  30. return files.reduce((ignores, file) => {
  31. ignores.add(parseGitIgnore(file.content, {
  32. cwd: file.cwd,
  33. fileName: file.filePath
  34. }));
  35. return ignores;
  36. }, gitIgnore());
  37. };
  38. const ensureAbsolutePathForCwd = (cwd, p) => {
  39. if (path.isAbsolute(p)) {
  40. if (p.startsWith(cwd)) {
  41. return p;
  42. }
  43. throw new Error(`Path ${p} is not in cwd ${cwd}`);
  44. }
  45. return path.join(cwd, p);
  46. };
  47. const getIsIgnoredPredecate = (ignores, cwd) => {
  48. return p => ignores.ignores(slash(path.relative(cwd, ensureAbsolutePathForCwd(cwd, p))));
  49. };
  50. const getFile = async (file, cwd) => {
  51. const filePath = path.join(cwd, file);
  52. const content = await readFileP(filePath, 'utf8');
  53. return {
  54. cwd,
  55. filePath,
  56. content
  57. };
  58. };
  59. const getFileSync = (file, cwd) => {
  60. const filePath = path.join(cwd, file);
  61. const content = fs.readFileSync(filePath, 'utf8');
  62. return {
  63. cwd,
  64. filePath,
  65. content
  66. };
  67. };
  68. const normalizeOptions = ({
  69. ignore = [],
  70. cwd = slash(process.cwd())
  71. } = {}) => {
  72. return {ignore, cwd};
  73. };
  74. module.exports = async options => {
  75. options = normalizeOptions(options);
  76. const paths = await fastGlob('**/.gitignore', {
  77. ignore: DEFAULT_IGNORE.concat(options.ignore),
  78. cwd: options.cwd
  79. });
  80. const files = await Promise.all(paths.map(file => getFile(file, options.cwd)));
  81. const ignores = reduceIgnore(files);
  82. return getIsIgnoredPredecate(ignores, options.cwd);
  83. };
  84. module.exports.sync = options => {
  85. options = normalizeOptions(options);
  86. const paths = fastGlob.sync('**/.gitignore', {
  87. ignore: DEFAULT_IGNORE.concat(options.ignore),
  88. cwd: options.cwd
  89. });
  90. const files = paths.map(file => getFileSync(file, options.cwd));
  91. const ignores = reduceIgnore(files);
  92. return getIsIgnoredPredecate(ignores, options.cwd);
  93. };