@j0hanz/filesystem-context-mcp 1.2.2 → 1.2.4
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/__tests__/lib/file-operations.test.js +0 -12
- package/dist/__tests__/lib/file-operations.test.js.map +1 -1
- package/dist/lib/file-operations/read-multiple-files.d.ts.map +1 -1
- package/dist/lib/file-operations/read-multiple-files.js +0 -13
- package/dist/lib/file-operations/read-multiple-files.js.map +1 -1
- package/dist/lib/file-operations/search/context-manager.d.ts +12 -0
- package/dist/lib/file-operations/search/context-manager.d.ts.map +1 -0
- package/dist/lib/file-operations/search/context-manager.js +50 -0
- package/dist/lib/file-operations/search/context-manager.js.map +1 -0
- package/dist/lib/file-operations/search/engine.d.ts +28 -0
- package/dist/lib/file-operations/search/engine.d.ts.map +1 -0
- package/dist/lib/file-operations/search/engine.js +180 -0
- package/dist/lib/file-operations/search/engine.js.map +1 -0
- package/dist/lib/file-operations/search/file-processor.d.ts +16 -0
- package/dist/lib/file-operations/search/file-processor.d.ts.map +1 -0
- package/dist/lib/file-operations/search/file-processor.js +121 -0
- package/dist/lib/file-operations/search/file-processor.js.map +1 -0
- package/dist/lib/file-operations/search/match-strategy.d.ts +26 -0
- package/dist/lib/file-operations/search/match-strategy.d.ts.map +1 -0
- package/dist/lib/file-operations/search/match-strategy.js +128 -0
- package/dist/lib/file-operations/search/match-strategy.js.map +1 -0
- package/dist/lib/file-operations/search/types.d.ts +34 -0
- package/dist/lib/file-operations/search/types.d.ts.map +1 -0
- package/dist/lib/file-operations/search/types.js +2 -0
- package/dist/lib/file-operations/search/types.js.map +1 -0
- package/dist/lib/file-operations/search-content.d.ts.map +1 -1
- package/dist/lib/file-operations/search-content.js +3 -431
- package/dist/lib/file-operations/search-content.js.map +1 -1
- package/dist/lib/fs-helpers/concurrency.d.ts.map +1 -1
- package/dist/lib/fs-helpers/concurrency.js +18 -9
- package/dist/lib/fs-helpers/concurrency.js.map +1 -1
- package/dist/lib/fs-helpers/readers/read-file.d.ts.map +1 -1
- package/dist/lib/fs-helpers/readers/read-file.js +1 -1
- package/dist/lib/fs-helpers/readers/read-file.js.map +1 -1
- package/dist/server.d.ts.map +1 -1
- package/dist/server.js +7 -4
- package/dist/server.js.map +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
export interface MatchStrategy {
|
|
2
|
+
countMatches(line: string): number;
|
|
3
|
+
isValid(): boolean;
|
|
4
|
+
}
|
|
5
|
+
export declare class LiteralMatchStrategy implements MatchStrategy {
|
|
6
|
+
private readonly needle;
|
|
7
|
+
private readonly haystackTransform;
|
|
8
|
+
constructor(pattern: string, caseSensitive: boolean);
|
|
9
|
+
countMatches(line: string): number;
|
|
10
|
+
isValid(): boolean;
|
|
11
|
+
}
|
|
12
|
+
export declare class RegexMatchStrategy implements MatchStrategy {
|
|
13
|
+
private readonly regex;
|
|
14
|
+
private readonly timeoutMs;
|
|
15
|
+
constructor(regex: RegExp, timeoutMs?: number);
|
|
16
|
+
countMatches(line: string): number;
|
|
17
|
+
isValid(): boolean;
|
|
18
|
+
private shouldCheckTimeout;
|
|
19
|
+
}
|
|
20
|
+
export declare function createMatchStrategy(pattern: string, options: {
|
|
21
|
+
isLiteral: boolean;
|
|
22
|
+
wholeWord: boolean;
|
|
23
|
+
caseSensitive: boolean;
|
|
24
|
+
basePath: string;
|
|
25
|
+
}): MatchStrategy;
|
|
26
|
+
//# sourceMappingURL=match-strategy.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"match-strategy.d.ts","sourceRoot":"","sources":["../../../../src/lib/file-operations/search/match-strategy.ts"],"names":[],"mappings":"AAKA,MAAM,WAAW,aAAa;IAC5B,YAAY,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAAC;IACnC,OAAO,IAAI,OAAO,CAAC;CACpB;AAED,qBAAa,oBAAqB,YAAW,aAAa;IACxD,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAS;IAChC,OAAO,CAAC,QAAQ,CAAC,iBAAiB,CAAwB;gBAE9C,OAAO,EAAE,MAAM,EAAE,aAAa,EAAE,OAAO;IAKnD,YAAY,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM;IAelC,OAAO,IAAI,OAAO;CAGnB;AAED,qBAAa,kBAAmB,YAAW,aAAa;IACtD,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAS;IAC/B,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAS;gBAEvB,KAAK,EAAE,MAAM,EAAE,SAAS,GAAE,MAA+B;IAKrE,YAAY,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM;IAgClC,OAAO,IAAI,OAAO;IAIlB,OAAO,CAAC,kBAAkB;CAY3B;AAED,wBAAgB,mBAAmB,CACjC,OAAO,EAAE,MAAM,EACf,OAAO,EAAE;IACP,SAAS,EAAE,OAAO,CAAC;IACnB,SAAS,EAAE,OAAO,CAAC;IACnB,aAAa,EAAE,OAAO,CAAC;IACvB,QAAQ,EAAE,MAAM,CAAC;CAClB,GACA,aAAa,CAef"}
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
import safeRegex from 'safe-regex2';
|
|
2
|
+
import { REGEX_MATCH_TIMEOUT_MS } from '../../constants.js';
|
|
3
|
+
import { ErrorCode, McpError } from '../../errors.js';
|
|
4
|
+
export class LiteralMatchStrategy {
|
|
5
|
+
needle;
|
|
6
|
+
haystackTransform;
|
|
7
|
+
constructor(pattern, caseSensitive) {
|
|
8
|
+
this.needle = caseSensitive ? pattern : pattern.toLowerCase();
|
|
9
|
+
this.haystackTransform = caseSensitive ? (s) => s : (s) => s.toLowerCase();
|
|
10
|
+
}
|
|
11
|
+
countMatches(line) {
|
|
12
|
+
if (line.length === 0 || this.needle.length === 0)
|
|
13
|
+
return 0;
|
|
14
|
+
const haystack = this.haystackTransform(line);
|
|
15
|
+
let count = 0;
|
|
16
|
+
let pos = 0;
|
|
17
|
+
while ((pos = haystack.indexOf(this.needle, pos)) !== -1) {
|
|
18
|
+
count++;
|
|
19
|
+
pos += this.needle.length;
|
|
20
|
+
}
|
|
21
|
+
return count;
|
|
22
|
+
}
|
|
23
|
+
isValid() {
|
|
24
|
+
return true;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
export class RegexMatchStrategy {
|
|
28
|
+
regex;
|
|
29
|
+
timeoutMs;
|
|
30
|
+
constructor(regex, timeoutMs = REGEX_MATCH_TIMEOUT_MS) {
|
|
31
|
+
this.regex = regex;
|
|
32
|
+
this.timeoutMs = timeoutMs;
|
|
33
|
+
}
|
|
34
|
+
countMatches(line) {
|
|
35
|
+
if (line.length === 0)
|
|
36
|
+
return 0;
|
|
37
|
+
this.regex.lastIndex = 0;
|
|
38
|
+
let count = 0;
|
|
39
|
+
const deadline = Date.now() + this.timeoutMs;
|
|
40
|
+
const maxIterations = Math.min(line.length * 2, 10000);
|
|
41
|
+
let iterations = 0;
|
|
42
|
+
let lastIndex = 0;
|
|
43
|
+
let match;
|
|
44
|
+
while ((match = this.regex.exec(line)) !== null) {
|
|
45
|
+
count++;
|
|
46
|
+
iterations++;
|
|
47
|
+
const { lastIndex: currentIndex } = this.regex;
|
|
48
|
+
if (match[0] === '') {
|
|
49
|
+
this.regex.lastIndex++;
|
|
50
|
+
}
|
|
51
|
+
if (currentIndex === lastIndex) {
|
|
52
|
+
return -1; // Infinite loop protection
|
|
53
|
+
}
|
|
54
|
+
({ lastIndex } = this.regex);
|
|
55
|
+
if (this.shouldCheckTimeout(count, iterations, deadline, maxIterations)) {
|
|
56
|
+
return -1;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
return count;
|
|
60
|
+
}
|
|
61
|
+
isValid() {
|
|
62
|
+
return true;
|
|
63
|
+
}
|
|
64
|
+
shouldCheckTimeout(count, iterations, deadline, maxIterations) {
|
|
65
|
+
const shouldCheck = (count > 0 && count % 10 === 0) ||
|
|
66
|
+
(iterations > 0 && iterations % 50 === 0);
|
|
67
|
+
return (shouldCheck && Date.now() > deadline) || iterations > maxIterations;
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
export function createMatchStrategy(pattern, options) {
|
|
71
|
+
const { isLiteral, wholeWord, caseSensitive, basePath } = options;
|
|
72
|
+
if (isLiteral && !wholeWord) {
|
|
73
|
+
return new LiteralMatchStrategy(pattern, caseSensitive);
|
|
74
|
+
}
|
|
75
|
+
// For wholeWord or regex, we use RegexStrategy
|
|
76
|
+
const finalPattern = preparePattern(pattern, isLiteral, wholeWord);
|
|
77
|
+
const needsReDoSCheck = !isLiteral && !isSimpleSafePattern(finalPattern);
|
|
78
|
+
ensureSafePattern(finalPattern, pattern, basePath, needsReDoSCheck);
|
|
79
|
+
const regex = compileRegex(finalPattern, caseSensitive, basePath);
|
|
80
|
+
return new RegexMatchStrategy(regex);
|
|
81
|
+
}
|
|
82
|
+
function preparePattern(pattern, isLiteral, wholeWord) {
|
|
83
|
+
let finalPattern = pattern;
|
|
84
|
+
if (isLiteral) {
|
|
85
|
+
finalPattern = finalPattern.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
86
|
+
}
|
|
87
|
+
if (wholeWord) {
|
|
88
|
+
finalPattern = `\\b${finalPattern}\\b`;
|
|
89
|
+
}
|
|
90
|
+
return finalPattern;
|
|
91
|
+
}
|
|
92
|
+
function isSimpleSafePattern(pattern) {
|
|
93
|
+
if (typeof pattern !== 'string' || pattern.length === 0) {
|
|
94
|
+
return false;
|
|
95
|
+
}
|
|
96
|
+
const nestedQuantifierPattern = /[+*?}]\s*\)\s*[+*?{]/;
|
|
97
|
+
if (nestedQuantifierPattern.test(pattern)) {
|
|
98
|
+
return false;
|
|
99
|
+
}
|
|
100
|
+
const highRepetitionPattern = /\{(\d+)(?:,\d*)?\}/g;
|
|
101
|
+
let match;
|
|
102
|
+
while ((match = highRepetitionPattern.exec(pattern)) !== null) {
|
|
103
|
+
const countStr = match[1];
|
|
104
|
+
if (countStr === undefined)
|
|
105
|
+
continue;
|
|
106
|
+
const count = parseInt(countStr, 10);
|
|
107
|
+
if (Number.isNaN(count) || count >= 25) {
|
|
108
|
+
return false;
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
return true;
|
|
112
|
+
}
|
|
113
|
+
function ensureSafePattern(finalPattern, originalPattern, basePath, needsReDoSCheck) {
|
|
114
|
+
if (!needsReDoSCheck || safeRegex(finalPattern))
|
|
115
|
+
return;
|
|
116
|
+
throw new McpError(ErrorCode.E_INVALID_PATTERN, `Potentially unsafe regular expression (ReDoS risk): ${originalPattern}. ` +
|
|
117
|
+
'Avoid patterns with nested quantifiers, overlapping alternations, or exponential backtracking.', basePath, { reason: 'ReDoS risk detected' });
|
|
118
|
+
}
|
|
119
|
+
function compileRegex(pattern, caseSensitive, basePath) {
|
|
120
|
+
try {
|
|
121
|
+
return new RegExp(pattern, caseSensitive ? 'g' : 'gi');
|
|
122
|
+
}
|
|
123
|
+
catch (error) {
|
|
124
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
125
|
+
throw new McpError(ErrorCode.E_INVALID_PATTERN, `Invalid regular expression: ${pattern} (${message})`, basePath, { searchPattern: pattern });
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
//# sourceMappingURL=match-strategy.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"match-strategy.js","sourceRoot":"","sources":["../../../../src/lib/file-operations/search/match-strategy.ts"],"names":[],"mappings":"AAAA,OAAO,SAAS,MAAM,aAAa,CAAC;AAEpC,OAAO,EAAE,sBAAsB,EAAE,MAAM,oBAAoB,CAAC;AAC5D,OAAO,EAAE,SAAS,EAAE,QAAQ,EAAE,MAAM,iBAAiB,CAAC;AAOtD,MAAM,OAAO,oBAAoB;IACd,MAAM,CAAS;IACf,iBAAiB,CAAwB;IAE1D,YAAY,OAAe,EAAE,aAAsB;QACjD,IAAI,CAAC,MAAM,GAAG,aAAa,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,OAAO,CAAC,WAAW,EAAE,CAAC;QAC9D,IAAI,CAAC,iBAAiB,GAAG,aAAa,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,WAAW,EAAE,CAAC;IAC7E,CAAC;IAED,YAAY,CAAC,IAAY;QACvB,IAAI,IAAI,CAAC,MAAM,KAAK,CAAC,IAAI,IAAI,CAAC,MAAM,CAAC,MAAM,KAAK,CAAC;YAAE,OAAO,CAAC,CAAC;QAE5D,MAAM,QAAQ,GAAG,IAAI,CAAC,iBAAiB,CAAC,IAAI,CAAC,CAAC;QAC9C,IAAI,KAAK,GAAG,CAAC,CAAC;QACd,IAAI,GAAG,GAAG,CAAC,CAAC;QAEZ,OAAO,CAAC,GAAG,GAAG,QAAQ,CAAC,OAAO,CAAC,IAAI,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC;YACzD,KAAK,EAAE,CAAC;YACR,GAAG,IAAI,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC;QAC5B,CAAC;QAED,OAAO,KAAK,CAAC;IACf,CAAC;IAED,OAAO;QACL,OAAO,IAAI,CAAC;IACd,CAAC;CACF;AAED,MAAM,OAAO,kBAAkB;IACZ,KAAK,CAAS;IACd,SAAS,CAAS;IAEnC,YAAY,KAAa,EAAE,YAAoB,sBAAsB;QACnE,IAAI,CAAC,KAAK,GAAG,KAAK,CAAC;QACnB,IAAI,CAAC,SAAS,GAAG,SAAS,CAAC;IAC7B,CAAC;IAED,YAAY,CAAC,IAAY;QACvB,IAAI,IAAI,CAAC,MAAM,KAAK,CAAC;YAAE,OAAO,CAAC,CAAC;QAEhC,IAAI,CAAC,KAAK,CAAC,SAAS,GAAG,CAAC,CAAC;QACzB,IAAI,KAAK,GAAG,CAAC,CAAC;QACd,MAAM,QAAQ,GAAG,IAAI,CAAC,GAAG,EAAE,GAAG,IAAI,CAAC,SAAS,CAAC;QAC7C,MAAM,aAAa,GAAG,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,MAAM,GAAG,CAAC,EAAE,KAAK,CAAC,CAAC;QACvD,IAAI,UAAU,GAAG,CAAC,CAAC;QACnB,IAAI,SAAS,GAAG,CAAC,CAAC;QAElB,IAAI,KAA6B,CAAC;QAClC,OAAO,CAAC,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,KAAK,IAAI,EAAE,CAAC;YAChD,KAAK,EAAE,CAAC;YACR,UAAU,EAAE,CAAC;YAEb,MAAM,EAAE,SAAS,EAAE,YAAY,EAAE,GAAG,IAAI,CAAC,KAAK,CAAC;YAC/C,IAAI,KAAK,CAAC,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC;gBACpB,IAAI,CAAC,KAAK,CAAC,SAAS,EAAE,CAAC;YACzB,CAAC;YACD,IAAI,YAAY,KAAK,SAAS,EAAE,CAAC;gBAC/B,OAAO,CAAC,CAAC,CAAC,CAAC,2BAA2B;YACxC,CAAC;YACD,CAAC,EAAE,SAAS,EAAE,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC;YAE7B,IAAI,IAAI,CAAC,kBAAkB,CAAC,KAAK,EAAE,UAAU,EAAE,QAAQ,EAAE,aAAa,CAAC,EAAE,CAAC;gBACxE,OAAO,CAAC,CAAC,CAAC;YACZ,CAAC;QACH,CAAC;QAED,OAAO,KAAK,CAAC;IACf,CAAC;IAED,OAAO;QACL,OAAO,IAAI,CAAC;IACd,CAAC;IAEO,kBAAkB,CACxB,KAAa,EACb,UAAkB,EAClB,QAAgB,EAChB,aAAqB;QAErB,MAAM,WAAW,GACf,CAAC,KAAK,GAAG,CAAC,IAAI,KAAK,GAAG,EAAE,KAAK,CAAC,CAAC;YAC/B,CAAC,UAAU,GAAG,CAAC,IAAI,UAAU,GAAG,EAAE,KAAK,CAAC,CAAC,CAAC;QAE5C,OAAO,CAAC,WAAW,IAAI,IAAI,CAAC,GAAG,EAAE,GAAG,QAAQ,CAAC,IAAI,UAAU,GAAG,aAAa,CAAC;IAC9E,CAAC;CACF;AAED,MAAM,UAAU,mBAAmB,CACjC,OAAe,EACf,OAKC;IAED,MAAM,EAAE,SAAS,EAAE,SAAS,EAAE,aAAa,EAAE,QAAQ,EAAE,GAAG,OAAO,CAAC;IAElE,IAAI,SAAS,IAAI,CAAC,SAAS,EAAE,CAAC;QAC5B,OAAO,IAAI,oBAAoB,CAAC,OAAO,EAAE,aAAa,CAAC,CAAC;IAC1D,CAAC;IAED,+CAA+C;IAC/C,MAAM,YAAY,GAAG,cAAc,CAAC,OAAO,EAAE,SAAS,EAAE,SAAS,CAAC,CAAC;IACnE,MAAM,eAAe,GAAG,CAAC,SAAS,IAAI,CAAC,mBAAmB,CAAC,YAAY,CAAC,CAAC;IAEzE,iBAAiB,CAAC,YAAY,EAAE,OAAO,EAAE,QAAQ,EAAE,eAAe,CAAC,CAAC;IAEpE,MAAM,KAAK,GAAG,YAAY,CAAC,YAAY,EAAE,aAAa,EAAE,QAAQ,CAAC,CAAC;IAClE,OAAO,IAAI,kBAAkB,CAAC,KAAK,CAAC,CAAC;AACvC,CAAC;AAED,SAAS,cAAc,CACrB,OAAe,EACf,SAAkB,EAClB,SAAkB;IAElB,IAAI,YAAY,GAAG,OAAO,CAAC;IAE3B,IAAI,SAAS,EAAE,CAAC;QACd,YAAY,GAAG,YAAY,CAAC,OAAO,CAAC,qBAAqB,EAAE,MAAM,CAAC,CAAC;IACrE,CAAC;IAED,IAAI,SAAS,EAAE,CAAC;QACd,YAAY,GAAG,MAAM,YAAY,KAAK,CAAC;IACzC,CAAC;IAED,OAAO,YAAY,CAAC;AACtB,CAAC;AAED,SAAS,mBAAmB,CAAC,OAAe;IAC1C,IAAI,OAAO,OAAO,KAAK,QAAQ,IAAI,OAAO,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACxD,OAAO,KAAK,CAAC;IACf,CAAC;IAED,MAAM,uBAAuB,GAAG,sBAAsB,CAAC;IACvD,IAAI,uBAAuB,CAAC,IAAI,CAAC,OAAO,CAAC,EAAE,CAAC;QAC1C,OAAO,KAAK,CAAC;IACf,CAAC;IAED,MAAM,qBAAqB,GAAG,qBAAqB,CAAC;IACpD,IAAI,KAAK,CAAC;IACV,OAAO,CAAC,KAAK,GAAG,qBAAqB,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC,KAAK,IAAI,EAAE,CAAC;QAC9D,MAAM,QAAQ,GAAG,KAAK,CAAC,CAAC,CAAC,CAAC;QAC1B,IAAI,QAAQ,KAAK,SAAS;YAAE,SAAS;QAErC,MAAM,KAAK,GAAG,QAAQ,CAAC,QAAQ,EAAE,EAAE,CAAC,CAAC;QACrC,IAAI,MAAM,CAAC,KAAK,CAAC,KAAK,CAAC,IAAI,KAAK,IAAI,EAAE,EAAE,CAAC;YACvC,OAAO,KAAK,CAAC;QACf,CAAC;IACH,CAAC;IAED,OAAO,IAAI,CAAC;AACd,CAAC;AAED,SAAS,iBAAiB,CACxB,YAAoB,EACpB,eAAuB,EACvB,QAAgB,EAChB,eAAwB;IAExB,IAAI,CAAC,eAAe,IAAI,SAAS,CAAC,YAAY,CAAC;QAAE,OAAO;IAExD,MAAM,IAAI,QAAQ,CAChB,SAAS,CAAC,iBAAiB,EAC3B,uDAAuD,eAAe,IAAI;QACxE,gGAAgG,EAClG,QAAQ,EACR,EAAE,MAAM,EAAE,qBAAqB,EAAE,CAClC,CAAC;AACJ,CAAC;AAED,SAAS,YAAY,CACnB,OAAe,EACf,aAAsB,EACtB,QAAgB;IAEhB,IAAI,CAAC;QACH,OAAO,IAAI,MAAM,CAAC,OAAO,EAAE,aAAa,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC;IACzD,CAAC;IAAC,OAAO,KAAc,EAAE,CAAC;QACxB,MAAM,OAAO,GAAG,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;QACvE,MAAM,IAAI,QAAQ,CAChB,SAAS,CAAC,iBAAiB,EAC3B,+BAA+B,OAAO,KAAK,OAAO,GAAG,EACrD,QAAQ,EACR,EAAE,aAAa,EAAE,OAAO,EAAE,CAC3B,CAAC;IACJ,CAAC;AACH,CAAC"}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import type { ContentMatch, SearchContentResult } from '../../../config/types.js';
|
|
2
|
+
export interface SearchOptions {
|
|
3
|
+
maxResults: number;
|
|
4
|
+
contextLines: number;
|
|
5
|
+
deadlineMs?: number;
|
|
6
|
+
currentMatchCount: number;
|
|
7
|
+
isLiteral?: boolean;
|
|
8
|
+
searchString?: string;
|
|
9
|
+
caseSensitive?: boolean;
|
|
10
|
+
wholeWord?: boolean;
|
|
11
|
+
maxFileSize: number;
|
|
12
|
+
skipBinary: boolean;
|
|
13
|
+
searchPattern: string;
|
|
14
|
+
}
|
|
15
|
+
export interface ScanResult {
|
|
16
|
+
matches: ContentMatch[];
|
|
17
|
+
linesSkippedDueToRegexTimeout: number;
|
|
18
|
+
fileHadMatches: boolean;
|
|
19
|
+
skippedTooLarge: boolean;
|
|
20
|
+
skippedBinary: boolean;
|
|
21
|
+
scanned: boolean;
|
|
22
|
+
}
|
|
23
|
+
export interface SearchState {
|
|
24
|
+
matches: ContentMatch[];
|
|
25
|
+
filesScanned: number;
|
|
26
|
+
filesMatched: number;
|
|
27
|
+
skippedTooLarge: number;
|
|
28
|
+
skippedBinary: number;
|
|
29
|
+
skippedInaccessible: number;
|
|
30
|
+
linesSkippedDueToRegexTimeout: number;
|
|
31
|
+
truncated: boolean;
|
|
32
|
+
stoppedReason: SearchContentResult['summary']['stoppedReason'];
|
|
33
|
+
}
|
|
34
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../../src/lib/file-operations/search/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,YAAY,EACZ,mBAAmB,EACpB,MAAM,0BAA0B,CAAC;AAElC,MAAM,WAAW,aAAa;IAC5B,UAAU,EAAE,MAAM,CAAC;IACnB,YAAY,EAAE,MAAM,CAAC;IACrB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,iBAAiB,EAAE,MAAM,CAAC;IAC1B,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,aAAa,CAAC,EAAE,OAAO,CAAC;IACxB,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,WAAW,EAAE,MAAM,CAAC;IACpB,UAAU,EAAE,OAAO,CAAC;IACpB,aAAa,EAAE,MAAM,CAAC;CACvB;AAED,MAAM,WAAW,UAAU;IACzB,OAAO,EAAE,YAAY,EAAE,CAAC;IACxB,6BAA6B,EAAE,MAAM,CAAC;IACtC,cAAc,EAAE,OAAO,CAAC;IACxB,eAAe,EAAE,OAAO,CAAC;IACzB,aAAa,EAAE,OAAO,CAAC;IACvB,OAAO,EAAE,OAAO,CAAC;CAClB;AAED,MAAM,WAAW,WAAW;IAC1B,OAAO,EAAE,YAAY,EAAE,CAAC;IACxB,YAAY,EAAE,MAAM,CAAC;IACrB,YAAY,EAAE,MAAM,CAAC;IACrB,eAAe,EAAE,MAAM,CAAC;IACxB,aAAa,EAAE,MAAM,CAAC;IACtB,mBAAmB,EAAE,MAAM,CAAC;IAC5B,6BAA6B,EAAE,MAAM,CAAC;IACtC,SAAS,EAAE,OAAO,CAAC;IACnB,aAAa,EAAE,mBAAmB,CAAC,SAAS,CAAC,CAAC,eAAe,CAAC,CAAC;CAChE"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../../../../src/lib/file-operations/search/types.ts"],"names":[],"mappings":""}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"search-content.d.ts","sourceRoot":"","sources":["../../../src/lib/file-operations/search-content.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"search-content.d.ts","sourceRoot":"","sources":["../../../src/lib/file-operations/search-content.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,uBAAuB,CAAC;AAGjE,wBAAsB,aAAa,CACjC,QAAQ,EAAE,MAAM,EAChB,aAAa,EAAE,MAAM,EACrB,OAAO,GAAE;IACP,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,eAAe,CAAC,EAAE,MAAM,EAAE,CAAC;IAC3B,aAAa,CAAC,EAAE,OAAO,CAAC;IACxB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,aAAa,CAAC,EAAE,OAAO,CAAC;IACxB,aAAa,CAAC,EAAE,OAAO,CAAC;IACxB,sBAAsB,CAAC,EAAE,OAAO,CAAC;CAC7B,GACL,OAAO,CAAC,mBAAmB,CAAC,CAG9B"}
|
|
@@ -1,434 +1,6 @@
|
|
|
1
|
-
import
|
|
2
|
-
import * as readline from 'node:readline';
|
|
3
|
-
import { createReadStream } from 'node:fs';
|
|
4
|
-
import fg from 'fast-glob';
|
|
5
|
-
import safeRegex from 'safe-regex2';
|
|
6
|
-
import { DEFAULT_MAX_RESULTS, DEFAULT_SEARCH_MAX_FILES, DEFAULT_SEARCH_TIMEOUT_MS, MAX_LINE_CONTENT_LENGTH, MAX_SEARCHABLE_FILE_SIZE, REGEX_MATCH_TIMEOUT_MS, } from '../constants.js';
|
|
7
|
-
import { ErrorCode, McpError } from '../errors.js';
|
|
8
|
-
import { isProbablyBinary, safeDestroy } from '../fs-helpers.js';
|
|
9
|
-
import { validateExistingPath, validateExistingPathDetailed, } from '../path-validation.js';
|
|
10
|
-
import { validateGlobPatternOrThrow } from './pattern-validator.js';
|
|
11
|
-
function initSearchContentState() {
|
|
12
|
-
return {
|
|
13
|
-
matches: [],
|
|
14
|
-
filesScanned: 0,
|
|
15
|
-
filesMatched: 0,
|
|
16
|
-
skippedTooLarge: 0,
|
|
17
|
-
skippedBinary: 0,
|
|
18
|
-
skippedInaccessible: 0,
|
|
19
|
-
linesSkippedDueToRegexTimeout: 0,
|
|
20
|
-
truncated: false,
|
|
21
|
-
stoppedReason: undefined,
|
|
22
|
-
};
|
|
23
|
-
}
|
|
24
|
-
function getSearchStopReason(deadlineMs, maxFilesScanned, maxResults, filesScanned, matchCount) {
|
|
25
|
-
if (deadlineMs !== undefined && Date.now() > deadlineMs)
|
|
26
|
-
return 'timeout';
|
|
27
|
-
if (maxFilesScanned !== undefined && filesScanned >= maxFilesScanned) {
|
|
28
|
-
return 'maxFiles';
|
|
29
|
-
}
|
|
30
|
-
if (matchCount >= maxResults)
|
|
31
|
-
return 'maxResults';
|
|
32
|
-
return undefined;
|
|
33
|
-
}
|
|
34
|
-
function applySearchStop(state, reason) {
|
|
35
|
-
state.truncated = true;
|
|
36
|
-
state.stoppedReason = reason;
|
|
37
|
-
}
|
|
38
|
-
function getStopReasonIfAny(state, options) {
|
|
39
|
-
return getSearchStopReason(options.deadlineMs, options.maxFilesScanned, options.maxResults, state.filesScanned, state.matches.length);
|
|
40
|
-
}
|
|
41
|
-
function applyStopIfNeeded(state, reason) {
|
|
42
|
-
if (!reason)
|
|
43
|
-
return false;
|
|
44
|
-
applySearchStop(state, reason);
|
|
45
|
-
return true;
|
|
46
|
-
}
|
|
47
|
-
function isSimpleSafePattern(pattern) {
|
|
48
|
-
if (typeof pattern !== 'string' || pattern.length === 0) {
|
|
49
|
-
return false;
|
|
50
|
-
}
|
|
51
|
-
const nestedQuantifierPattern = /[+*?}]\s*\)\s*[+*?{]/;
|
|
52
|
-
if (nestedQuantifierPattern.test(pattern)) {
|
|
53
|
-
return false;
|
|
54
|
-
}
|
|
55
|
-
const highRepetitionPattern = /\{(\d+)(?:,\d*)?\}/g;
|
|
56
|
-
let match;
|
|
57
|
-
while ((match = highRepetitionPattern.exec(pattern)) !== null) {
|
|
58
|
-
const countStr = match[1];
|
|
59
|
-
if (countStr === undefined)
|
|
60
|
-
continue;
|
|
61
|
-
const count = parseInt(countStr, 10);
|
|
62
|
-
if (Number.isNaN(count) || count >= 25) {
|
|
63
|
-
return false;
|
|
64
|
-
}
|
|
65
|
-
}
|
|
66
|
-
return true;
|
|
67
|
-
}
|
|
68
|
-
function prepareSearchPattern(searchPattern, options) {
|
|
69
|
-
let finalPattern = searchPattern;
|
|
70
|
-
if (options.isLiteral) {
|
|
71
|
-
finalPattern = finalPattern.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
72
|
-
}
|
|
73
|
-
if (options.wholeWord) {
|
|
74
|
-
finalPattern = `\\b${finalPattern}\\b`;
|
|
75
|
-
}
|
|
76
|
-
return finalPattern;
|
|
77
|
-
}
|
|
78
|
-
function ensureSafePattern(finalPattern, searchPattern, basePath, needsReDoSCheck) {
|
|
79
|
-
if (!needsReDoSCheck || safeRegex(finalPattern))
|
|
80
|
-
return;
|
|
81
|
-
throw new McpError(ErrorCode.E_INVALID_PATTERN, `Potentially unsafe regular expression (ReDoS risk): ${searchPattern}. ` +
|
|
82
|
-
'Avoid patterns with nested quantifiers, overlapping alternations, or exponential backtracking.', basePath, { reason: 'ReDoS risk detected' });
|
|
83
|
-
}
|
|
84
|
-
function compileRegex(finalPattern, caseSensitive, basePath) {
|
|
85
|
-
try {
|
|
86
|
-
return new RegExp(finalPattern, caseSensitive ? 'g' : 'gi');
|
|
87
|
-
}
|
|
88
|
-
catch (error) {
|
|
89
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
90
|
-
throw new McpError(ErrorCode.E_INVALID_PATTERN, `Invalid regular expression: ${finalPattern} (${message})`, basePath, { searchPattern: finalPattern });
|
|
91
|
-
}
|
|
92
|
-
}
|
|
93
|
-
function buildSearchRegex(searchPattern, options) {
|
|
94
|
-
const { isLiteral, wholeWord, caseSensitive, basePath } = options;
|
|
95
|
-
const finalPattern = prepareSearchPattern(searchPattern, {
|
|
96
|
-
isLiteral,
|
|
97
|
-
wholeWord,
|
|
98
|
-
});
|
|
99
|
-
const needsReDoSCheck = !isLiteral && !isSimpleSafePattern(finalPattern);
|
|
100
|
-
ensureSafePattern(finalPattern, searchPattern, basePath, needsReDoSCheck);
|
|
101
|
-
return {
|
|
102
|
-
regex: compileRegex(finalPattern, caseSensitive, basePath),
|
|
103
|
-
finalPattern,
|
|
104
|
-
};
|
|
105
|
-
}
|
|
106
|
-
function countLiteralMatches(line, searchString, caseSensitive) {
|
|
107
|
-
if (line.length === 0 || searchString.length === 0)
|
|
108
|
-
return 0;
|
|
109
|
-
const haystack = caseSensitive ? line : line.toLowerCase();
|
|
110
|
-
const needle = caseSensitive ? searchString : searchString.toLowerCase();
|
|
111
|
-
let count = 0;
|
|
112
|
-
let pos = 0;
|
|
113
|
-
while ((pos = haystack.indexOf(needle, pos)) !== -1) {
|
|
114
|
-
count++;
|
|
115
|
-
pos += needle.length;
|
|
116
|
-
}
|
|
117
|
-
return count;
|
|
118
|
-
}
|
|
119
|
-
function countRegexMatches(line, regex, timeoutMs = REGEX_MATCH_TIMEOUT_MS) {
|
|
120
|
-
if (line.length === 0)
|
|
121
|
-
return 0;
|
|
122
|
-
regex.lastIndex = 0;
|
|
123
|
-
let count = 0;
|
|
124
|
-
const deadline = Date.now() + timeoutMs;
|
|
125
|
-
const maxIterations = Math.min(line.length * 2, 10000);
|
|
126
|
-
let iterations = 0;
|
|
127
|
-
let lastIndex = 0;
|
|
128
|
-
let match;
|
|
129
|
-
while ((match = regex.exec(line)) !== null) {
|
|
130
|
-
count++;
|
|
131
|
-
iterations++;
|
|
132
|
-
const { lastIndex: currentIndex } = regex;
|
|
133
|
-
if (match[0] === '') {
|
|
134
|
-
regex.lastIndex++;
|
|
135
|
-
}
|
|
136
|
-
if (currentIndex === lastIndex) {
|
|
137
|
-
return -1;
|
|
138
|
-
}
|
|
139
|
-
({ lastIndex } = regex);
|
|
140
|
-
const shouldCheckTimeout = (count > 0 && count % 10 === 0) ||
|
|
141
|
-
(iterations > 0 && iterations % 50 === 0);
|
|
142
|
-
if ((shouldCheckTimeout && Date.now() > deadline) ||
|
|
143
|
-
iterations > maxIterations) {
|
|
144
|
-
return -1;
|
|
145
|
-
}
|
|
146
|
-
}
|
|
147
|
-
return count;
|
|
148
|
-
}
|
|
149
|
-
function scanFileForContent(filePath, regex, options) {
|
|
150
|
-
const state = {
|
|
151
|
-
matches: [],
|
|
152
|
-
linesSkippedDueToRegexTimeout: 0,
|
|
153
|
-
fileHadMatches: false,
|
|
154
|
-
lineNumber: 0,
|
|
155
|
-
contextBuffer: [],
|
|
156
|
-
pendingMatches: [],
|
|
157
|
-
};
|
|
158
|
-
const trimAndClampLine = (line) => line.trimEnd().substring(0, MAX_LINE_CONTENT_LENGTH);
|
|
159
|
-
const getMatchCount = (line) => {
|
|
160
|
-
if (options.isLiteral && options.searchString && !options.wholeWord) {
|
|
161
|
-
return countLiteralMatches(line, options.searchString, options.caseSensitive ?? false);
|
|
162
|
-
}
|
|
163
|
-
return countRegexMatches(line, regex);
|
|
164
|
-
};
|
|
165
|
-
const updatePending = (trimmedLine) => {
|
|
166
|
-
for (const pending of state.pendingMatches) {
|
|
167
|
-
if (pending.afterNeeded > 0) {
|
|
168
|
-
pending.match.contextAfter ??= [];
|
|
169
|
-
pending.match.contextAfter.push(trimmedLine);
|
|
170
|
-
pending.afterNeeded--;
|
|
171
|
-
}
|
|
172
|
-
}
|
|
173
|
-
while (state.pendingMatches[0]?.afterNeeded === 0) {
|
|
174
|
-
state.pendingMatches.shift();
|
|
175
|
-
}
|
|
176
|
-
};
|
|
177
|
-
const pushContextLine = (trimmedLine) => {
|
|
178
|
-
if (options.contextLines <= 0)
|
|
179
|
-
return;
|
|
180
|
-
state.contextBuffer.push(trimmedLine);
|
|
181
|
-
if (state.contextBuffer.length > options.contextLines) {
|
|
182
|
-
state.contextBuffer.shift();
|
|
183
|
-
}
|
|
184
|
-
};
|
|
185
|
-
const buildMatch = (line, content, matchCount) => {
|
|
186
|
-
const match = {
|
|
187
|
-
file: filePath,
|
|
188
|
-
line,
|
|
189
|
-
content,
|
|
190
|
-
matchCount,
|
|
191
|
-
};
|
|
192
|
-
if (state.contextBuffer.length > 0) {
|
|
193
|
-
match.contextBefore = [...state.contextBuffer];
|
|
194
|
-
}
|
|
195
|
-
return match;
|
|
196
|
-
};
|
|
197
|
-
const queueAfter = (match) => {
|
|
198
|
-
if (options.contextLines <= 0)
|
|
199
|
-
return;
|
|
200
|
-
state.pendingMatches.push({ match, afterNeeded: options.contextLines });
|
|
201
|
-
};
|
|
202
|
-
const handleLine = (line) => {
|
|
203
|
-
state.lineNumber++;
|
|
204
|
-
if (options.deadlineMs !== undefined && Date.now() > options.deadlineMs) {
|
|
205
|
-
return true;
|
|
206
|
-
}
|
|
207
|
-
if (state.matches.length + options.currentMatchCount >=
|
|
208
|
-
options.maxResults) {
|
|
209
|
-
return true;
|
|
210
|
-
}
|
|
211
|
-
const trimmed = trimAndClampLine(line);
|
|
212
|
-
updatePending(trimmed);
|
|
213
|
-
const matchCount = getMatchCount(line);
|
|
214
|
-
if (matchCount < 0) {
|
|
215
|
-
state.linesSkippedDueToRegexTimeout++;
|
|
216
|
-
pushContextLine(trimmed);
|
|
217
|
-
return false;
|
|
218
|
-
}
|
|
219
|
-
if (matchCount > 0) {
|
|
220
|
-
state.fileHadMatches = true;
|
|
221
|
-
const m = buildMatch(state.lineNumber, trimmed, matchCount);
|
|
222
|
-
state.matches.push(m);
|
|
223
|
-
queueAfter(m);
|
|
224
|
-
}
|
|
225
|
-
pushContextLine(trimmed);
|
|
226
|
-
return false;
|
|
227
|
-
};
|
|
228
|
-
const { rl, stream } = (() => {
|
|
229
|
-
const fileStream = options.fileHandle?.createReadStream({
|
|
230
|
-
encoding: 'utf-8',
|
|
231
|
-
autoClose: false,
|
|
232
|
-
}) ?? createReadStream(filePath, { encoding: 'utf-8' });
|
|
233
|
-
const reader = readline.createInterface({
|
|
234
|
-
input: fileStream,
|
|
235
|
-
crlfDelay: Infinity,
|
|
236
|
-
});
|
|
237
|
-
return { rl: reader, stream: fileStream };
|
|
238
|
-
})();
|
|
239
|
-
return (async () => {
|
|
240
|
-
try {
|
|
241
|
-
for await (const line of rl) {
|
|
242
|
-
if (handleLine(line))
|
|
243
|
-
break;
|
|
244
|
-
}
|
|
245
|
-
}
|
|
246
|
-
finally {
|
|
247
|
-
rl.close();
|
|
248
|
-
stream.destroy();
|
|
249
|
-
}
|
|
250
|
-
return {
|
|
251
|
-
matches: state.matches,
|
|
252
|
-
linesSkippedDueToRegexTimeout: state.linesSkippedDueToRegexTimeout,
|
|
253
|
-
fileHadMatches: state.fileHadMatches,
|
|
254
|
-
};
|
|
255
|
-
})();
|
|
256
|
-
}
|
|
257
|
-
async function resolveSearchPath(rawPath) {
|
|
258
|
-
try {
|
|
259
|
-
const validatedPath = await validateExistingPathDetailed(rawPath);
|
|
260
|
-
return {
|
|
261
|
-
openPath: validatedPath.resolvedPath,
|
|
262
|
-
displayPath: validatedPath.requestedPath,
|
|
263
|
-
};
|
|
264
|
-
}
|
|
265
|
-
catch {
|
|
266
|
-
return null;
|
|
267
|
-
}
|
|
268
|
-
}
|
|
269
|
-
function mapMatchesToDisplayPath(matches, displayPath) {
|
|
270
|
-
return matches.map((match) => ({ ...match, file: displayPath }));
|
|
271
|
-
}
|
|
272
|
-
function buildSkipResult(scanned, skippedTooLarge, skippedBinary) {
|
|
273
|
-
return {
|
|
274
|
-
matches: [],
|
|
275
|
-
fileHadMatches: false,
|
|
276
|
-
linesSkippedDueToRegexTimeout: 0,
|
|
277
|
-
skippedTooLarge,
|
|
278
|
-
skippedBinary,
|
|
279
|
-
scanned,
|
|
280
|
-
};
|
|
281
|
-
}
|
|
282
|
-
async function scanWithHandle(handle, openPath, displayPath, regex, options) {
|
|
283
|
-
const stats = await handle.stat();
|
|
284
|
-
if (stats.size > options.maxFileSize) {
|
|
285
|
-
return buildSkipResult(true, true, false);
|
|
286
|
-
}
|
|
287
|
-
if (options.skipBinary) {
|
|
288
|
-
const binary = await isProbablyBinary(openPath, handle);
|
|
289
|
-
if (binary) {
|
|
290
|
-
return buildSkipResult(true, false, true);
|
|
291
|
-
}
|
|
292
|
-
}
|
|
293
|
-
const scanResult = await scanFileForContent(openPath, regex, {
|
|
294
|
-
maxResults: options.maxResults,
|
|
295
|
-
contextLines: options.contextLines,
|
|
296
|
-
deadlineMs: options.deadlineMs,
|
|
297
|
-
currentMatchCount: options.currentMatchCount,
|
|
298
|
-
isLiteral: options.isLiteral,
|
|
299
|
-
searchString: options.isLiteral ? options.searchPattern : undefined,
|
|
300
|
-
caseSensitive: options.caseSensitive,
|
|
301
|
-
wholeWord: options.wholeWord,
|
|
302
|
-
fileHandle: handle,
|
|
303
|
-
});
|
|
304
|
-
const mappedMatches = mapMatchesToDisplayPath(scanResult.matches, displayPath);
|
|
305
|
-
return {
|
|
306
|
-
matches: mappedMatches,
|
|
307
|
-
fileHadMatches: scanResult.fileHadMatches,
|
|
308
|
-
linesSkippedDueToRegexTimeout: scanResult.linesSkippedDueToRegexTimeout,
|
|
309
|
-
skippedTooLarge: false,
|
|
310
|
-
skippedBinary: false,
|
|
311
|
-
scanned: true,
|
|
312
|
-
};
|
|
313
|
-
}
|
|
314
|
-
async function scanCandidateFile(openPath, displayPath, regex, options) {
|
|
315
|
-
const handle = await fs.open(openPath, 'r');
|
|
316
|
-
try {
|
|
317
|
-
return await scanWithHandle(handle, openPath, displayPath, regex, options);
|
|
318
|
-
}
|
|
319
|
-
finally {
|
|
320
|
-
await handle.close().catch(() => { });
|
|
321
|
-
}
|
|
322
|
-
}
|
|
323
|
-
function updateStateFromScan(state, scanResult) {
|
|
324
|
-
if (scanResult.scanned)
|
|
325
|
-
state.filesScanned++;
|
|
326
|
-
if (scanResult.skippedTooLarge) {
|
|
327
|
-
state.skippedTooLarge++;
|
|
328
|
-
return;
|
|
329
|
-
}
|
|
330
|
-
if (scanResult.skippedBinary) {
|
|
331
|
-
state.skippedBinary++;
|
|
332
|
-
return;
|
|
333
|
-
}
|
|
334
|
-
state.matches.push(...scanResult.matches);
|
|
335
|
-
state.linesSkippedDueToRegexTimeout +=
|
|
336
|
-
scanResult.linesSkippedDueToRegexTimeout;
|
|
337
|
-
if (scanResult.fileHadMatches)
|
|
338
|
-
state.filesMatched++;
|
|
339
|
-
}
|
|
340
|
-
function createSearchStream(basePath, filePattern, excludePatterns, includeHidden, baseNameMatch = false, caseSensitiveFileMatch = true) {
|
|
341
|
-
return fg.stream(filePattern, {
|
|
342
|
-
cwd: basePath,
|
|
343
|
-
absolute: true,
|
|
344
|
-
onlyFiles: true,
|
|
345
|
-
dot: includeHidden,
|
|
346
|
-
ignore: excludePatterns,
|
|
347
|
-
suppressErrors: true,
|
|
348
|
-
followSymbolicLinks: false,
|
|
349
|
-
baseNameMatch,
|
|
350
|
-
caseSensitiveMatch: caseSensitiveFileMatch,
|
|
351
|
-
});
|
|
352
|
-
}
|
|
353
|
-
async function scanSearchStream(stream, state, options, regex) {
|
|
354
|
-
for await (const entry of stream) {
|
|
355
|
-
const rawPath = typeof entry === 'string' ? entry : String(entry);
|
|
356
|
-
const resolved = await resolveSearchPath(rawPath);
|
|
357
|
-
if (!resolved) {
|
|
358
|
-
state.skippedInaccessible++;
|
|
359
|
-
continue;
|
|
360
|
-
}
|
|
361
|
-
if (applyStopIfNeeded(state, getStopReasonIfAny(state, options)))
|
|
362
|
-
break;
|
|
363
|
-
try {
|
|
364
|
-
const scanResult = await scanCandidateFile(resolved.openPath, resolved.displayPath, regex, {
|
|
365
|
-
maxResults: options.maxResults,
|
|
366
|
-
currentMatchCount: state.matches.length,
|
|
367
|
-
maxFileSize: options.maxFileSize,
|
|
368
|
-
skipBinary: options.skipBinary,
|
|
369
|
-
isLiteral: options.isLiteral,
|
|
370
|
-
searchPattern: options.searchPattern,
|
|
371
|
-
caseSensitive: options.caseSensitive,
|
|
372
|
-
contextLines: options.contextLines,
|
|
373
|
-
wholeWord: options.wholeWord,
|
|
374
|
-
deadlineMs: options.deadlineMs,
|
|
375
|
-
});
|
|
376
|
-
updateStateFromScan(state, scanResult);
|
|
377
|
-
}
|
|
378
|
-
catch {
|
|
379
|
-
state.skippedInaccessible++;
|
|
380
|
-
}
|
|
381
|
-
if (applyStopIfNeeded(state, getStopReasonIfAny(state, options)))
|
|
382
|
-
break;
|
|
383
|
-
}
|
|
384
|
-
}
|
|
1
|
+
import { SearchEngine } from './search/engine.js';
|
|
385
2
|
export async function searchContent(basePath, searchPattern, options = {}) {
|
|
386
|
-
const
|
|
387
|
-
|
|
388
|
-
// Validate file pattern
|
|
389
|
-
validateGlobPatternOrThrow(filePattern, validPath);
|
|
390
|
-
const deadlineMs = timeoutMs ? Date.now() + timeoutMs : undefined;
|
|
391
|
-
const { regex } = buildSearchRegex(searchPattern, {
|
|
392
|
-
isLiteral,
|
|
393
|
-
wholeWord,
|
|
394
|
-
caseSensitive,
|
|
395
|
-
basePath,
|
|
396
|
-
});
|
|
397
|
-
const state = initSearchContentState();
|
|
398
|
-
const stream = createSearchStream(validPath, filePattern, excludePatterns, includeHidden, baseNameMatch ?? false, caseSensitiveFileMatch ?? true);
|
|
399
|
-
try {
|
|
400
|
-
await scanSearchStream(stream, state, {
|
|
401
|
-
deadlineMs,
|
|
402
|
-
maxFilesScanned,
|
|
403
|
-
maxResults,
|
|
404
|
-
maxFileSize,
|
|
405
|
-
skipBinary,
|
|
406
|
-
isLiteral,
|
|
407
|
-
searchPattern,
|
|
408
|
-
caseSensitive,
|
|
409
|
-
contextLines,
|
|
410
|
-
wholeWord,
|
|
411
|
-
}, regex);
|
|
412
|
-
}
|
|
413
|
-
finally {
|
|
414
|
-
safeDestroy(stream);
|
|
415
|
-
}
|
|
416
|
-
return {
|
|
417
|
-
basePath: validPath,
|
|
418
|
-
pattern: searchPattern,
|
|
419
|
-
filePattern,
|
|
420
|
-
matches: state.matches,
|
|
421
|
-
summary: {
|
|
422
|
-
filesScanned: state.filesScanned,
|
|
423
|
-
filesMatched: state.filesMatched,
|
|
424
|
-
matches: state.matches.length,
|
|
425
|
-
truncated: state.truncated,
|
|
426
|
-
skippedTooLarge: state.skippedTooLarge,
|
|
427
|
-
skippedBinary: state.skippedBinary,
|
|
428
|
-
skippedInaccessible: state.skippedInaccessible,
|
|
429
|
-
linesSkippedDueToRegexTimeout: state.linesSkippedDueToRegexTimeout,
|
|
430
|
-
stoppedReason: state.stoppedReason,
|
|
431
|
-
},
|
|
432
|
-
};
|
|
3
|
+
const engine = new SearchEngine(basePath, options);
|
|
4
|
+
return engine.search(searchPattern);
|
|
433
5
|
}
|
|
434
6
|
//# sourceMappingURL=search-content.js.map
|