gitignore.js 2.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120
  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. const ignores = gitIgnore();
  31. for (const file of files) {
  32. ignores.add(parseGitIgnore(file.content, {
  33. cwd: file.cwd,
  34. fileName: file.filePath
  35. }));
  36. }
  37. return ignores;
  38. };
  39. const ensureAbsolutePathForCwd = (cwd, p) => {
  40. cwd = slash(cwd);
  41. if (path.isAbsolute(p)) {
  42. if (slash(p).startsWith(cwd)) {
  43. return p;
  44. }
  45. throw new Error(`Path ${p} is not in cwd ${cwd}`);
  46. }
  47. return path.join(cwd, p);
  48. };
  49. const getIsIgnoredPredecate = (ignores, cwd) => {
  50. return p => ignores.ignores(slash(path.relative(cwd, ensureAbsolutePathForCwd(cwd, p.path || p))));
  51. };
  52. const getFile = async (file, cwd) => {
  53. const filePath = path.join(cwd, file);
  54. const content = await readFileP(filePath, 'utf8');
  55. return {
  56. cwd,
  57. filePath,
  58. content
  59. };
  60. };
  61. const getFileSync = (file, cwd) => {
  62. const filePath = path.join(cwd, file);
  63. const content = fs.readFileSync(filePath, 'utf8');
  64. return {
  65. cwd,
  66. filePath,
  67. content
  68. };
  69. };
  70. const normalizeOptions = ({
  71. ignore = [],
  72. cwd = slash(process.cwd())
  73. } = {}) => {
  74. return {ignore, cwd};
  75. };
  76. module.exports = async options => {
  77. options = normalizeOptions(options);
  78. const paths = await fastGlob('**/.gitignore', {
  79. ignore: DEFAULT_IGNORE.concat(options.ignore),
  80. cwd: options.cwd
  81. });
  82. const files = await Promise.all(paths.map(file => getFile(file, options.cwd)));
  83. const ignores = reduceIgnore(files);
  84. return getIsIgnoredPredecate(ignores, options.cwd);
  85. };
  86. module.exports.sync = options => {
  87. options = normalizeOptions(options);
  88. const paths = fastGlob.sync('**/.gitignore', {
  89. ignore: DEFAULT_IGNORE.concat(options.ignore),
  90. cwd: options.cwd
  91. });
  92. const files = paths.map(file => getFileSync(file, options.cwd));
  93. const ignores = reduceIgnore(files);
  94. return getIsIgnoredPredecate(ignores, options.cwd);
  95. };