@mcp-monorepo/file-browser 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +26 -0
- package/dist/index.js.map +1 -0
- package/dist/lib/env.d.ts +10 -0
- package/dist/lib/env.d.ts.map +1 -0
- package/dist/lib/env.js +20 -0
- package/dist/lib/env.js.map +1 -0
- package/dist/lib/find-best-patch-match.d.ts +31 -0
- package/dist/lib/find-best-patch-match.d.ts.map +1 -0
- package/dist/lib/find-best-patch-match.js +74 -0
- package/dist/lib/find-best-patch-match.js.map +1 -0
- package/dist/lib/finder.d.ts +46 -0
- package/dist/lib/finder.d.ts.map +1 -0
- package/dist/lib/finder.js +90 -0
- package/dist/lib/finder.js.map +1 -0
- package/dist/lib/validators.d.ts +12 -0
- package/dist/lib/validators.d.ts.map +1 -0
- package/dist/lib/validators.js +57 -0
- package/dist/lib/validators.js.map +1 -0
- package/dist/lib/walker/better-walker.d.ts +52 -0
- package/dist/lib/walker/better-walker.d.ts.map +1 -0
- package/dist/lib/walker/better-walker.js +120 -0
- package/dist/lib/walker/better-walker.js.map +1 -0
- package/dist/lib/walker/ignore-file-service.d.ts +8 -0
- package/dist/lib/walker/ignore-file-service.d.ts.map +1 -0
- package/dist/lib/walker/ignore-file-service.js +128 -0
- package/dist/lib/walker/ignore-file-service.js.map +1 -0
- package/dist/tools/find-or-replace.d.ts +3 -0
- package/dist/tools/find-or-replace.d.ts.map +1 -0
- package/dist/tools/find-or-replace.js +102 -0
- package/dist/tools/find-or-replace.js.map +1 -0
- package/dist/tools/list-directory.d.ts +3 -0
- package/dist/tools/list-directory.d.ts.map +1 -0
- package/dist/tools/list-directory.js +75 -0
- package/dist/tools/list-directory.js.map +1 -0
- package/dist/tools/mk-dir.d.ts +3 -0
- package/dist/tools/mk-dir.d.ts.map +1 -0
- package/dist/tools/mk-dir.js +45 -0
- package/dist/tools/mk-dir.js.map +1 -0
- package/dist/tools/move-path.d.ts +3 -0
- package/dist/tools/move-path.d.ts.map +1 -0
- package/dist/tools/move-path.js +35 -0
- package/dist/tools/move-path.js.map +1 -0
- package/dist/tools/open-file.d.ts +3 -0
- package/dist/tools/open-file.d.ts.map +1 -0
- package/dist/tools/open-file.js +31 -0
- package/dist/tools/open-file.js.map +1 -0
- package/dist/tools/patch-file.d.ts +3 -0
- package/dist/tools/patch-file.d.ts.map +1 -0
- package/dist/tools/patch-file.js +80 -0
- package/dist/tools/patch-file.js.map +1 -0
- package/dist/tools/remove-file.d.ts +3 -0
- package/dist/tools/remove-file.d.ts.map +1 -0
- package/dist/tools/remove-file.js +52 -0
- package/dist/tools/remove-file.js.map +1 -0
- package/dist/tools/tree-directory.d.ts +3 -0
- package/dist/tools/tree-directory.d.ts.map +1 -0
- package/dist/tools/tree-directory.js +71 -0
- package/dist/tools/tree-directory.js.map +1 -0
- package/dist/tools/write-tool.d.ts +3 -0
- package/dist/tools/write-tool.d.ts.map +1 -0
- package/dist/tools/write-tool.js +45 -0
- package/dist/tools/write-tool.js.map +1 -0
- package/package.json +39 -0
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":""}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { createMcpServer } from '@mcp-monorepo/shared';
|
|
3
|
+
import { registerFindOrReplaceTool } from './tools/find-or-replace.js';
|
|
4
|
+
import { registerListDirectoryTool } from './tools/list-directory.js';
|
|
5
|
+
import { registerMkDirTool } from './tools/mk-dir.js';
|
|
6
|
+
import { registerMovePathTool } from './tools/move-path.js';
|
|
7
|
+
import { registerOpenFileTool } from './tools/open-file.js';
|
|
8
|
+
import { registerPatchFileTool } from './tools/patch-file.js';
|
|
9
|
+
import { registerRemoveFileTool } from './tools/remove-file.js';
|
|
10
|
+
import { registerTreeDirectoryTool } from './tools/tree-directory.js';
|
|
11
|
+
createMcpServer({
|
|
12
|
+
name: 'file-browser',
|
|
13
|
+
importMetaPath: import.meta.filename,
|
|
14
|
+
title: 'File Browser MCP Server',
|
|
15
|
+
tools: [
|
|
16
|
+
registerOpenFileTool,
|
|
17
|
+
registerMovePathTool,
|
|
18
|
+
registerListDirectoryTool,
|
|
19
|
+
registerTreeDirectoryTool,
|
|
20
|
+
registerMkDirTool,
|
|
21
|
+
registerRemoveFileTool,
|
|
22
|
+
registerFindOrReplaceTool,
|
|
23
|
+
registerPatchFileTool,
|
|
24
|
+
],
|
|
25
|
+
});
|
|
26
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AAEA,OAAO,EAAE,eAAe,EAAE,MAAM,sBAAsB,CAAA;AAEtD,OAAO,EAAE,yBAAyB,EAAE,MAAM,4BAA4B,CAAA;AACtE,OAAO,EAAE,yBAAyB,EAAE,MAAM,2BAA2B,CAAA;AACrE,OAAO,EAAE,iBAAiB,EAAE,MAAM,mBAAmB,CAAA;AACrD,OAAO,EAAE,oBAAoB,EAAE,MAAM,sBAAsB,CAAA;AAC3D,OAAO,EAAE,oBAAoB,EAAE,MAAM,sBAAsB,CAAA;AAC3D,OAAO,EAAE,qBAAqB,EAAE,MAAM,uBAAuB,CAAA;AAC7D,OAAO,EAAE,sBAAsB,EAAE,MAAM,wBAAwB,CAAA;AAC/D,OAAO,EAAE,yBAAyB,EAAE,MAAM,2BAA2B,CAAA;AAErE,eAAe,CAAC;IACd,IAAI,EAAE,cAAc;IACpB,cAAc,EAAE,MAAM,CAAC,IAAI,CAAC,QAAQ;IACpC,KAAK,EAAE,yBAAyB;IAChC,KAAK,EAAE;QACL,oBAAoB;QACpB,oBAAoB;QACpB,yBAAyB;QACzB,yBAAyB;QACzB,iBAAiB;QACjB,sBAAsB;QACtB,yBAAyB;QACzB,qBAAqB;KACtB;CACF,CAAC,CAAA"}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Retrieves and validates the WORKING_DIR from environment variables.
|
|
3
|
+
* Ensures the path is an absolute and normalized path.
|
|
4
|
+
* Throws an error if the path is invalid, missing, or not absolute.
|
|
5
|
+
*
|
|
6
|
+
* @returns {string} - Normalized absolute path
|
|
7
|
+
* @throws {Error} - If WORKING_DIR is invalid
|
|
8
|
+
*/
|
|
9
|
+
export declare function getWorkingDir(): string;
|
|
10
|
+
//# sourceMappingURL=env.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"env.d.ts","sourceRoot":"","sources":["../../src/lib/env.ts"],"names":[],"mappings":"AAEA;;;;;;;GAOG;AACH,wBAAgB,aAAa,WAY5B"}
|
package/dist/lib/env.js
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { isAbsolute, normalize } from 'node:path';
|
|
2
|
+
/**
|
|
3
|
+
* Retrieves and validates the WORKING_DIR from environment variables.
|
|
4
|
+
* Ensures the path is an absolute and normalized path.
|
|
5
|
+
* Throws an error if the path is invalid, missing, or not absolute.
|
|
6
|
+
*
|
|
7
|
+
* @returns {string} - Normalized absolute path
|
|
8
|
+
* @throws {Error} - If WORKING_DIR is invalid
|
|
9
|
+
*/
|
|
10
|
+
export function getWorkingDir() {
|
|
11
|
+
const workingDir = process.env.WORKING_DIR;
|
|
12
|
+
if (!workingDir || workingDir.trim() === '') {
|
|
13
|
+
throw new Error('WORKING_DIR environment variable is not set or empty.');
|
|
14
|
+
}
|
|
15
|
+
if (!isAbsolute(workingDir)) {
|
|
16
|
+
throw new Error('WORKING_DIR must be an absolute path.');
|
|
17
|
+
}
|
|
18
|
+
return normalize(workingDir);
|
|
19
|
+
}
|
|
20
|
+
//# sourceMappingURL=env.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"env.js","sourceRoot":"","sources":["../../src/lib/env.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,SAAS,EAAE,MAAM,WAAW,CAAA;AAEjD;;;;;;;GAOG;AACH,MAAM,UAAU,aAAa;IAC3B,MAAM,UAAU,GAAG,OAAO,CAAC,GAAG,CAAC,WAAW,CAAA;IAE1C,IAAI,CAAC,UAAU,IAAI,UAAU,CAAC,IAAI,EAAE,KAAK,EAAE,EAAE,CAAC;QAC5C,MAAM,IAAI,KAAK,CAAC,uDAAuD,CAAC,CAAA;IAC1E,CAAC;IAED,IAAI,CAAC,UAAU,CAAC,UAAU,CAAC,EAAE,CAAC;QAC5B,MAAM,IAAI,KAAK,CAAC,uCAAuC,CAAC,CAAA;IAC1D,CAAC;IAED,OAAO,SAAS,CAAC,UAAU,CAAC,CAAA;AAC9B,CAAC"}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
type PatchMatchResult = {
|
|
2
|
+
maxOverlap: number;
|
|
3
|
+
bestMatchPos: number;
|
|
4
|
+
};
|
|
5
|
+
type PatchPositionResult = {
|
|
6
|
+
patchStartPos: PatchMatchResult | undefined;
|
|
7
|
+
patchEndPos: PatchMatchResult | undefined;
|
|
8
|
+
};
|
|
9
|
+
/**
|
|
10
|
+
* Finds the longest match for a patch substring using binary search.
|
|
11
|
+
*
|
|
12
|
+
* @param fileContent - The content of the file to search within.
|
|
13
|
+
* @param patch - The patch substring to search for.
|
|
14
|
+
* @param approxLine - The approximate line number to center the search around.
|
|
15
|
+
* @param isStart - Whether to search for the start or end of the patch.
|
|
16
|
+
* @param radius - The number of lines to search around the approximate line.
|
|
17
|
+
* @returns The position of the longest match, or undefined if not found.
|
|
18
|
+
*/
|
|
19
|
+
export declare function findLongestMatch(fileContent: string, patch: string, approxLine: number, isStart: boolean, radius: number): PatchMatchResult | undefined;
|
|
20
|
+
/**
|
|
21
|
+
* Finds the start and end positions of a patch in the file content.
|
|
22
|
+
*
|
|
23
|
+
* @param fileContent - The content of the file to search within.
|
|
24
|
+
* @param patch - The patch substring to locate.
|
|
25
|
+
* @param approxStartLine - The approximate line number where the patch starts.
|
|
26
|
+
* @param approxEndLine - The approximate line number where the patch ends.
|
|
27
|
+
* @returns The start and end positions of the patch in the file content.
|
|
28
|
+
*/
|
|
29
|
+
export declare function findPatchPositions(fileContent: string, patch: string, approxStartLine: number, approxEndLine: number): PatchPositionResult;
|
|
30
|
+
export {};
|
|
31
|
+
//# sourceMappingURL=find-best-patch-match.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"find-best-patch-match.d.ts","sourceRoot":"","sources":["../../src/lib/find-best-patch-match.ts"],"names":[],"mappings":"AAMA,KAAK,gBAAgB,GAAG;IAAE,UAAU,EAAE,MAAM,CAAC;IAAC,YAAY,EAAE,MAAM,CAAA;CAAE,CAAA;AACpE,KAAK,mBAAmB,GAAG;IAAE,aAAa,EAAE,gBAAgB,GAAG,SAAS,CAAC;IAAC,WAAW,EAAE,gBAAgB,GAAG,SAAS,CAAA;CAAE,CAAA;AAErH;;;;;;;;;GASG;AACH,wBAAgB,gBAAgB,CAC9B,WAAW,EAAE,MAAM,EACnB,KAAK,EAAE,MAAM,EACb,UAAU,EAAE,MAAM,EAClB,OAAO,EAAE,OAAO,EAChB,MAAM,EAAE,MAAM,GACb,gBAAgB,GAAG,SAAS,CA8C9B;AAED;;;;;;;;GAQG;AACH,wBAAgB,kBAAkB,CAChC,WAAW,EAAE,MAAM,EACnB,KAAK,EAAE,MAAM,EACb,eAAe,EAAE,MAAM,EACvB,aAAa,EAAE,MAAM,GACpB,mBAAmB,CAUrB"}
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import { getNthLastLineBreakBeforePosition, getNthNextLineBreakAfterPosition, getPositionForLineNumber, } from './finder.js';
|
|
2
|
+
/**
|
|
3
|
+
* Finds the longest match for a patch substring using binary search.
|
|
4
|
+
*
|
|
5
|
+
* @param fileContent - The content of the file to search within.
|
|
6
|
+
* @param patch - The patch substring to search for.
|
|
7
|
+
* @param approxLine - The approximate line number to center the search around.
|
|
8
|
+
* @param isStart - Whether to search for the start or end of the patch.
|
|
9
|
+
* @param radius - The number of lines to search around the approximate line.
|
|
10
|
+
* @returns The position of the longest match, or undefined if not found.
|
|
11
|
+
*/
|
|
12
|
+
export function findLongestMatch(fileContent, patch, approxLine, isStart, radius) {
|
|
13
|
+
// Calculate the search range using line-based helper methods
|
|
14
|
+
const approxPos = getPositionForLineNumber(fileContent, approxLine);
|
|
15
|
+
if (approxPos === undefined)
|
|
16
|
+
return undefined;
|
|
17
|
+
const startSearchPos = getNthLastLineBreakBeforePosition(fileContent, approxPos, radius) ?? 0;
|
|
18
|
+
const endSearchPos = getNthNextLineBreakAfterPosition(fileContent, approxPos, radius) ?? fileContent.length;
|
|
19
|
+
const targetSegment = fileContent.slice(startSearchPos, endSearchPos);
|
|
20
|
+
// Binary search for the longest match
|
|
21
|
+
let low = 1; // Minimum overlap length
|
|
22
|
+
let isFirstStage = true;
|
|
23
|
+
let high = Math.min(10, patch.length);
|
|
24
|
+
let bestMatchPos = undefined;
|
|
25
|
+
while (low <= high) {
|
|
26
|
+
const mid = isFirstStage ? high : Math.floor((low + high) / 2);
|
|
27
|
+
const substring = isStart ? patch.slice(0, mid) : patch.slice(-mid);
|
|
28
|
+
const regex = new RegExp(substring.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'), 'g');
|
|
29
|
+
const match = targetSegment.match(regex);
|
|
30
|
+
if (match) {
|
|
31
|
+
// Match found, try for a longer overlap
|
|
32
|
+
if (isFirstStage) {
|
|
33
|
+
high *= 2;
|
|
34
|
+
if (high >= patch.length) {
|
|
35
|
+
high = patch.length;
|
|
36
|
+
isFirstStage = false;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
else {
|
|
40
|
+
low = mid + 1;
|
|
41
|
+
}
|
|
42
|
+
bestMatchPos = fileContent.indexOf(match[0], startSearchPos);
|
|
43
|
+
bestMatchPos = isStart ? bestMatchPos : bestMatchPos + match[0].length;
|
|
44
|
+
}
|
|
45
|
+
else {
|
|
46
|
+
// No match, try for a shorter overlap
|
|
47
|
+
if (isFirstStage) {
|
|
48
|
+
isFirstStage = false;
|
|
49
|
+
}
|
|
50
|
+
else {
|
|
51
|
+
high = mid - 1;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
return bestMatchPos !== undefined ? { maxOverlap: high, bestMatchPos } : undefined;
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Finds the start and end positions of a patch in the file content.
|
|
59
|
+
*
|
|
60
|
+
* @param fileContent - The content of the file to search within.
|
|
61
|
+
* @param patch - The patch substring to locate.
|
|
62
|
+
* @param approxStartLine - The approximate line number where the patch starts.
|
|
63
|
+
* @param approxEndLine - The approximate line number where the patch ends.
|
|
64
|
+
* @returns The start and end positions of the patch in the file content.
|
|
65
|
+
*/
|
|
66
|
+
export function findPatchPositions(fileContent, patch, approxStartLine, approxEndLine) {
|
|
67
|
+
const searchRadius = 10; // Number of lines to search around the approximate position
|
|
68
|
+
// Find the start position of the patch
|
|
69
|
+
const patchStartPos = findLongestMatch(fileContent, patch, approxStartLine, true, searchRadius);
|
|
70
|
+
// Find the end position of the patch
|
|
71
|
+
const patchEndPos = findLongestMatch(fileContent, patch, approxEndLine, false, searchRadius);
|
|
72
|
+
return { patchStartPos, patchEndPos };
|
|
73
|
+
}
|
|
74
|
+
//# sourceMappingURL=find-best-patch-match.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"find-best-patch-match.js","sourceRoot":"","sources":["../../src/lib/find-best-patch-match.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Finds the n-th last line break before the specified position in the input string.
|
|
3
|
+
*
|
|
4
|
+
* @param input - The input string to search within.
|
|
5
|
+
* @param position - The position in the string to search before.
|
|
6
|
+
* @param n - Number of line breaks.
|
|
7
|
+
* @returns The index of the n-th last line break before the position, or undefined if not found.
|
|
8
|
+
*/
|
|
9
|
+
export declare function getNthLastLineBreakBeforePosition(input: string, position: number, n: number): number | undefined;
|
|
10
|
+
/**
|
|
11
|
+
* Finds the n-th next line break after the specified position in the input string.
|
|
12
|
+
*
|
|
13
|
+
* @param input - The input string to search within.
|
|
14
|
+
* @param position - The position in the string to search after.
|
|
15
|
+
* @param n - Number of line breaks.
|
|
16
|
+
* @returns The index of the n-th next line break after the position, or undefined if not found.
|
|
17
|
+
*/
|
|
18
|
+
export declare function getNthNextLineBreakAfterPosition(input: string, position: number, n: number): number | undefined;
|
|
19
|
+
/**
|
|
20
|
+
* Determines the line number at the specified position in the input string.
|
|
21
|
+
*
|
|
22
|
+
* @param input - The input string to analyze.
|
|
23
|
+
* @param position - The position in the string to determine the line number for.
|
|
24
|
+
* @returns The 1-based line number at the specified position.
|
|
25
|
+
* @throws If the position is out of bounds of the input string.
|
|
26
|
+
*/
|
|
27
|
+
export declare function getLineNumberAtPosition(input: string, position: number): number;
|
|
28
|
+
/**
|
|
29
|
+
* Converts a 1-based line number to a position index in the file content.
|
|
30
|
+
*
|
|
31
|
+
* @param fileContent - The content of the file.
|
|
32
|
+
* @param lineNumber - The 1-based line number to convert.
|
|
33
|
+
* @returns The position index of the start of the specified line, or undefined if the line does not exist.
|
|
34
|
+
*/
|
|
35
|
+
export declare function getPositionForLineNumber(fileContent: string, lineNumber: number): number | undefined;
|
|
36
|
+
/**
|
|
37
|
+
* Extracts the substring from two line breaks before and after the given position,
|
|
38
|
+
* and prepends each line with line numbers, linebreaks, and "|".
|
|
39
|
+
*
|
|
40
|
+
* @param input - The input string to process.
|
|
41
|
+
* @param startPos - The start position in the string to center the extraction around.
|
|
42
|
+
* @param endPos - The end position in the string to center the extraction around.
|
|
43
|
+
* @returns The formatted substring with line numbers, linebreaks, and "|" prepended to each line.
|
|
44
|
+
*/
|
|
45
|
+
export declare function getFormattedSubstring(input: string, startPos: number, endPos: number): string;
|
|
46
|
+
//# sourceMappingURL=finder.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"finder.d.ts","sourceRoot":"","sources":["../../src/lib/finder.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AACH,wBAAgB,iCAAiC,CAAC,KAAK,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAUhH;AAED;;;;;;;GAOG;AACH,wBAAgB,gCAAgC,CAAC,KAAK,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAY/G;AAED;;;;;;;GAOG;AACH,wBAAgB,uBAAuB,CAAC,KAAK,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,MAAM,CAS/E;AAED;;;;;;GAMG;AACH,wBAAgB,wBAAwB,CAAC,WAAW,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAIpG;AAED;;;;;;;;GAQG;AACH,wBAAgB,qBAAqB,CAAC,KAAK,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,MAAM,CAiB7F"}
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Finds the n-th last line break before the specified position in the input string.
|
|
3
|
+
*
|
|
4
|
+
* @param input - The input string to search within.
|
|
5
|
+
* @param position - The position in the string to search before.
|
|
6
|
+
* @param n - Number of line breaks.
|
|
7
|
+
* @returns The index of the n-th last line break before the position, or undefined if not found.
|
|
8
|
+
*/
|
|
9
|
+
export function getNthLastLineBreakBeforePosition(input, position, n) {
|
|
10
|
+
const regex = new RegExp(`(?:\\n[^\\n]*?){${n - 1}}\\n(?=[^\\n]*$)`);
|
|
11
|
+
const substring = input.slice(0, position);
|
|
12
|
+
const match = substring.match(regex);
|
|
13
|
+
if (match) {
|
|
14
|
+
return match.index ?? undefined;
|
|
15
|
+
}
|
|
16
|
+
return undefined;
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Finds the n-th next line break after the specified position in the input string.
|
|
20
|
+
*
|
|
21
|
+
* @param input - The input string to search within.
|
|
22
|
+
* @param position - The position in the string to search after.
|
|
23
|
+
* @param n - Number of line breaks.
|
|
24
|
+
* @returns The index of the n-th next line break after the position, or undefined if not found.
|
|
25
|
+
*/
|
|
26
|
+
export function getNthNextLineBreakAfterPosition(input, position, n) {
|
|
27
|
+
const substring = input.slice(position);
|
|
28
|
+
const regex = new RegExp(`(?:[^\\n]*\\n){${n}}`);
|
|
29
|
+
const match = substring.match(regex);
|
|
30
|
+
if (match) {
|
|
31
|
+
const nthNextLineBreakIndex = match[0].length;
|
|
32
|
+
if (nthNextLineBreakIndex === 0)
|
|
33
|
+
return 0;
|
|
34
|
+
return position + nthNextLineBreakIndex - 1;
|
|
35
|
+
}
|
|
36
|
+
return undefined;
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Determines the line number at the specified position in the input string.
|
|
40
|
+
*
|
|
41
|
+
* @param input - The input string to analyze.
|
|
42
|
+
* @param position - The position in the string to determine the line number for.
|
|
43
|
+
* @returns The 1-based line number at the specified position.
|
|
44
|
+
* @throws If the position is out of bounds of the input string.
|
|
45
|
+
*/
|
|
46
|
+
export function getLineNumberAtPosition(input, position) {
|
|
47
|
+
if (position < 0 || position > input.length) {
|
|
48
|
+
throw new Error('Position is out of bounds');
|
|
49
|
+
}
|
|
50
|
+
const substring = input.slice(0, position);
|
|
51
|
+
const lineBreakCount = (substring.match(/\n/g) || []).length;
|
|
52
|
+
return lineBreakCount + 1;
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* Converts a 1-based line number to a position index in the file content.
|
|
56
|
+
*
|
|
57
|
+
* @param fileContent - The content of the file.
|
|
58
|
+
* @param lineNumber - The 1-based line number to convert.
|
|
59
|
+
* @returns The position index of the start of the specified line, or undefined if the line does not exist.
|
|
60
|
+
*/
|
|
61
|
+
export function getPositionForLineNumber(fileContent, lineNumber) {
|
|
62
|
+
const position = getNthNextLineBreakAfterPosition(fileContent, 0, lineNumber - 1);
|
|
63
|
+
const offset = lineNumber === 1 ? 0 : 1;
|
|
64
|
+
return position === undefined || fileContent.length <= position ? undefined : position + offset;
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* Extracts the substring from two line breaks before and after the given position,
|
|
68
|
+
* and prepends each line with line numbers, linebreaks, and "|".
|
|
69
|
+
*
|
|
70
|
+
* @param input - The input string to process.
|
|
71
|
+
* @param startPos - The start position in the string to center the extraction around.
|
|
72
|
+
* @param endPos - The end position in the string to center the extraction around.
|
|
73
|
+
* @returns The formatted substring with line numbers, linebreaks, and "|" prepended to each line.
|
|
74
|
+
*/
|
|
75
|
+
export function getFormattedSubstring(input, startPos, endPos) {
|
|
76
|
+
let start = getNthLastLineBreakBeforePosition(input, startPos, 2);
|
|
77
|
+
start = start === undefined ? 0 : start + 1;
|
|
78
|
+
let end = getNthNextLineBreakAfterPosition(input, endPos, 2);
|
|
79
|
+
end = end === undefined ? input.length : end;
|
|
80
|
+
const substring = input.slice(start, end);
|
|
81
|
+
const startLineNumber = getLineNumberAtPosition(input, start);
|
|
82
|
+
if (substring === '') {
|
|
83
|
+
return '';
|
|
84
|
+
}
|
|
85
|
+
return substring
|
|
86
|
+
.split('\n')
|
|
87
|
+
.map((line, index) => `${startLineNumber + index} | ${line}`)
|
|
88
|
+
.join('\n');
|
|
89
|
+
}
|
|
90
|
+
//# sourceMappingURL=finder.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"finder.js","sourceRoot":"","sources":["../../src/lib/finder.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Checks if a given absolute path (candidatePath) is a subpath of another absolute path (basePath).
|
|
3
|
+
* @param {string} basePath - The parent path to check against.
|
|
4
|
+
* @param {string} candidatePath - The path to check if it's within the basePath.
|
|
5
|
+
* @throws {Error} - If either path is not absolute or candidatePath not is within basePath
|
|
6
|
+
*/
|
|
7
|
+
export declare function validateWithinBasePath(basePath: string, candidatePath: string): void;
|
|
8
|
+
export declare function validateExists(path: string): Promise<void>;
|
|
9
|
+
export declare function validateDoesNotExists(path: string): Promise<void>;
|
|
10
|
+
export declare function validateIsFile(path: string): Promise<void>;
|
|
11
|
+
export declare function validateIsDir(path: string): Promise<void>;
|
|
12
|
+
//# sourceMappingURL=validators.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"validators.d.ts","sourceRoot":"","sources":["../../src/lib/validators.ts"],"names":[],"mappings":"AAKA;;;;;GAKG;AACH,wBAAgB,sBAAsB,CAAC,QAAQ,EAAE,MAAM,EAAE,aAAa,EAAE,MAAM,QAwB7E;AAED,wBAAsB,cAAc,CAAC,IAAI,EAAE,MAAM,iBAMhD;AAED,wBAAsB,qBAAqB,CAAC,IAAI,EAAE,MAAM,iBASvD;AAED,wBAAsB,cAAc,CAAC,IAAI,EAAE,MAAM,iBAKhD;AAED,wBAAsB,aAAa,CAAC,IAAI,EAAE,MAAM,iBAK/C"}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import { stat } from 'node:fs/promises';
|
|
2
|
+
import { isAbsolute, normalize } from 'node:path';
|
|
3
|
+
import { logger } from '@mcp-monorepo/shared';
|
|
4
|
+
/**
|
|
5
|
+
* Checks if a given absolute path (candidatePath) is a subpath of another absolute path (basePath).
|
|
6
|
+
* @param {string} basePath - The parent path to check against.
|
|
7
|
+
* @param {string} candidatePath - The path to check if it's within the basePath.
|
|
8
|
+
* @throws {Error} - If either path is not absolute or candidatePath not is within basePath
|
|
9
|
+
*/
|
|
10
|
+
export function validateWithinBasePath(basePath, candidatePath) {
|
|
11
|
+
if (!isAbsolute(basePath)) {
|
|
12
|
+
logger.error('Base path must be an absolute path. Got: ', basePath, ' instead.');
|
|
13
|
+
throw new Error('Base path must be an absolute path.');
|
|
14
|
+
}
|
|
15
|
+
if (!isAbsolute(candidatePath)) {
|
|
16
|
+
logger.error('Candidate path must be an absolute path. Got: ', candidatePath, ' instead.');
|
|
17
|
+
throw new Error('Candidate path must be an absolute path.');
|
|
18
|
+
}
|
|
19
|
+
const normalizedBase = normalize(basePath).replace(/[/\\]+$/, ''); // Remove trailing slash
|
|
20
|
+
const normalizedCandidate = normalize(candidatePath);
|
|
21
|
+
if (!normalizedCandidate.startsWith(normalizedBase)) {
|
|
22
|
+
logger.error('Candidate path must be within base path. Got: ', normalizedCandidate, ' in base path: ', normalizedBase, ' instead.');
|
|
23
|
+
throw new Error('Candidate path must be within base path.');
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
export async function validateExists(path) {
|
|
27
|
+
try {
|
|
28
|
+
await stat(path);
|
|
29
|
+
}
|
|
30
|
+
catch (_) {
|
|
31
|
+
throw new Error('The provided path does not exist.');
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
export async function validateDoesNotExists(path) {
|
|
35
|
+
try {
|
|
36
|
+
await stat(path);
|
|
37
|
+
throw new Error('The provided path does exist.');
|
|
38
|
+
}
|
|
39
|
+
catch (error) {
|
|
40
|
+
if (error && typeof error === 'object' && 'code' in error && error.code !== 'ENOENT') {
|
|
41
|
+
throw error;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
export async function validateIsFile(path) {
|
|
46
|
+
const fileStats = await stat(path);
|
|
47
|
+
if (!fileStats.isFile()) {
|
|
48
|
+
throw new Error('The provided path does not point to a valid file.');
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
export async function validateIsDir(path) {
|
|
52
|
+
const fileStats = await stat(path);
|
|
53
|
+
if (!fileStats.isDirectory()) {
|
|
54
|
+
throw new Error('The provided path does not point to a valid directory.');
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
//# sourceMappingURL=validators.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"validators.js","sourceRoot":"","sources":["../../src/lib/validators.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,MAAM,kBAAkB,CAAA;AACvC,OAAO,EAAE,UAAU,EAAE,SAAS,EAAE,MAAM,WAAW,CAAA;AAEjD,OAAO,EAAE,MAAM,EAAE,MAAM,sBAAsB,CAAA;AAE7C;;;;;GAKG;AACH,MAAM,UAAU,sBAAsB,CAAC,QAAgB,EAAE,aAAqB;IAC5E,IAAI,CAAC,UAAU,CAAC,QAAQ,CAAC,EAAE,CAAC;QAC1B,MAAM,CAAC,KAAK,CAAC,2CAA2C,EAAE,QAAQ,EAAE,WAAW,CAAC,CAAA;QAChF,MAAM,IAAI,KAAK,CAAC,qCAAqC,CAAC,CAAA;IACxD,CAAC;IAED,IAAI,CAAC,UAAU,CAAC,aAAa,CAAC,EAAE,CAAC;QAC/B,MAAM,CAAC,KAAK,CAAC,gDAAgD,EAAE,aAAa,EAAE,WAAW,CAAC,CAAA;QAC1F,MAAM,IAAI,KAAK,CAAC,0CAA0C,CAAC,CAAA;IAC7D,CAAC;IAED,MAAM,cAAc,GAAG,SAAS,CAAC,QAAQ,CAAC,CAAC,OAAO,CAAC,SAAS,EAAE,EAAE,CAAC,CAAA,CAAC,wBAAwB;IAC1F,MAAM,mBAAmB,GAAG,SAAS,CAAC,aAAa,CAAC,CAAA;IAEpD,IAAI,CAAC,mBAAmB,CAAC,UAAU,CAAC,cAAc,CAAC,EAAE,CAAC;QACpD,MAAM,CAAC,KAAK,CACV,gDAAgD,EAChD,mBAAmB,EACnB,iBAAiB,EACjB,cAAc,EACd,WAAW,CACZ,CAAA;QACD,MAAM,IAAI,KAAK,CAAC,0CAA0C,CAAC,CAAA;IAC7D,CAAC;AACH,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,cAAc,CAAC,IAAY;IAC/C,IAAI,CAAC;QACH,MAAM,IAAI,CAAC,IAAI,CAAC,CAAA;IAClB,CAAC;IAAC,OAAO,CAAC,EAAE,CAAC;QACX,MAAM,IAAI,KAAK,CAAC,mCAAmC,CAAC,CAAA;IACtD,CAAC;AACH,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,qBAAqB,CAAC,IAAY;IACtD,IAAI,CAAC;QACH,MAAM,IAAI,CAAC,IAAI,CAAC,CAAA;QAChB,MAAM,IAAI,KAAK,CAAC,+BAA+B,CAAC,CAAA;IAClD,CAAC;IAAC,OAAO,KAAc,EAAE,CAAC;QACxB,IAAI,KAAK,IAAI,OAAO,KAAK,KAAK,QAAQ,IAAI,MAAM,IAAI,KAAK,IAAI,KAAK,CAAC,IAAI,KAAK,QAAQ,EAAE,CAAC;YACrF,MAAM,KAAK,CAAA;QACb,CAAC;IACH,CAAC;AACH,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,cAAc,CAAC,IAAY;IAC/C,MAAM,SAAS,GAAG,MAAM,IAAI,CAAC,IAAI,CAAC,CAAA;IAClC,IAAI,CAAC,SAAS,CAAC,MAAM,EAAE,EAAE,CAAC;QACxB,MAAM,IAAI,KAAK,CAAC,mDAAmD,CAAC,CAAA;IACtE,CAAC;AACH,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,aAAa,CAAC,IAAY;IAC9C,MAAM,SAAS,GAAG,MAAM,IAAI,CAAC,IAAI,CAAC,CAAA;IAClC,IAAI,CAAC,SAAS,CAAC,WAAW,EAAE,EAAE,CAAC;QAC7B,MAAM,IAAI,KAAK,CAAC,wDAAwD,CAAC,CAAA;IAC3E,CAAC;AACH,CAAC"}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { type Stats } from 'fs';
|
|
2
|
+
export declare function getTypeFromStats(entry: Stats): "folder" | "file" | "block device" | "character device" | "FIFO" | "socket" | "symbolic link" | "unknown";
|
|
3
|
+
export type StatType = ReturnType<typeof getTypeFromStats>;
|
|
4
|
+
/**
|
|
5
|
+
* Options for traversing a directory using Breadth-First Search (BFS).
|
|
6
|
+
*/
|
|
7
|
+
interface TraverseOptions {
|
|
8
|
+
/** The absolute path to the directory to traverse. */
|
|
9
|
+
absoluteFolderPath: string;
|
|
10
|
+
/**
|
|
11
|
+
* An array of file names containing ignore patterns.
|
|
12
|
+
* @default ['.gitignore']
|
|
13
|
+
*/
|
|
14
|
+
ignoreFiles?: string[];
|
|
15
|
+
/**
|
|
16
|
+
* Whether to follow symbolic links.
|
|
17
|
+
* Symbolic links are only followed if this is true and the operating system is not Windows.
|
|
18
|
+
* @default false
|
|
19
|
+
*/
|
|
20
|
+
followSymLinks?: boolean;
|
|
21
|
+
/**
|
|
22
|
+
* The maximum depth to traverse.
|
|
23
|
+
* A depth of 0 means only the starting directory is processed.
|
|
24
|
+
* @default Infinity
|
|
25
|
+
*/
|
|
26
|
+
maxDepth?: number;
|
|
27
|
+
/**
|
|
28
|
+
* The maximum number of entries (files/directories) to yield.
|
|
29
|
+
* The traversal stops once this limit is reached.
|
|
30
|
+
* @default Infinity
|
|
31
|
+
*/
|
|
32
|
+
maxEntries?: number;
|
|
33
|
+
/**
|
|
34
|
+
* Whether to include empty directories in the traversal results. Dirs with content are never included.
|
|
35
|
+
* If set to `true`, empty directories will be yielded as part of the traversal.
|
|
36
|
+
* If set to `false`, empty directories will be skipped.
|
|
37
|
+
* @default false
|
|
38
|
+
*/
|
|
39
|
+
includeEmptyDir?: boolean;
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Asynchronously traverses a directory using Breadth-First Search (BFS) and yields each file/directory path.
|
|
43
|
+
*
|
|
44
|
+
* @param opts Options for the traversal, including the relative folder path, whether to follow symbolic links, max depth, and max entries.
|
|
45
|
+
* @returns An asynchronous generator that yields relative paths of files and directories.
|
|
46
|
+
*/
|
|
47
|
+
export declare function traverseDirectoryBFS(opts: TraverseOptions): AsyncGenerator<{
|
|
48
|
+
relPath: string;
|
|
49
|
+
type: StatType;
|
|
50
|
+
}>;
|
|
51
|
+
export {};
|
|
52
|
+
//# sourceMappingURL=better-walker.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"better-walker.d.ts","sourceRoot":"","sources":["../../../src/lib/walker/better-walker.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,KAAK,EAAE,MAAM,IAAI,CAAA;AAS/B,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,KAAK,6GAiB5C;AAED,MAAM,MAAM,QAAQ,GAAG,UAAU,CAAC,OAAO,gBAAgB,CAAC,CAAA;AAE1D;;GAEG;AACH,UAAU,eAAe;IACvB,sDAAsD;IACtD,kBAAkB,EAAE,MAAM,CAAA;IAC1B;;;OAGG;IACH,WAAW,CAAC,EAAE,MAAM,EAAE,CAAA;IACtB;;;;OAIG;IACH,cAAc,CAAC,EAAE,OAAO,CAAA;IACxB;;;;OAIG;IACH,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB;;;;OAIG;IACH,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB;;;;;OAKG;IACH,eAAe,CAAC,EAAE,OAAO,CAAA;CAC1B;AAED;;;;;GAKG;AACH,wBAAuB,oBAAoB,CACzC,IAAI,EAAE,eAAe,GACpB,cAAc,CAAC;IAAE,OAAO,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,QAAQ,CAAA;CAAE,CAAC,CAoGrD"}
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
import { lstat, readdir, stat } from 'node:fs/promises';
|
|
2
|
+
import { platform } from 'node:os';
|
|
3
|
+
import { join, relative } from 'node:path';
|
|
4
|
+
import { logger } from '@mcp-monorepo/shared';
|
|
5
|
+
import { IgnoreFileService } from './ignore-file-service.js';
|
|
6
|
+
export function getTypeFromStats(entry) {
|
|
7
|
+
if (entry.isDirectory()) {
|
|
8
|
+
return 'folder';
|
|
9
|
+
}
|
|
10
|
+
else if (entry.isFile()) {
|
|
11
|
+
return 'file';
|
|
12
|
+
}
|
|
13
|
+
else if (entry.isBlockDevice()) {
|
|
14
|
+
return 'block device';
|
|
15
|
+
}
|
|
16
|
+
else if (entry.isCharacterDevice()) {
|
|
17
|
+
return 'character device';
|
|
18
|
+
}
|
|
19
|
+
else if (entry.isFIFO()) {
|
|
20
|
+
return 'FIFO';
|
|
21
|
+
}
|
|
22
|
+
else if (entry.isSocket()) {
|
|
23
|
+
return 'socket';
|
|
24
|
+
}
|
|
25
|
+
else if (entry.isSymbolicLink()) {
|
|
26
|
+
return 'symbolic link';
|
|
27
|
+
}
|
|
28
|
+
return 'unknown';
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Asynchronously traverses a directory using Breadth-First Search (BFS) and yields each file/directory path.
|
|
32
|
+
*
|
|
33
|
+
* @param opts Options for the traversal, including the relative folder path, whether to follow symbolic links, max depth, and max entries.
|
|
34
|
+
* @returns An asynchronous generator that yields relative paths of files and directories.
|
|
35
|
+
*/
|
|
36
|
+
export async function* traverseDirectoryBFS(opts) {
|
|
37
|
+
const { absoluteFolderPath, ignoreFiles = ['.gitignore'], followSymLinks = false, maxDepth = Infinity, maxEntries = Infinity, includeEmptyDir = false, } = opts;
|
|
38
|
+
const isWindows = platform() === 'win32';
|
|
39
|
+
const ignoreService = new IgnoreFileService();
|
|
40
|
+
ignoreService.addByContent(absoluteFolderPath, '/.git/');
|
|
41
|
+
const baseDirStats = await lstat(absoluteFolderPath).catch(() => undefined);
|
|
42
|
+
if (!baseDirStats || !baseDirStats.isDirectory()) {
|
|
43
|
+
throw new Error('The provided path is not a directory.');
|
|
44
|
+
}
|
|
45
|
+
const queue = [
|
|
46
|
+
{
|
|
47
|
+
stats: baseDirStats,
|
|
48
|
+
currentPath: absoluteFolderPath,
|
|
49
|
+
currentDepth: 0,
|
|
50
|
+
},
|
|
51
|
+
];
|
|
52
|
+
const visited = new Set();
|
|
53
|
+
let yieldedCount = 0;
|
|
54
|
+
while (queue.length > 0 && yieldedCount < maxEntries) {
|
|
55
|
+
const shifted = queue.shift();
|
|
56
|
+
if (!shifted)
|
|
57
|
+
break;
|
|
58
|
+
let { stats } = shifted;
|
|
59
|
+
const { currentPath, currentDepth } = shifted;
|
|
60
|
+
if (currentDepth > maxDepth || visited.has(currentPath)) {
|
|
61
|
+
continue;
|
|
62
|
+
}
|
|
63
|
+
visited.add(currentPath);
|
|
64
|
+
if (stats.isSymbolicLink() && followSymLinks && !isWindows) {
|
|
65
|
+
try {
|
|
66
|
+
stats = await stat(currentPath); // Follow symlink
|
|
67
|
+
}
|
|
68
|
+
catch (error) {
|
|
69
|
+
logger.error(`Error accessing path ${currentPath}:`, error);
|
|
70
|
+
continue; // Skip broken symlinks
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
const relPath = relative(absoluteFolderPath, currentPath).replace(/\\/g, '/');
|
|
74
|
+
// Process directories
|
|
75
|
+
if (stats.isDirectory()) {
|
|
76
|
+
// ** THE FIX: Perform a two-pass scan. First, load ignore files. **
|
|
77
|
+
const dirents = await readdir(currentPath, { withFileTypes: true });
|
|
78
|
+
// Pass 1: Find and immediately process all ignore files in this directory.
|
|
79
|
+
for (const dirent of dirents) {
|
|
80
|
+
if (dirent.isFile() && ignoreFiles.includes(dirent.name)) {
|
|
81
|
+
await ignoreService.add(join(currentPath, dirent.name));
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
// Pass 2: Process children with the now-updated rules.
|
|
85
|
+
let hasYieldableChildren = false;
|
|
86
|
+
for (const dirent of dirents) {
|
|
87
|
+
const childPath = join(currentPath, dirent.name);
|
|
88
|
+
if (visited.has(childPath))
|
|
89
|
+
continue;
|
|
90
|
+
const childRelPath = join(relPath, dirent.name);
|
|
91
|
+
if (!ignoreService.isPathIgnored(childPath)) {
|
|
92
|
+
hasYieldableChildren = true;
|
|
93
|
+
}
|
|
94
|
+
// Prune directories that cannot possibly contain allowed files.
|
|
95
|
+
if (dirent.isDirectory() && !ignoreService.couldDirectoryContainAllowedFiles(childPath)) {
|
|
96
|
+
continue;
|
|
97
|
+
}
|
|
98
|
+
try {
|
|
99
|
+
const childStats = await lstat(childPath);
|
|
100
|
+
queue.push({ stats: childStats, currentPath: childPath, currentDepth: currentDepth + 1 });
|
|
101
|
+
}
|
|
102
|
+
catch (e) {
|
|
103
|
+
// ignore files that might have been deleted during traversal
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
if (relPath && includeEmptyDir && !hasYieldableChildren) {
|
|
107
|
+
yieldedCount++;
|
|
108
|
+
yield { relPath, type: 'folder' };
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
else {
|
|
112
|
+
// Process files
|
|
113
|
+
if (relPath && !ignoreService.isPathIgnored(currentPath)) {
|
|
114
|
+
yieldedCount++;
|
|
115
|
+
yield { relPath, type: getTypeFromStats(stats) };
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
//# sourceMappingURL=better-walker.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"better-walker.js","sourceRoot":"","sources":["../../../src/lib/walker/better-walker.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export declare class IgnoreFileService {
|
|
2
|
+
private rulesByDirectory;
|
|
3
|
+
add(filePath: string): Promise<void>;
|
|
4
|
+
addByContent(directoryPath: string, fileContent: string): void;
|
|
5
|
+
isPathIgnored(absolutePath: string): boolean;
|
|
6
|
+
couldDirectoryContainAllowedFiles(absolutePath: string): boolean;
|
|
7
|
+
}
|
|
8
|
+
//# sourceMappingURL=ignore-file-service.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ignore-file-service.d.ts","sourceRoot":"","sources":["../../../src/lib/walker/ignore-file-service.ts"],"names":[],"mappings":"AAsBA,qBAAa,iBAAiB;IAC5B,OAAO,CAAC,gBAAgB,CAA6B;IAE/C,GAAG,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAM1C,YAAY,CAAC,aAAa,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,GAAG,IAAI;IAwB9D,aAAa,CAAC,YAAY,EAAE,MAAM,GAAG,OAAO;IAsD5C,iCAAiC,CAAC,YAAY,EAAE,MAAM,GAAG,OAAO;CA6CjE"}
|