Skip to content

Commit 8497149

Browse files
committed
Add file system utils
1 parent ef72948 commit 8497149

File tree

1 file changed

+81
-0
lines changed

1 file changed

+81
-0
lines changed

lib/src/utils.ts

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
import fs from "node:fs/promises";
2+
import path from "node:path";
3+
import { NormalizedConfig } from "./normalizer";
4+
5+
/**
6+
* Checks whether the given file contains Git merge conflict markers.
7+
*
8+
* @param filePath - Absolute path to the file.
9+
* @returns `true` if conflict markers exist, otherwise `false`.
10+
*/
11+
const hasConflict = async (filePath: string): Promise<boolean> => {
12+
try {
13+
const content = await fs.readFile(filePath, "utf8");
14+
return (
15+
content.includes("<<<<<<<") && content.includes("=======") && content.includes(">>>>>>>")
16+
);
17+
} catch {
18+
// If file cannot be read (permissions, etc.), treat as non-conflicted
19+
return false;
20+
}
21+
};
22+
23+
export interface CollectFilesOptions {
24+
/** Root directory to start traversal (defaults to `process.cwd()`). */
25+
root?: string;
26+
/**
27+
* Include files that match `fileFilter` even if they do not contain
28+
* merge conflicts. Defaults to `false`.
29+
*/
30+
includeNonConflicted?: boolean;
31+
}
32+
33+
/**
34+
* Recursively collects files that match the provided `fileFilter`.
35+
*
36+
* - By default, only conflicted files are returned.
37+
* - If `includeNonConflicted` is enabled, matching files are always included
38+
* (skipping conflict check).
39+
*
40+
* @param config - Normalized configuration containing `fileFilter`.
41+
* @param options - Behavior flags (e.g., `includeNonConflicted`).
42+
* @returns A promise that resolves with an array of matching file paths.
43+
*/
44+
export const collectFiles = async (
45+
config: NormalizedConfig,
46+
options: CollectFilesOptions = {},
47+
): Promise<string[]> => {
48+
const { root = process.cwd(), includeNonConflicted = false } = options;
49+
const allFiles: string[] = [];
50+
51+
/**
52+
* Recursively traverses a directory, checking each file against
53+
* the filter and (if enabled) conflict conditions.
54+
*
55+
* @param dir - Directory path to walk through.
56+
*/
57+
const walk = async (dir: string) => {
58+
const entries = await fs.readdir(dir, { withFileTypes: true });
59+
60+
for (const entry of entries) {
61+
const fullPath = path.join(dir, entry.name);
62+
63+
if (entry.isDirectory()) {
64+
await walk(fullPath);
65+
} else if (config.fileFilter(fullPath)) {
66+
if (includeNonConflicted) {
67+
allFiles.push(fullPath);
68+
} else {
69+
if (await hasConflict(fullPath)) {
70+
allFiles.push(fullPath);
71+
} else {
72+
console.info(`Skipped (no conflicts): ${fullPath}`);
73+
}
74+
}
75+
}
76+
}
77+
};
78+
79+
await walk(root);
80+
return allFiles;
81+
};

0 commit comments

Comments
 (0)