@j0hanz/filesystem-context-mcp 1.0.10 → 1.1.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/README.md +18 -5
- package/dist/__tests__/schemas/validators.test.js +32 -5
- package/dist/__tests__/schemas/validators.test.js.map +1 -1
- package/dist/config/types.d.ts +27 -0
- package/dist/config/types.d.ts.map +1 -1
- package/dist/index.js +0 -2
- package/dist/index.js.map +1 -1
- package/dist/lib/constants.d.ts +6 -6
- package/dist/lib/constants.d.ts.map +1 -1
- package/dist/lib/constants.js +24 -41
- package/dist/lib/constants.js.map +1 -1
- package/dist/lib/errors.d.ts +2 -0
- package/dist/lib/errors.d.ts.map +1 -1
- package/dist/lib/errors.js +13 -31
- package/dist/lib/errors.js.map +1 -1
- package/dist/lib/file-operations.d.ts.map +1 -1
- package/dist/lib/file-operations.js +124 -84
- package/dist/lib/file-operations.js.map +1 -1
- package/dist/lib/formatters.d.ts +1 -12
- package/dist/lib/formatters.d.ts.map +1 -1
- package/dist/lib/formatters.js +11 -15
- package/dist/lib/formatters.js.map +1 -1
- package/dist/lib/fs-helpers.d.ts.map +1 -1
- package/dist/lib/fs-helpers.js +25 -48
- package/dist/lib/fs-helpers.js.map +1 -1
- package/dist/lib/image-parsing.d.ts.map +1 -1
- package/dist/lib/image-parsing.js +0 -6
- package/dist/lib/image-parsing.js.map +1 -1
- package/dist/lib/path-utils.d.ts.map +1 -1
- package/dist/lib/path-utils.js +0 -2
- package/dist/lib/path-utils.js.map +1 -1
- package/dist/lib/path-validation.d.ts.map +1 -1
- package/dist/lib/path-validation.js +39 -22
- package/dist/lib/path-validation.js.map +1 -1
- package/dist/lib/search-helpers.d.ts +3 -0
- package/dist/lib/search-helpers.d.ts.map +1 -1
- package/dist/lib/search-helpers.js +29 -65
- package/dist/lib/search-helpers.js.map +1 -1
- package/dist/schemas/outputs.d.ts +14 -14
- package/dist/server.d.ts.map +1 -1
- package/dist/server.js +1 -15
- package/dist/server.js.map +1 -1
- package/dist/tools/analyze-directory.d.ts.map +1 -1
- package/dist/tools/analyze-directory.js +6 -6
- package/dist/tools/analyze-directory.js.map +1 -1
- package/dist/tools/list-allowed-dirs.d.ts.map +1 -1
- package/dist/tools/list-allowed-dirs.js.map +1 -1
- package/dist/tools/list-directory.js +5 -5
- package/dist/tools/list-directory.js.map +1 -1
- package/dist/tools/read-file.d.ts.map +1 -1
- package/dist/tools/read-file.js +4 -4
- package/dist/tools/read-file.js.map +1 -1
- package/dist/tools/read-multiple-files.d.ts.map +1 -1
- package/dist/tools/read-multiple-files.js +2 -3
- package/dist/tools/read-multiple-files.js.map +1 -1
- package/dist/tools/search-content.d.ts.map +1 -1
- package/dist/tools/search-content.js +5 -10
- package/dist/tools/search-content.js.map +1 -1
- package/dist/tools/search-files.d.ts.map +1 -1
- package/dist/tools/search-files.js +5 -5
- package/dist/tools/search-files.js.map +1 -1
- package/package.json +1 -1
package/dist/lib/errors.js
CHANGED
|
@@ -1,13 +1,10 @@
|
|
|
1
1
|
import { ErrorCode, } from '../config/types.js';
|
|
2
|
-
// Re-export ErrorCode from centralized location
|
|
3
2
|
export { ErrorCode };
|
|
4
|
-
// Type guard for Node.js ErrnoException
|
|
5
3
|
export function isNodeError(error) {
|
|
6
4
|
return (error instanceof Error &&
|
|
7
5
|
'code' in error &&
|
|
8
6
|
typeof error.code === 'string');
|
|
9
7
|
}
|
|
10
|
-
// Mapping of Node.js error codes to McpError codes
|
|
11
8
|
export const NODE_ERROR_CODE_MAP = {
|
|
12
9
|
ENOENT: ErrorCode.E_NOT_FOUND,
|
|
13
10
|
EACCES: ErrorCode.E_PERMISSION_DENIED,
|
|
@@ -24,7 +21,6 @@ export const NODE_ERROR_CODE_MAP = {
|
|
|
24
21
|
EEXIST: ErrorCode.E_INVALID_INPUT,
|
|
25
22
|
EINVAL: ErrorCode.E_INVALID_INPUT,
|
|
26
23
|
};
|
|
27
|
-
// Custom error class for MCP operations
|
|
28
24
|
export class McpError extends Error {
|
|
29
25
|
code;
|
|
30
26
|
path;
|
|
@@ -35,20 +31,16 @@ export class McpError extends Error {
|
|
|
35
31
|
this.path = path;
|
|
36
32
|
this.details = details;
|
|
37
33
|
this.name = 'McpError';
|
|
38
|
-
// Ensure proper prototype chain for instanceof checks
|
|
39
34
|
Object.setPrototypeOf(this, McpError.prototype);
|
|
40
35
|
}
|
|
41
|
-
// Create McpError from existing error
|
|
42
36
|
static fromError(code, message, originalError, path, details) {
|
|
43
37
|
const mcpError = new McpError(code, message, path, details, originalError);
|
|
44
|
-
// Preserve stack trace if available
|
|
45
38
|
if (originalError instanceof Error && originalError.stack) {
|
|
46
39
|
mcpError.stack = `${String(mcpError.stack)}\nCaused by: ${originalError.stack}`;
|
|
47
40
|
}
|
|
48
41
|
return mcpError;
|
|
49
42
|
}
|
|
50
43
|
}
|
|
51
|
-
// Error code to suggestion mapping
|
|
52
44
|
const ERROR_SUGGESTIONS = {
|
|
53
45
|
[ErrorCode.E_ACCESS_DENIED]: 'Check that the path is within an allowed directory. Use list_allowed_directories to see available paths.',
|
|
54
46
|
[ErrorCode.E_NOT_FOUND]: 'Verify the path exists. Use list_directory to explore available files and directories.',
|
|
@@ -64,31 +56,24 @@ const ERROR_SUGGESTIONS = {
|
|
|
64
56
|
[ErrorCode.E_PATH_TRAVERSAL]: 'Path traversal attempts (../) that escape allowed directories are not permitted.',
|
|
65
57
|
[ErrorCode.E_UNKNOWN]: 'An unexpected error occurred. Check the error message for details.',
|
|
66
58
|
};
|
|
67
|
-
// Classify an unknown error into a standardized ErrorCode
|
|
68
59
|
export function classifyError(error) {
|
|
69
|
-
// 1. Direct McpError classification
|
|
70
60
|
if (error instanceof McpError) {
|
|
71
61
|
return error.code;
|
|
72
62
|
}
|
|
73
|
-
// 2. Node.js ErrnoException code mapping
|
|
74
63
|
if (isNodeError(error) && error.code) {
|
|
75
64
|
const mapped = NODE_ERROR_CODE_MAP[error.code];
|
|
76
65
|
if (mapped)
|
|
77
66
|
return mapped;
|
|
78
67
|
}
|
|
79
|
-
// 3. Common case optimization: check for ENOENT in message
|
|
80
68
|
const message = error instanceof Error ? error.message : String(error);
|
|
81
69
|
if (message.toLowerCase().includes('enoent')) {
|
|
82
70
|
return validateErrorCode(ErrorCode.E_NOT_FOUND);
|
|
83
71
|
}
|
|
84
|
-
// 4. Message pattern matching (fallback for non-Node errors)
|
|
85
72
|
return classifyByMessage(error);
|
|
86
73
|
}
|
|
87
|
-
// Assertion function for exhaustive checks
|
|
88
74
|
function assertNever(value) {
|
|
89
75
|
throw new Error(`Unhandled discriminated union member: ${JSON.stringify(value)}`);
|
|
90
76
|
}
|
|
91
|
-
// Validate ErrorCode exhaustiveness at compile time
|
|
92
77
|
function validateErrorCode(code) {
|
|
93
78
|
switch (code) {
|
|
94
79
|
case ErrorCode.E_ACCESS_DENIED:
|
|
@@ -112,13 +97,11 @@ function validateErrorCode(code) {
|
|
|
112
97
|
function classifyByMessage(error) {
|
|
113
98
|
const message = error instanceof Error ? error.message : String(error);
|
|
114
99
|
const lowerMessage = message.toLowerCase();
|
|
115
|
-
// Access/security related
|
|
116
100
|
if (lowerMessage.includes('not within allowed') ||
|
|
117
101
|
lowerMessage.includes('access denied') ||
|
|
118
102
|
lowerMessage.includes('outside allowed')) {
|
|
119
103
|
return validateErrorCode(ErrorCode.E_ACCESS_DENIED);
|
|
120
104
|
}
|
|
121
|
-
// Not found (path-related patterns)
|
|
122
105
|
if ((lowerMessage.includes('path') ||
|
|
123
106
|
lowerMessage.includes('file') ||
|
|
124
107
|
lowerMessage.includes('directory')) &&
|
|
@@ -126,7 +109,6 @@ function classifyByMessage(error) {
|
|
|
126
109
|
lowerMessage.includes('does not exist'))) {
|
|
127
110
|
return validateErrorCode(ErrorCode.E_NOT_FOUND);
|
|
128
111
|
}
|
|
129
|
-
// Type mismatches
|
|
130
112
|
if (lowerMessage.includes('not a file') ||
|
|
131
113
|
lowerMessage.includes('is a directory')) {
|
|
132
114
|
return validateErrorCode(ErrorCode.E_NOT_FILE);
|
|
@@ -134,52 +116,42 @@ function classifyByMessage(error) {
|
|
|
134
116
|
if (lowerMessage.includes('not a directory')) {
|
|
135
117
|
return validateErrorCode(ErrorCode.E_NOT_DIRECTORY);
|
|
136
118
|
}
|
|
137
|
-
// Size limits
|
|
138
119
|
if (lowerMessage.includes('too large') || lowerMessage.includes('exceeds')) {
|
|
139
120
|
return validateErrorCode(ErrorCode.E_TOO_LARGE);
|
|
140
121
|
}
|
|
141
|
-
// Binary file
|
|
142
122
|
if (lowerMessage.includes('binary')) {
|
|
143
123
|
return validateErrorCode(ErrorCode.E_BINARY_FILE);
|
|
144
124
|
}
|
|
145
|
-
// Timeout
|
|
146
125
|
if (lowerMessage.includes('timeout') || lowerMessage.includes('timed out')) {
|
|
147
126
|
return validateErrorCode(ErrorCode.E_TIMEOUT);
|
|
148
127
|
}
|
|
149
|
-
// Invalid pattern
|
|
150
128
|
if (lowerMessage.includes('invalid') && lowerMessage.includes('pattern')) {
|
|
151
129
|
return validateErrorCode(ErrorCode.E_INVALID_PATTERN);
|
|
152
130
|
}
|
|
153
131
|
if (lowerMessage.includes('regex') || lowerMessage.includes('regexp')) {
|
|
154
132
|
return validateErrorCode(ErrorCode.E_INVALID_PATTERN);
|
|
155
133
|
}
|
|
156
|
-
// Invalid input
|
|
157
134
|
if (lowerMessage.includes('invalid') ||
|
|
158
135
|
lowerMessage.includes('cannot specify')) {
|
|
159
136
|
return validateErrorCode(ErrorCode.E_INVALID_INPUT);
|
|
160
137
|
}
|
|
161
|
-
// Permission (when no error code available)
|
|
162
138
|
if (lowerMessage.includes('permission denied') ||
|
|
163
139
|
lowerMessage.includes('permission')) {
|
|
164
140
|
return validateErrorCode(ErrorCode.E_PERMISSION_DENIED);
|
|
165
141
|
}
|
|
166
|
-
// Symlink
|
|
167
142
|
if (lowerMessage.includes('symlink')) {
|
|
168
143
|
return validateErrorCode(ErrorCode.E_SYMLINK_NOT_ALLOWED);
|
|
169
144
|
}
|
|
170
|
-
// Path traversal
|
|
171
145
|
if (lowerMessage.includes('traversal')) {
|
|
172
146
|
return validateErrorCode(ErrorCode.E_PATH_TRAVERSAL);
|
|
173
147
|
}
|
|
174
148
|
return validateErrorCode(ErrorCode.E_UNKNOWN);
|
|
175
149
|
}
|
|
176
|
-
// Create detailed error with suggestions
|
|
177
150
|
export function createDetailedError(error, path, additionalDetails) {
|
|
178
151
|
const message = error instanceof Error ? error.message : String(error);
|
|
179
152
|
const code = classifyError(error);
|
|
180
153
|
const suggestion = ERROR_SUGGESTIONS[code];
|
|
181
154
|
const effectivePath = path ?? (error instanceof McpError ? error.path : undefined);
|
|
182
|
-
// Merge details from McpError and additionalDetails
|
|
183
155
|
const mcpDetails = error instanceof McpError ? error.details : undefined;
|
|
184
156
|
const mergedDetails = {
|
|
185
157
|
...mcpDetails,
|
|
@@ -194,7 +166,6 @@ export function createDetailedError(error, path, additionalDetails) {
|
|
|
194
166
|
details: hasDetails ? mergedDetails : undefined,
|
|
195
167
|
};
|
|
196
168
|
}
|
|
197
|
-
// Format error for display
|
|
198
169
|
export function formatDetailedError(error) {
|
|
199
170
|
const lines = [`Error [${error.code}]: ${error.message}`];
|
|
200
171
|
if (error.path) {
|
|
@@ -208,10 +179,8 @@ export function formatDetailedError(error) {
|
|
|
208
179
|
export function getSuggestion(code) {
|
|
209
180
|
return ERROR_SUGGESTIONS[code];
|
|
210
181
|
}
|
|
211
|
-
// Create MCP-compatible error response
|
|
212
182
|
export function createErrorResponse(error, defaultCode, path) {
|
|
213
183
|
const detailed = createDetailedError(error, path);
|
|
214
|
-
// Use more specific code if classified, otherwise use default
|
|
215
184
|
const finalCode = detailed.code === ErrorCode.E_UNKNOWN ? defaultCode : detailed.code;
|
|
216
185
|
detailed.code = finalCode;
|
|
217
186
|
return {
|
|
@@ -228,4 +197,17 @@ export function createErrorResponse(error, defaultCode, path) {
|
|
|
228
197
|
isError: true,
|
|
229
198
|
};
|
|
230
199
|
}
|
|
200
|
+
export function validateMutuallyExclusive(options, optionNames, context) {
|
|
201
|
+
const definedOptions = optionNames.filter((name) => options[name] !== undefined);
|
|
202
|
+
if (definedOptions.length > 1) {
|
|
203
|
+
throw new McpError(ErrorCode.E_INVALID_INPUT, `Cannot specify multiple of: ${definedOptions.join(', ')}`, context);
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
export function validateOptionPair(options, optionA, optionB, context) {
|
|
207
|
+
const hasA = options[optionA] !== undefined;
|
|
208
|
+
const hasB = options[optionB] !== undefined;
|
|
209
|
+
if (hasA !== hasB) {
|
|
210
|
+
throw new McpError(ErrorCode.E_INVALID_INPUT, `${optionA} and ${optionB} must be specified together`, context);
|
|
211
|
+
}
|
|
212
|
+
}
|
|
231
213
|
//# sourceMappingURL=errors.js.map
|
package/dist/lib/errors.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"errors.js","sourceRoot":"","sources":["../../src/lib/errors.ts"],"names":[],"mappings":"AAAA,OAAO,EAEL,SAAS,GAEV,MAAM,oBAAoB,CAAC;AAE5B,
|
|
1
|
+
{"version":3,"file":"errors.js","sourceRoot":"","sources":["../../src/lib/errors.ts"],"names":[],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"file-operations.d.ts","sourceRoot":"","sources":["../../src/lib/file-operations.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"file-operations.d.ts","sourceRoot":"","sources":["../../src/lib/file-operations.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,18 +1,67 @@
|
|
|
1
1
|
import * as fs from 'node:fs/promises';
|
|
2
2
|
import * as path from 'node:path';
|
|
3
3
|
import fg from 'fast-glob';
|
|
4
|
+
import { Minimatch } from 'minimatch';
|
|
4
5
|
import safeRegex from 'safe-regex2';
|
|
5
6
|
import { DEFAULT_MAX_DEPTH, DEFAULT_MAX_RESULTS, DEFAULT_TOP_N, DIR_TRAVERSAL_CONCURRENCY, getMimeType, MAX_MEDIA_FILE_SIZE, MAX_SEARCHABLE_FILE_SIZE, MAX_TEXT_FILE_SIZE, PARALLEL_CONCURRENCY, } from './constants.js';
|
|
6
|
-
import { classifyAccessError, createExcludeMatcher, insertSorted, } from './directory-helpers.js';
|
|
7
7
|
import { ErrorCode, McpError } from './errors.js';
|
|
8
8
|
import { getFileType, isHidden, isProbablyBinary, processInParallel, readFile, runWorkQueue, } from './fs-helpers.js';
|
|
9
9
|
import { parseImageDimensions } from './image-parsing.js';
|
|
10
10
|
import { validateExistingPath, validateExistingPathDetailed, } from './path-validation.js';
|
|
11
11
|
import { isSimpleSafePattern, prepareSearchPattern, scanFileForContent, } from './search-helpers.js';
|
|
12
|
-
|
|
13
|
-
|
|
12
|
+
function createExcludeMatcher(excludePatterns) {
|
|
13
|
+
if (excludePatterns.length === 0) {
|
|
14
|
+
return () => false;
|
|
15
|
+
}
|
|
16
|
+
const matchers = excludePatterns.map((pattern) => new Minimatch(pattern));
|
|
17
|
+
return (name, relativePath) => matchers.some((m) => m.match(name) || m.match(relativePath));
|
|
18
|
+
}
|
|
19
|
+
function classifyAccessError(error) {
|
|
20
|
+
if (error instanceof McpError &&
|
|
21
|
+
(error.code === ErrorCode.E_ACCESS_DENIED ||
|
|
22
|
+
error.code === ErrorCode.E_SYMLINK_NOT_ALLOWED)) {
|
|
23
|
+
return 'symlink';
|
|
24
|
+
}
|
|
25
|
+
return 'inaccessible';
|
|
26
|
+
}
|
|
27
|
+
function insertSorted(arr, item, compare, maxLen) {
|
|
28
|
+
if (maxLen <= 0)
|
|
29
|
+
return;
|
|
30
|
+
const idx = arr.findIndex((el) => compare(item, el) < 0);
|
|
31
|
+
if (idx === -1) {
|
|
32
|
+
if (arr.length < maxLen)
|
|
33
|
+
arr.push(item);
|
|
34
|
+
}
|
|
35
|
+
else {
|
|
36
|
+
arr.splice(idx, 0, item);
|
|
37
|
+
if (arr.length > maxLen)
|
|
38
|
+
arr.pop();
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
const SORT_COMPARATORS = {
|
|
42
|
+
size: (a, b) => (b.size ?? 0) - (a.size ?? 0),
|
|
43
|
+
modified: (a, b) => (b.modified?.getTime() ?? 0) - (a.modified?.getTime() ?? 0),
|
|
44
|
+
type: (a, b) => {
|
|
45
|
+
if (a.type !== b.type)
|
|
46
|
+
return a.type === 'directory' ? -1 : 1;
|
|
47
|
+
return (a.name ?? '').localeCompare(b.name ?? '');
|
|
48
|
+
},
|
|
49
|
+
path: (a, b) => (a.path ?? '').localeCompare(b.path ?? ''),
|
|
50
|
+
name: (a, b) => (a.name ?? '').localeCompare(b.name ?? ''),
|
|
51
|
+
};
|
|
52
|
+
function sortByField(items, sortBy) {
|
|
53
|
+
const comparator = SORT_COMPARATORS[sortBy];
|
|
54
|
+
items.sort(comparator);
|
|
55
|
+
}
|
|
56
|
+
function sortSearchResults(results, sortBy) {
|
|
57
|
+
if (sortBy === 'name') {
|
|
58
|
+
results.sort((a, b) => path.basename(a.path ?? '').localeCompare(path.basename(b.path ?? '')));
|
|
59
|
+
}
|
|
60
|
+
else {
|
|
61
|
+
sortByField(results, sortBy);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
14
64
|
function getPermissions(mode) {
|
|
15
|
-
// Permission strings indexed by octal value (0-7)
|
|
16
65
|
const PERM_STRINGS = [
|
|
17
66
|
'---',
|
|
18
67
|
'--x',
|
|
@@ -23,7 +72,6 @@ function getPermissions(mode) {
|
|
|
23
72
|
'rw-',
|
|
24
73
|
'rwx',
|
|
25
74
|
];
|
|
26
|
-
// Bitwise mask guarantees indices 0-7
|
|
27
75
|
const ownerIndex = (mode >> 6) & 0b111;
|
|
28
76
|
const groupIndex = (mode >> 3) & 0b111;
|
|
29
77
|
const otherIndex = mode & 0b111;
|
|
@@ -173,7 +221,7 @@ export async function listDirectory(dirPath, options = {}) {
|
|
|
173
221
|
enqueue(enqueueDir);
|
|
174
222
|
}
|
|
175
223
|
}, DIR_TRAVERSAL_CONCURRENCY);
|
|
176
|
-
entries
|
|
224
|
+
sortByField(entries, sortBy);
|
|
177
225
|
return {
|
|
178
226
|
path: validPath,
|
|
179
227
|
entries,
|
|
@@ -250,7 +298,7 @@ export async function searchFiles(basePath, pattern, excludePatterns = [], optio
|
|
|
250
298
|
}
|
|
251
299
|
}
|
|
252
300
|
await flushBatch();
|
|
253
|
-
results
|
|
301
|
+
sortSearchResults(results, sortBy);
|
|
254
302
|
return {
|
|
255
303
|
basePath: validPath,
|
|
256
304
|
pattern,
|
|
@@ -269,7 +317,6 @@ export async function readMultipleFiles(filePaths, options = {}) {
|
|
|
269
317
|
if (filePaths.length === 0)
|
|
270
318
|
return [];
|
|
271
319
|
const output = filePaths.map((filePath) => ({ path: filePath }));
|
|
272
|
-
// Pre-calculate total size to avoid race condition in parallel reads
|
|
273
320
|
let totalSize = 0;
|
|
274
321
|
const fileSizes = new Map();
|
|
275
322
|
for (const filePath of filePaths) {
|
|
@@ -280,7 +327,6 @@ export async function readMultipleFiles(filePaths, options = {}) {
|
|
|
280
327
|
totalSize += stats.size;
|
|
281
328
|
}
|
|
282
329
|
catch {
|
|
283
|
-
// Skip files we can't access - they'll error during read
|
|
284
330
|
fileSizes.set(filePath, 0);
|
|
285
331
|
}
|
|
286
332
|
}
|
|
@@ -357,82 +403,88 @@ export async function searchContent(basePath, searchPattern, options = {}) {
|
|
|
357
403
|
suppressErrors: true,
|
|
358
404
|
followSymbolicLinks: false,
|
|
359
405
|
});
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
}
|
|
368
|
-
catch {
|
|
369
|
-
console.error('[SECURITY] fast-glob returned invalid path:', file);
|
|
370
|
-
stopNow('maxFiles');
|
|
371
|
-
break;
|
|
372
|
-
}
|
|
373
|
-
}
|
|
374
|
-
if (deadlineMs !== undefined && Date.now() > deadlineMs) {
|
|
375
|
-
stopNow('timeout');
|
|
376
|
-
break;
|
|
377
|
-
}
|
|
378
|
-
if (maxFilesScanned !== undefined && filesScanned >= maxFilesScanned) {
|
|
379
|
-
stopNow('maxFiles');
|
|
380
|
-
break;
|
|
381
|
-
}
|
|
382
|
-
if (matches.length >= maxResults) {
|
|
383
|
-
stopNow('maxResults');
|
|
384
|
-
break;
|
|
385
|
-
}
|
|
386
|
-
try {
|
|
387
|
-
// fast-glob operates within validated cwd with followSymbolicLinks:false,
|
|
388
|
-
// so paths are already bounded - skip redundant validateExistingPath for glob results
|
|
389
|
-
const handle = await fs.open(file, 'r');
|
|
390
|
-
let shouldScan = true;
|
|
391
|
-
try {
|
|
392
|
-
const stats = await handle.stat();
|
|
393
|
-
filesScanned++;
|
|
394
|
-
if (stats.size > maxFileSize) {
|
|
395
|
-
skippedTooLarge++;
|
|
396
|
-
shouldScan = false;
|
|
406
|
+
try {
|
|
407
|
+
for await (const entry of stream) {
|
|
408
|
+
const file = typeof entry === 'string' ? entry : String(entry);
|
|
409
|
+
if (!firstPathValidated) {
|
|
410
|
+
try {
|
|
411
|
+
await validateExistingPath(file);
|
|
412
|
+
firstPathValidated = true;
|
|
397
413
|
}
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
shouldScan = false;
|
|
403
|
-
}
|
|
414
|
+
catch {
|
|
415
|
+
console.error('[SECURITY] fast-glob returned invalid path:', file);
|
|
416
|
+
stopNow('maxFiles');
|
|
417
|
+
break;
|
|
404
418
|
}
|
|
405
419
|
}
|
|
406
|
-
finally {
|
|
407
|
-
await handle.close().catch(() => { });
|
|
408
|
-
}
|
|
409
|
-
if (!shouldScan)
|
|
410
|
-
continue;
|
|
411
|
-
const scanResult = await scanFileForContent(file, regex, {
|
|
412
|
-
maxResults,
|
|
413
|
-
contextLines,
|
|
414
|
-
deadlineMs,
|
|
415
|
-
currentMatchCount: matches.length,
|
|
416
|
-
});
|
|
417
|
-
matches.push(...scanResult.matches);
|
|
418
|
-
linesSkippedDueToRegexTimeout += scanResult.linesSkippedDueToRegexTimeout;
|
|
419
|
-
if (scanResult.fileHadMatches)
|
|
420
|
-
filesMatched++;
|
|
421
420
|
if (deadlineMs !== undefined && Date.now() > deadlineMs) {
|
|
422
421
|
stopNow('timeout');
|
|
423
422
|
break;
|
|
424
423
|
}
|
|
424
|
+
if (maxFilesScanned !== undefined && filesScanned >= maxFilesScanned) {
|
|
425
|
+
stopNow('maxFiles');
|
|
426
|
+
break;
|
|
427
|
+
}
|
|
425
428
|
if (matches.length >= maxResults) {
|
|
426
429
|
stopNow('maxResults');
|
|
427
430
|
break;
|
|
428
431
|
}
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
432
|
+
try {
|
|
433
|
+
const handle = await fs.open(file, 'r');
|
|
434
|
+
let shouldScan = true;
|
|
435
|
+
try {
|
|
436
|
+
const stats = await handle.stat();
|
|
437
|
+
filesScanned++;
|
|
438
|
+
if (stats.size > maxFileSize) {
|
|
439
|
+
skippedTooLarge++;
|
|
440
|
+
shouldScan = false;
|
|
441
|
+
}
|
|
442
|
+
else if (skipBinary) {
|
|
443
|
+
const binary = await isProbablyBinary(file, handle);
|
|
444
|
+
if (binary) {
|
|
445
|
+
skippedBinary++;
|
|
446
|
+
shouldScan = false;
|
|
447
|
+
}
|
|
448
|
+
}
|
|
449
|
+
}
|
|
450
|
+
finally {
|
|
451
|
+
await handle.close().catch(() => { });
|
|
452
|
+
}
|
|
453
|
+
if (!shouldScan)
|
|
454
|
+
continue;
|
|
455
|
+
const scanResult = await scanFileForContent(file, regex, {
|
|
456
|
+
maxResults,
|
|
457
|
+
contextLines,
|
|
458
|
+
deadlineMs,
|
|
459
|
+
currentMatchCount: matches.length,
|
|
460
|
+
isLiteral,
|
|
461
|
+
searchString: isLiteral ? searchPattern : undefined,
|
|
462
|
+
caseSensitive,
|
|
463
|
+
});
|
|
464
|
+
matches.push(...scanResult.matches);
|
|
465
|
+
linesSkippedDueToRegexTimeout +=
|
|
466
|
+
scanResult.linesSkippedDueToRegexTimeout;
|
|
467
|
+
if (scanResult.fileHadMatches)
|
|
468
|
+
filesMatched++;
|
|
469
|
+
if (deadlineMs !== undefined && Date.now() > deadlineMs) {
|
|
470
|
+
stopNow('timeout');
|
|
471
|
+
break;
|
|
472
|
+
}
|
|
473
|
+
if (matches.length >= maxResults) {
|
|
474
|
+
stopNow('maxResults');
|
|
475
|
+
break;
|
|
476
|
+
}
|
|
477
|
+
if (stoppedReason !== undefined)
|
|
478
|
+
break;
|
|
479
|
+
}
|
|
480
|
+
catch {
|
|
481
|
+
skippedInaccessible++;
|
|
482
|
+
}
|
|
434
483
|
}
|
|
435
484
|
}
|
|
485
|
+
finally {
|
|
486
|
+
// Ensure the stream is closed
|
|
487
|
+
}
|
|
436
488
|
return {
|
|
437
489
|
basePath: validPath,
|
|
438
490
|
pattern: searchPattern,
|
|
@@ -558,7 +610,6 @@ export async function getDirectoryTree(dirPath, options = {}) {
|
|
|
558
610
|
};
|
|
559
611
|
const collectedEntries = [];
|
|
560
612
|
const directoriesFound = new Set([validPath]);
|
|
561
|
-
// Phase 1: Collect all entries using runWorkQueue for work-stealing concurrency
|
|
562
613
|
await runWorkQueue([{ currentPath: validPath, depth: 0 }], async ({ currentPath, depth }, enqueue) => {
|
|
563
614
|
if (hitMaxFiles()) {
|
|
564
615
|
truncated = true;
|
|
@@ -583,23 +634,19 @@ export async function getDirectoryTree(dirPath, options = {}) {
|
|
|
583
634
|
break;
|
|
584
635
|
}
|
|
585
636
|
const { name } = item;
|
|
586
|
-
// Filter hidden files
|
|
587
637
|
if (!includeHidden && name.startsWith('.')) {
|
|
588
638
|
continue;
|
|
589
639
|
}
|
|
590
640
|
const fullPath = path.join(currentPath, name);
|
|
591
641
|
const relativePath = path.relative(validPath, fullPath);
|
|
592
|
-
// Check exclusion patterns
|
|
593
642
|
if (shouldExclude(name, relativePath)) {
|
|
594
643
|
continue;
|
|
595
644
|
}
|
|
596
|
-
// Handle symlinks - skip but count
|
|
597
645
|
if (item.isSymbolicLink()) {
|
|
598
646
|
symlinksNotFollowed++;
|
|
599
647
|
continue;
|
|
600
648
|
}
|
|
601
649
|
try {
|
|
602
|
-
// Validate path is within allowed directories
|
|
603
650
|
const { resolvedPath, isSymlink } = await validateExistingPathDetailed(fullPath);
|
|
604
651
|
if (isSymlink) {
|
|
605
652
|
symlinksNotFollowed++;
|
|
@@ -625,12 +672,10 @@ export async function getDirectoryTree(dirPath, options = {}) {
|
|
|
625
672
|
type: 'directory',
|
|
626
673
|
depth,
|
|
627
674
|
});
|
|
628
|
-
// Enqueue subdirectory for traversal if not at max depth
|
|
629
675
|
if (depth + 1 <= maxDepth) {
|
|
630
676
|
enqueue({ currentPath: resolvedPath, depth: depth + 1 });
|
|
631
677
|
}
|
|
632
678
|
else {
|
|
633
|
-
// Directory exists but we can't recurse due to depth limit
|
|
634
679
|
truncated = true;
|
|
635
680
|
}
|
|
636
681
|
}
|
|
@@ -645,13 +690,10 @@ export async function getDirectoryTree(dirPath, options = {}) {
|
|
|
645
690
|
}
|
|
646
691
|
}
|
|
647
692
|
}, DIR_TRAVERSAL_CONCURRENCY);
|
|
648
|
-
// Phase 2: Build tree structure from collected entries
|
|
649
693
|
const childrenByParent = new Map();
|
|
650
|
-
// Initialize all directories with empty children arrays
|
|
651
694
|
for (const dirPath of directoriesFound) {
|
|
652
695
|
childrenByParent.set(dirPath, []);
|
|
653
696
|
}
|
|
654
|
-
// Group entries by parent and create TreeEntry objects
|
|
655
697
|
for (const entry of collectedEntries) {
|
|
656
698
|
const treeEntry = {
|
|
657
699
|
name: entry.name,
|
|
@@ -669,7 +711,6 @@ export async function getDirectoryTree(dirPath, options = {}) {
|
|
|
669
711
|
siblings.push(treeEntry);
|
|
670
712
|
}
|
|
671
713
|
}
|
|
672
|
-
// Sort children: directories first, then alphabetically by name
|
|
673
714
|
const sortChildren = (entries) => {
|
|
674
715
|
entries.sort((a, b) => {
|
|
675
716
|
if (a.type !== b.type) {
|
|
@@ -681,7 +722,6 @@ export async function getDirectoryTree(dirPath, options = {}) {
|
|
|
681
722
|
for (const children of childrenByParent.values()) {
|
|
682
723
|
sortChildren(children);
|
|
683
724
|
}
|
|
684
|
-
// Build root entry
|
|
685
725
|
const rootName = path.basename(validPath);
|
|
686
726
|
const tree = {
|
|
687
727
|
name: rootName || validPath,
|