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