@j0hanz/fs-context-mcp 2.2.0 → 2.3.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 +171 -417
- package/dist/assets/logo.svg +3766 -0
- package/dist/config.d.ts +0 -1
- package/dist/config.js +0 -1
- package/dist/index.d.ts +0 -1
- package/dist/index.js +0 -1
- package/dist/lib/constants.d.ts +0 -1
- package/dist/lib/constants.js +0 -1
- package/dist/lib/errors.d.ts +0 -1
- package/dist/lib/errors.js +0 -1
- package/dist/lib/file-operations/file-info.d.ts +0 -1
- package/dist/lib/file-operations/file-info.js +0 -1
- package/dist/lib/file-operations/gitignore.d.ts +0 -1
- package/dist/lib/file-operations/gitignore.js +0 -1
- package/dist/lib/file-operations/glob-engine.d.ts +0 -1
- package/dist/lib/file-operations/glob-engine.js +101 -84
- package/dist/lib/file-operations/list-directory.d.ts +0 -1
- package/dist/lib/file-operations/list-directory.js +91 -62
- package/dist/lib/file-operations/read-multiple-files.d.ts +0 -1
- package/dist/lib/file-operations/read-multiple-files.js +0 -1
- package/dist/lib/file-operations/search-content.d.ts +0 -1
- package/dist/lib/file-operations/search-content.js +68 -21
- package/dist/lib/file-operations/search-files.d.ts +0 -1
- package/dist/lib/file-operations/search-files.js +21 -11
- package/dist/lib/file-operations/search-worker.d.ts +0 -1
- package/dist/lib/file-operations/search-worker.js +0 -1
- package/dist/lib/file-operations/tree.d.ts +0 -1
- package/dist/lib/file-operations/tree.js +18 -8
- package/dist/lib/fs-helpers.d.ts +0 -1
- package/dist/lib/fs-helpers.js +19 -25
- package/dist/lib/observability.d.ts +0 -1
- package/dist/lib/observability.js +49 -43
- package/dist/lib/path-policy.d.ts +0 -1
- package/dist/lib/path-policy.js +0 -1
- package/dist/lib/path-validation.d.ts +0 -1
- package/dist/lib/path-validation.js +54 -38
- package/dist/lib/resource-store.d.ts +0 -1
- package/dist/lib/resource-store.js +0 -1
- package/dist/resources.d.ts +2 -3
- package/dist/resources.js +16 -3
- package/dist/schemas.d.ts +0 -1
- package/dist/schemas.js +35 -51
- package/dist/server.d.ts +0 -1
- package/dist/server.js +42 -11
- package/dist/tools/list-directory.d.ts +0 -1
- package/dist/tools/list-directory.js +14 -2
- package/dist/tools/read-multiple.d.ts +0 -1
- package/dist/tools/read-multiple.js +14 -2
- package/dist/tools/read.d.ts +0 -1
- package/dist/tools/read.js +14 -2
- package/dist/tools/roots.d.ts +0 -1
- package/dist/tools/roots.js +14 -2
- package/dist/tools/search-content.d.ts +0 -1
- package/dist/tools/search-content.js +29 -14
- package/dist/tools/search-files.d.ts +0 -1
- package/dist/tools/search-files.js +14 -2
- package/dist/tools/shared.d.ts +7 -1
- package/dist/tools/shared.js +7 -4
- package/dist/tools/stat-many.d.ts +0 -1
- package/dist/tools/stat-many.js +14 -2
- package/dist/tools/stat.d.ts +0 -1
- package/dist/tools/stat.js +14 -2
- package/dist/tools/tree.d.ts +0 -1
- package/dist/tools/tree.js +14 -2
- package/dist/tools.d.ts +0 -1
- package/dist/tools.js +0 -1
- package/package.json +22 -19
- package/dist/config.d.ts.map +0 -1
- package/dist/config.js.map +0 -1
- package/dist/index.d.ts.map +0 -1
- package/dist/index.js.map +0 -1
- package/dist/lib/constants.d.ts.map +0 -1
- package/dist/lib/constants.js.map +0 -1
- package/dist/lib/errors.d.ts.map +0 -1
- package/dist/lib/errors.js.map +0 -1
- package/dist/lib/file-operations/file-info.d.ts.map +0 -1
- package/dist/lib/file-operations/file-info.js.map +0 -1
- package/dist/lib/file-operations/gitignore.d.ts.map +0 -1
- package/dist/lib/file-operations/gitignore.js.map +0 -1
- package/dist/lib/file-operations/glob-engine.d.ts.map +0 -1
- package/dist/lib/file-operations/glob-engine.js.map +0 -1
- package/dist/lib/file-operations/list-directory.d.ts.map +0 -1
- package/dist/lib/file-operations/list-directory.js.map +0 -1
- package/dist/lib/file-operations/read-multiple-files.d.ts.map +0 -1
- package/dist/lib/file-operations/read-multiple-files.js.map +0 -1
- package/dist/lib/file-operations/search-content.d.ts.map +0 -1
- package/dist/lib/file-operations/search-content.js.map +0 -1
- package/dist/lib/file-operations/search-files.d.ts.map +0 -1
- package/dist/lib/file-operations/search-files.js.map +0 -1
- package/dist/lib/file-operations/search-worker.d.ts.map +0 -1
- package/dist/lib/file-operations/search-worker.js.map +0 -1
- package/dist/lib/file-operations/tree.d.ts.map +0 -1
- package/dist/lib/file-operations/tree.js.map +0 -1
- package/dist/lib/fs-helpers.d.ts.map +0 -1
- package/dist/lib/fs-helpers.js.map +0 -1
- package/dist/lib/observability.d.ts.map +0 -1
- package/dist/lib/observability.js.map +0 -1
- package/dist/lib/path-policy.d.ts.map +0 -1
- package/dist/lib/path-policy.js.map +0 -1
- package/dist/lib/path-validation.d.ts.map +0 -1
- package/dist/lib/path-validation.js.map +0 -1
- package/dist/lib/resource-store.d.ts.map +0 -1
- package/dist/lib/resource-store.js.map +0 -1
- package/dist/resources.d.ts.map +0 -1
- package/dist/resources.js.map +0 -1
- package/dist/schemas.d.ts.map +0 -1
- package/dist/schemas.js.map +0 -1
- package/dist/server.d.ts.map +0 -1
- package/dist/server.js.map +0 -1
- package/dist/tools/list-directory.d.ts.map +0 -1
- package/dist/tools/list-directory.js.map +0 -1
- package/dist/tools/read-multiple.d.ts.map +0 -1
- package/dist/tools/read-multiple.js.map +0 -1
- package/dist/tools/read.d.ts.map +0 -1
- package/dist/tools/read.js.map +0 -1
- package/dist/tools/roots.d.ts.map +0 -1
- package/dist/tools/roots.js.map +0 -1
- package/dist/tools/search-content.d.ts.map +0 -1
- package/dist/tools/search-content.js.map +0 -1
- package/dist/tools/search-files.d.ts.map +0 -1
- package/dist/tools/search-files.js.map +0 -1
- package/dist/tools/shared.d.ts.map +0 -1
- package/dist/tools/shared.js.map +0 -1
- package/dist/tools/stat-many.d.ts.map +0 -1
- package/dist/tools/stat-many.js.map +0 -1
- package/dist/tools/stat.d.ts.map +0 -1
- package/dist/tools/stat.js.map +0 -1
- package/dist/tools/tree.d.ts.map +0 -1
- package/dist/tools/tree.js.map +0 -1
- package/dist/tools.d.ts.map +0 -1
- package/dist/tools.js.map +0 -1
package/dist/config.d.ts
CHANGED
package/dist/config.js
CHANGED
package/dist/index.d.ts
CHANGED
package/dist/index.js
CHANGED
package/dist/lib/constants.d.ts
CHANGED
|
@@ -20,4 +20,3 @@ export declare const SENSITIVE_FILE_ALLOWLIST: string[];
|
|
|
20
20
|
export declare const KNOWN_BINARY_EXTENSIONS: Set<string>;
|
|
21
21
|
export declare const DEFAULT_EXCLUDE_PATTERNS: string[];
|
|
22
22
|
export declare function getMimeType(ext: string): string;
|
|
23
|
-
//# sourceMappingURL=constants.d.ts.map
|
package/dist/lib/constants.js
CHANGED
package/dist/lib/errors.d.ts
CHANGED
|
@@ -18,4 +18,3 @@ export declare class McpError extends Error {
|
|
|
18
18
|
export declare function createDetailedError(error: unknown, path?: string, additionalDetails?: Record<string, unknown>): DetailedError;
|
|
19
19
|
export declare function formatDetailedError(error: DetailedError): string;
|
|
20
20
|
export declare function getSuggestion(code: ErrorCode): string;
|
|
21
|
-
//# sourceMappingURL=errors.d.ts.map
|
package/dist/lib/errors.js
CHANGED
|
@@ -2,6 +2,9 @@ import * as fs from 'node:fs/promises';
|
|
|
2
2
|
import * as path from 'node:path';
|
|
3
3
|
import { glob as fsGlob } from 'node:fs/promises';
|
|
4
4
|
import { publishOpsTraceEnd, publishOpsTraceError, publishOpsTraceStart, shouldPublishOpsTrace, } from '../observability.js';
|
|
5
|
+
function normalizeToPosixPath(filePath) {
|
|
6
|
+
return filePath.replace(/\\/gu, '/');
|
|
7
|
+
}
|
|
5
8
|
function normalizePattern(pattern, baseNameMatch) {
|
|
6
9
|
const normalized = pattern.replace(/\\/gu, '/');
|
|
7
10
|
if (!baseNameMatch)
|
|
@@ -41,14 +44,12 @@ function splitPatternPrefix(normalizedPattern) {
|
|
|
41
44
|
remainder: remainderSegments.join('/'),
|
|
42
45
|
};
|
|
43
46
|
}
|
|
44
|
-
function normalizeToPosixPath(filePath) {
|
|
45
|
-
return filePath.replace(/\\/gu, '/');
|
|
46
|
-
}
|
|
47
47
|
function buildHiddenPatterns(normalizedPattern, maxDepth) {
|
|
48
48
|
const patterns = new Set();
|
|
49
49
|
patterns.add(normalizedPattern);
|
|
50
50
|
const { prefix, remainder } = splitPatternPrefix(normalizedPattern);
|
|
51
51
|
const remainderSegments = remainder.length > 0 ? remainder.split('/') : [];
|
|
52
|
+
// Dotfile candidate handling (e.g. foo/bar -> .foo/bar, foo/.bar)
|
|
52
53
|
const dotfileSegments = [...remainderSegments];
|
|
53
54
|
const firstCandidateIndex = dotfileSegments.findIndex((segment) => segment !== '**' && segment.length > 0);
|
|
54
55
|
if (firstCandidateIndex !== -1) {
|
|
@@ -58,6 +59,7 @@ function buildHiddenPatterns(normalizedPattern, maxDepth) {
|
|
|
58
59
|
patterns.add(`${prefix}${dotfileSegments.join('/')}`);
|
|
59
60
|
}
|
|
60
61
|
}
|
|
62
|
+
// Globstar handling (e.g. **/foo -> **/.*/**/foo, **/.foo)
|
|
61
63
|
if (remainder.startsWith('**/')) {
|
|
62
64
|
const afterGlobstar = remainder.slice('**/'.length);
|
|
63
65
|
for (let depth = 0; depth <= maxDepth; depth += 1) {
|
|
@@ -70,6 +72,40 @@ function buildHiddenPatterns(normalizedPattern, maxDepth) {
|
|
|
70
72
|
}
|
|
71
73
|
return [...patterns];
|
|
72
74
|
}
|
|
75
|
+
function shouldUseGlobDirents(options) {
|
|
76
|
+
return !options.stats && !options.followSymbolicLinks;
|
|
77
|
+
}
|
|
78
|
+
function normalizeOptions(options) {
|
|
79
|
+
const normalized = normalizePattern(options.pattern, options.baseNameMatch);
|
|
80
|
+
const maxHiddenDepth = options.maxDepth ?? 10;
|
|
81
|
+
const patterns = options.includeHidden
|
|
82
|
+
? buildHiddenPatterns(normalized, maxHiddenDepth)
|
|
83
|
+
: [normalized];
|
|
84
|
+
const result = {
|
|
85
|
+
cwd: options.cwd,
|
|
86
|
+
patterns,
|
|
87
|
+
exclude: normalizeIgnorePatterns(options.excludePatterns),
|
|
88
|
+
useDirents: shouldUseGlobDirents(options),
|
|
89
|
+
suppressErrors: options.suppressErrors ?? false,
|
|
90
|
+
};
|
|
91
|
+
if (options.maxDepth !== undefined) {
|
|
92
|
+
result.maxDepth = options.maxDepth;
|
|
93
|
+
}
|
|
94
|
+
return result;
|
|
95
|
+
}
|
|
96
|
+
function resolveDepth(cwd, entryPath) {
|
|
97
|
+
const relative = path.relative(cwd, entryPath);
|
|
98
|
+
if (relative.length === 0)
|
|
99
|
+
return 0;
|
|
100
|
+
const normalized = normalizeToPosixPath(relative);
|
|
101
|
+
const parts = normalized.split('/').filter((part) => part.length > 0);
|
|
102
|
+
return parts.length;
|
|
103
|
+
}
|
|
104
|
+
function isWithinDepthLimit(normalized, entryPath) {
|
|
105
|
+
if (normalized.maxDepth === undefined)
|
|
106
|
+
return true;
|
|
107
|
+
return resolveDepth(normalized.cwd, entryPath) <= normalized.maxDepth;
|
|
108
|
+
}
|
|
73
109
|
function toAbsolutePath(cwd, match) {
|
|
74
110
|
return path.isAbsolute(match) ? match : path.resolve(cwd, match);
|
|
75
111
|
}
|
|
@@ -78,124 +114,106 @@ function toAbsolutePathFromDirent(cwd, dirent) {
|
|
|
78
114
|
return path.resolve(base, dirent.name);
|
|
79
115
|
}
|
|
80
116
|
function isGlobDirentLike(value) {
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
117
|
+
if (typeof value !== 'object' || value === null)
|
|
118
|
+
return false;
|
|
119
|
+
const v = value;
|
|
120
|
+
return (typeof v.name === 'string' &&
|
|
121
|
+
typeof v.isFile === 'function' &&
|
|
122
|
+
typeof v.isDirectory === 'function' &&
|
|
123
|
+
typeof v.isSymbolicLink === 'function');
|
|
85
124
|
}
|
|
86
125
|
function resolveAbsolutePathFromGlobMatch(cwd, match) {
|
|
87
126
|
return typeof match === 'string'
|
|
88
127
|
? toAbsolutePath(cwd, match)
|
|
89
128
|
: toAbsolutePathFromDirent(cwd, match);
|
|
90
129
|
}
|
|
91
|
-
function
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
130
|
+
function passesOnlyFilesFilter(dirent, onlyFiles) {
|
|
131
|
+
return !onlyFiles || dirent.isFile();
|
|
132
|
+
}
|
|
133
|
+
function buildEntryFromGlobDirent(absolutePath, dirent, options) {
|
|
134
|
+
if (!passesOnlyFilesFilter(dirent, options.onlyFiles))
|
|
135
|
+
return null;
|
|
136
|
+
return { path: absolutePath, dirent };
|
|
137
|
+
}
|
|
138
|
+
async function buildEntry(entryPath, options) {
|
|
139
|
+
try {
|
|
140
|
+
const stats = options.followSymbolicLinks
|
|
141
|
+
? await fs.stat(entryPath)
|
|
142
|
+
: await fs.lstat(entryPath);
|
|
143
|
+
if (!passesOnlyFilesFilter(stats, options.onlyFiles)) {
|
|
144
|
+
return null;
|
|
145
|
+
}
|
|
146
|
+
const result = { path: entryPath, dirent: stats };
|
|
147
|
+
if (options.stats)
|
|
148
|
+
result.stats = stats;
|
|
149
|
+
return result;
|
|
150
|
+
}
|
|
151
|
+
catch (error) {
|
|
152
|
+
if (options.suppressErrors)
|
|
153
|
+
return null;
|
|
154
|
+
throw error;
|
|
155
|
+
}
|
|
95
156
|
}
|
|
96
|
-
function
|
|
157
|
+
function tryCreateGlobIterable(pattern, normalized) {
|
|
97
158
|
try {
|
|
98
159
|
return fsGlob(pattern, {
|
|
99
|
-
cwd,
|
|
100
|
-
exclude,
|
|
101
|
-
withFileTypes,
|
|
160
|
+
cwd: normalized.cwd,
|
|
161
|
+
exclude: normalized.exclude,
|
|
162
|
+
withFileTypes: normalized.useDirents,
|
|
102
163
|
});
|
|
103
164
|
}
|
|
104
165
|
catch (error) {
|
|
105
|
-
if (suppressErrors)
|
|
166
|
+
if (normalized.suppressErrors)
|
|
106
167
|
return null;
|
|
107
168
|
throw error;
|
|
108
169
|
}
|
|
109
170
|
}
|
|
110
|
-
function
|
|
111
|
-
return !options.stats && !options.followSymbolicLinks;
|
|
112
|
-
}
|
|
113
|
-
function buildEntryFromGlobDirent(absolutePath, dirent, options) {
|
|
114
|
-
if (options.onlyFiles && !dirent.isFile())
|
|
115
|
-
return null;
|
|
116
|
-
return { path: absolutePath, dirent };
|
|
117
|
-
}
|
|
118
|
-
async function processGlobMatch(match, options, seen, useDirents) {
|
|
119
|
-
const absolutePath = resolveAbsolutePathFromGlobMatch(options.cwd, match);
|
|
171
|
+
function shouldYieldEntry(absolutePath, normalized, seen) {
|
|
120
172
|
if (seen.has(absolutePath))
|
|
121
|
-
return
|
|
173
|
+
return false;
|
|
122
174
|
seen.add(absolutePath);
|
|
123
|
-
if (!isWithinDepthLimit(
|
|
124
|
-
return
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
if (options.onlyFiles && !entry.dirent.isFile())
|
|
175
|
+
if (!isWithinDepthLimit(normalized, absolutePath))
|
|
176
|
+
return false;
|
|
177
|
+
return true;
|
|
178
|
+
}
|
|
179
|
+
async function buildEntryFromMatch(match, options, normalized, seen) {
|
|
180
|
+
const absolutePath = resolveAbsolutePathFromGlobMatch(normalized.cwd, match);
|
|
181
|
+
if (!shouldYieldEntry(absolutePath, normalized, seen)) {
|
|
131
182
|
return null;
|
|
132
|
-
|
|
183
|
+
}
|
|
184
|
+
if (normalized.useDirents && isGlobDirentLike(match)) {
|
|
185
|
+
return buildEntryFromGlobDirent(absolutePath, match, options);
|
|
186
|
+
}
|
|
187
|
+
return await buildEntry(absolutePath, options);
|
|
133
188
|
}
|
|
134
|
-
async function* scanPattern(pattern, options,
|
|
135
|
-
const
|
|
136
|
-
|
|
137
|
-
if (!iterator)
|
|
189
|
+
async function* scanPattern(pattern, options, normalized, seen) {
|
|
190
|
+
const iterable = tryCreateGlobIterable(pattern, normalized);
|
|
191
|
+
if (!iterable)
|
|
138
192
|
return;
|
|
139
193
|
try {
|
|
140
|
-
for await (const match of
|
|
141
|
-
const entry = await
|
|
194
|
+
for await (const match of iterable) {
|
|
195
|
+
const entry = await buildEntryFromMatch(match, options, normalized, seen);
|
|
142
196
|
if (entry)
|
|
143
197
|
yield entry;
|
|
144
198
|
}
|
|
145
199
|
}
|
|
146
200
|
catch (error) {
|
|
147
|
-
if (
|
|
201
|
+
if (normalized.suppressErrors)
|
|
148
202
|
return;
|
|
149
203
|
throw error;
|
|
150
204
|
}
|
|
151
205
|
}
|
|
152
|
-
function resolveDepth(cwd, entryPath) {
|
|
153
|
-
const relative = path.relative(cwd, entryPath);
|
|
154
|
-
if (relative.length === 0)
|
|
155
|
-
return 0;
|
|
156
|
-
const normalized = normalizeToPosixPath(relative);
|
|
157
|
-
const parts = normalized.split('/').filter((part) => part.length > 0);
|
|
158
|
-
return parts.length;
|
|
159
|
-
}
|
|
160
|
-
async function buildEntry(entryPath, options) {
|
|
161
|
-
try {
|
|
162
|
-
const stats = options.followSymbolicLinks
|
|
163
|
-
? await fs.stat(entryPath)
|
|
164
|
-
: await fs.lstat(entryPath);
|
|
165
|
-
const result = {
|
|
166
|
-
path: entryPath,
|
|
167
|
-
dirent: stats,
|
|
168
|
-
};
|
|
169
|
-
if (options.stats)
|
|
170
|
-
result.stats = stats;
|
|
171
|
-
return result;
|
|
172
|
-
}
|
|
173
|
-
catch (error) {
|
|
174
|
-
if (options.suppressErrors) {
|
|
175
|
-
return null;
|
|
176
|
-
}
|
|
177
|
-
throw error;
|
|
178
|
-
}
|
|
179
|
-
}
|
|
180
206
|
async function* nativeGlobEntries(options) {
|
|
181
|
-
const normalized =
|
|
182
|
-
const maxHiddenDepth = options.maxDepth ?? 10;
|
|
183
|
-
const patterns = options.includeHidden
|
|
184
|
-
? buildHiddenPatterns(normalized, maxHiddenDepth)
|
|
185
|
-
: [normalized];
|
|
186
|
-
const exclude = normalizeIgnorePatterns(options.excludePatterns);
|
|
207
|
+
const normalized = normalizeOptions(options);
|
|
187
208
|
const seen = new Set();
|
|
188
|
-
for (const pattern of patterns) {
|
|
189
|
-
yield* scanPattern(pattern, options,
|
|
209
|
+
for (const pattern of normalized.patterns) {
|
|
210
|
+
yield* scanPattern(pattern, options, normalized, seen);
|
|
190
211
|
}
|
|
191
212
|
}
|
|
192
213
|
export async function* globEntries(options) {
|
|
193
214
|
const engine = 'node:fs/promises.glob';
|
|
194
215
|
const traceContext = shouldPublishOpsTrace()
|
|
195
|
-
? {
|
|
196
|
-
op: 'globEntries',
|
|
197
|
-
engine,
|
|
198
|
-
}
|
|
216
|
+
? { op: 'globEntries', engine }
|
|
199
217
|
: undefined;
|
|
200
218
|
if (traceContext)
|
|
201
219
|
publishOpsTraceStart(traceContext);
|
|
@@ -212,4 +230,3 @@ export async function* globEntries(options) {
|
|
|
212
230
|
publishOpsTraceEnd(traceContext);
|
|
213
231
|
}
|
|
214
232
|
}
|
|
215
|
-
//# sourceMappingURL=glob-engine.js.map
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import * as fsp from 'node:fs/promises';
|
|
2
2
|
import * as path from 'node:path';
|
|
3
3
|
import { DEFAULT_LIST_MAX_ENTRIES, DEFAULT_MAX_DEPTH, DEFAULT_SEARCH_TIMEOUT_MS, PARALLEL_CONCURRENCY, } from '../constants.js';
|
|
4
|
-
import { createTimedAbortSignal, withAbort } from '../fs-helpers.js';
|
|
4
|
+
import { createTimedAbortSignal, processInParallel, withAbort, } from '../fs-helpers.js';
|
|
5
5
|
import { isSensitivePath } from '../path-policy.js';
|
|
6
|
-
import { validateExistingDirectory, validateExistingPathDetailed, } from '../path-validation.js';
|
|
6
|
+
import { isPathWithinDirectories, normalizePath, validateExistingDirectory, validateExistingPathDetailed, } from '../path-validation.js';
|
|
7
7
|
import { globEntries } from './glob-engine.js';
|
|
8
8
|
function normalizePattern(pattern) {
|
|
9
9
|
if (!pattern || pattern.length === 0)
|
|
@@ -20,10 +20,8 @@ function normalizeOptions(options) {
|
|
|
20
20
|
sortBy: options.sortBy ?? 'name',
|
|
21
21
|
includeSymlinkTargets: options.includeSymlinkTargets ?? false,
|
|
22
22
|
timeoutMs: options.timeoutMs ?? DEFAULT_SEARCH_TIMEOUT_MS,
|
|
23
|
+
...(pattern !== undefined ? { pattern } : {}),
|
|
23
24
|
};
|
|
24
|
-
if (pattern !== undefined) {
|
|
25
|
-
normalized.pattern = pattern;
|
|
26
|
-
}
|
|
27
25
|
return normalized;
|
|
28
26
|
}
|
|
29
27
|
function sortEntries(entries, sortBy) {
|
|
@@ -44,31 +42,51 @@ function resolveMaxDepth(normalized) {
|
|
|
44
42
|
}
|
|
45
43
|
return normalized.maxDepth;
|
|
46
44
|
}
|
|
47
|
-
function
|
|
48
|
-
if (signal.aborted)
|
|
49
|
-
return
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
}
|
|
54
|
-
return { stop: false };
|
|
45
|
+
function getStopReason(signal, acceptedCount, maxEntries) {
|
|
46
|
+
if (signal.aborted)
|
|
47
|
+
return 'aborted';
|
|
48
|
+
if (acceptedCount >= maxEntries)
|
|
49
|
+
return 'maxEntries';
|
|
50
|
+
return undefined;
|
|
55
51
|
}
|
|
56
52
|
async function* readDirectoryEntries(basePath, normalized, needsStats, signal) {
|
|
57
53
|
const dirents = await withAbort(fsp.readdir(basePath, { withFileTypes: true }), signal);
|
|
54
|
+
const entries = [];
|
|
58
55
|
for (const dirent of dirents) {
|
|
59
56
|
if (!normalized.includeHidden && dirent.name.startsWith('.')) {
|
|
60
57
|
continue;
|
|
61
58
|
}
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
59
|
+
entries.push({ dirent, entryPath: path.join(basePath, dirent.name) });
|
|
60
|
+
}
|
|
61
|
+
if (!needsStats) {
|
|
62
|
+
for (const entry of entries) {
|
|
63
|
+
yield {
|
|
64
|
+
path: entry.entryPath,
|
|
65
|
+
dirent: entry.dirent,
|
|
66
|
+
};
|
|
66
67
|
}
|
|
68
|
+
return;
|
|
69
|
+
}
|
|
70
|
+
const { results, errors } = await processInParallel(entries.map((entry, index) => ({ entry, index })), async ({ entry, index }) => ({
|
|
71
|
+
index,
|
|
72
|
+
stats: await withAbort(fsp.lstat(entry.entryPath), signal),
|
|
73
|
+
}), PARALLEL_CONCURRENCY, signal);
|
|
74
|
+
if (errors.length > 0) {
|
|
75
|
+
throw errors[0]?.error ?? new Error('Failed to read entry stats');
|
|
76
|
+
}
|
|
77
|
+
const statsByIndex = new Map();
|
|
78
|
+
for (const result of results) {
|
|
79
|
+
statsByIndex.set(result.index, result.stats);
|
|
80
|
+
}
|
|
81
|
+
let index = 0;
|
|
82
|
+
for (const entry of entries) {
|
|
83
|
+
const stats = statsByIndex.get(index);
|
|
67
84
|
yield {
|
|
68
|
-
path: entryPath,
|
|
69
|
-
dirent,
|
|
85
|
+
path: entry.entryPath,
|
|
86
|
+
dirent: entry.dirent,
|
|
70
87
|
...(stats ? { stats } : {}),
|
|
71
88
|
};
|
|
89
|
+
index += 1;
|
|
72
90
|
}
|
|
73
91
|
}
|
|
74
92
|
function createEntryStream(basePath, normalized, maxDepth, needsStats, signal) {
|
|
@@ -109,10 +127,10 @@ async function resolveSymlinkTarget(entryType, includeSymlinkTargets, entryPath)
|
|
|
109
127
|
}
|
|
110
128
|
return await fsp.readlink(entryPath).catch(() => undefined);
|
|
111
129
|
}
|
|
112
|
-
function updateTotals(
|
|
113
|
-
if (
|
|
130
|
+
function updateTotals(entryType, totals) {
|
|
131
|
+
if (entryType === 'file')
|
|
114
132
|
totals.files += 1;
|
|
115
|
-
if (
|
|
133
|
+
if (entryType === 'directory')
|
|
116
134
|
totals.directories += 1;
|
|
117
135
|
}
|
|
118
136
|
function buildDirectoryEntry(basePath, entry, entryType, needsStats, symlinkTarget) {
|
|
@@ -133,7 +151,19 @@ function trackSymlink(entryType, includeSymlinkTargets, counters) {
|
|
|
133
151
|
counters.symlinksNotFollowed += 1;
|
|
134
152
|
}
|
|
135
153
|
}
|
|
136
|
-
async function isEntryAccessible(entryPath, signal, counters) {
|
|
154
|
+
async function isEntryAccessible(entryPath, entryType, basePath, signal, counters) {
|
|
155
|
+
if (entryType !== 'symlink') {
|
|
156
|
+
const normalized = normalizePath(entryPath);
|
|
157
|
+
if (!isPathWithinDirectories(normalized, [basePath])) {
|
|
158
|
+
counters.skippedInaccessible += 1;
|
|
159
|
+
return false;
|
|
160
|
+
}
|
|
161
|
+
if (isSensitivePath(entryPath, normalized)) {
|
|
162
|
+
counters.skippedInaccessible += 1;
|
|
163
|
+
return false;
|
|
164
|
+
}
|
|
165
|
+
return true;
|
|
166
|
+
}
|
|
137
167
|
try {
|
|
138
168
|
const validated = await validateExistingPathDetailed(entryPath, signal);
|
|
139
169
|
if (isSensitivePath(validated.requestedPath, validated.resolvedPath)) {
|
|
@@ -147,24 +177,27 @@ async function isEntryAccessible(entryPath, signal, counters) {
|
|
|
147
177
|
return false;
|
|
148
178
|
}
|
|
149
179
|
}
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
180
|
+
function appendEntry(entry, entryType, symlinkTarget, ctx) {
|
|
181
|
+
updateTotals(entryType, ctx.totals);
|
|
182
|
+
ctx.entries.push(buildDirectoryEntry(ctx.basePath, entry, entryType, ctx.needsStats, symlinkTarget));
|
|
183
|
+
}
|
|
184
|
+
async function enqueueAppendEntry(entry, entryType, ctx, pending, flushPending) {
|
|
185
|
+
if (!ctx.includeSymlinkTargets) {
|
|
186
|
+
appendEntry(entry, entryType, undefined, ctx);
|
|
157
187
|
return;
|
|
158
188
|
}
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
189
|
+
// Preserve the original behavior: resolve symlink targets in parallel
|
|
190
|
+
// when includeSymlinkTargets is enabled (bounded by PARALLEL_CONCURRENCY).
|
|
191
|
+
const task = (async () => {
|
|
192
|
+
const symlinkTarget = await resolveSymlinkTarget(entryType, ctx.includeSymlinkTargets, entry.path);
|
|
193
|
+
appendEntry(entry, entryType, symlinkTarget, ctx);
|
|
194
|
+
})();
|
|
195
|
+
pending.push(task);
|
|
196
|
+
if (pending.length >= PARALLEL_CONCURRENCY) {
|
|
197
|
+
await flushPending();
|
|
198
|
+
}
|
|
166
199
|
}
|
|
167
|
-
function buildSummary(entries, totals, maxDepth, truncated, stoppedReason,
|
|
200
|
+
function buildSummary(entries, totals, maxDepth, truncated, stoppedReason, counters) {
|
|
168
201
|
const baseSummary = {
|
|
169
202
|
totalEntries: entries.length,
|
|
170
203
|
entriesScanned: entries.length,
|
|
@@ -173,8 +206,8 @@ function buildSummary(entries, totals, maxDepth, truncated, stoppedReason, extra
|
|
|
173
206
|
totalDirectories: totals.directories,
|
|
174
207
|
maxDepthReached: maxDepth,
|
|
175
208
|
truncated,
|
|
176
|
-
skippedInaccessible:
|
|
177
|
-
symlinksNotFollowed:
|
|
209
|
+
skippedInaccessible: counters.skippedInaccessible,
|
|
210
|
+
symlinksNotFollowed: counters.symlinksNotFollowed,
|
|
178
211
|
};
|
|
179
212
|
return {
|
|
180
213
|
...baseSummary,
|
|
@@ -184,56 +217,53 @@ function buildSummary(entries, totals, maxDepth, truncated, stoppedReason, extra
|
|
|
184
217
|
async function collectEntries(basePath, normalized, signal, needsStats, maxDepth) {
|
|
185
218
|
const entries = [];
|
|
186
219
|
const totals = { files: 0, directories: 0 };
|
|
220
|
+
const counters = { skippedInaccessible: 0, symlinksNotFollowed: 0 };
|
|
187
221
|
let truncated = false;
|
|
188
222
|
let stoppedReason;
|
|
189
|
-
const counters = { skippedInaccessible: 0, symlinksNotFollowed: 0 };
|
|
190
223
|
const pending = [];
|
|
191
|
-
let
|
|
224
|
+
let acceptedCount = 0;
|
|
192
225
|
const stream = createEntryStream(basePath, normalized, maxDepth, needsStats, signal);
|
|
193
226
|
const flushPending = async () => {
|
|
194
227
|
if (pending.length === 0)
|
|
195
228
|
return;
|
|
196
229
|
await Promise.allSettled(pending.splice(0));
|
|
197
230
|
};
|
|
231
|
+
const appendCtx = {
|
|
232
|
+
basePath,
|
|
233
|
+
needsStats,
|
|
234
|
+
includeSymlinkTargets: normalized.includeSymlinkTargets,
|
|
235
|
+
totals,
|
|
236
|
+
entries,
|
|
237
|
+
};
|
|
198
238
|
for await (const entry of stream) {
|
|
199
|
-
const
|
|
200
|
-
if (
|
|
239
|
+
const stopReason = getStopReason(signal, acceptedCount, normalized.maxEntries);
|
|
240
|
+
if (stopReason) {
|
|
201
241
|
truncated = true;
|
|
202
|
-
stoppedReason =
|
|
242
|
+
stoppedReason = stopReason;
|
|
203
243
|
break;
|
|
204
244
|
}
|
|
205
245
|
const entryType = resolveEntryType(entry.dirent);
|
|
206
246
|
trackSymlink(entryType, normalized.includeSymlinkTargets, counters);
|
|
207
|
-
const accessible = await isEntryAccessible(entry.path, signal, counters);
|
|
247
|
+
const accessible = await isEntryAccessible(entry.path, entryType, basePath, signal, counters);
|
|
208
248
|
if (!accessible) {
|
|
209
249
|
continue;
|
|
210
250
|
}
|
|
211
|
-
|
|
212
|
-
await
|
|
251
|
+
acceptedCount += 1;
|
|
252
|
+
await enqueueAppendEntry(entry, entryType, appendCtx, pending, flushPending);
|
|
213
253
|
}
|
|
214
254
|
if (normalized.includeSymlinkTargets) {
|
|
215
255
|
await flushPending();
|
|
216
256
|
}
|
|
217
|
-
return {
|
|
218
|
-
entries,
|
|
219
|
-
totals,
|
|
220
|
-
truncated,
|
|
221
|
-
stoppedReason,
|
|
222
|
-
skippedInaccessible: counters.skippedInaccessible,
|
|
223
|
-
symlinksNotFollowed: counters.symlinksNotFollowed,
|
|
224
|
-
};
|
|
257
|
+
return { entries, totals, truncated, stoppedReason, counters };
|
|
225
258
|
}
|
|
226
259
|
async function executeListDirectory(basePath, normalized, signal) {
|
|
227
260
|
const needsStats = needsStatsForSort(normalized.sortBy);
|
|
228
261
|
const maxDepth = resolveMaxDepth(normalized);
|
|
229
|
-
const { entries, totals, truncated, stoppedReason,
|
|
262
|
+
const { entries, totals, truncated, stoppedReason, counters } = await collectEntries(basePath, normalized, signal, needsStats, maxDepth);
|
|
230
263
|
sortEntries(entries, normalized.sortBy);
|
|
231
264
|
return {
|
|
232
265
|
entries,
|
|
233
|
-
summary: buildSummary(entries, totals, maxDepth, truncated, stoppedReason,
|
|
234
|
-
skippedInaccessible,
|
|
235
|
-
symlinksNotFollowed,
|
|
236
|
-
}),
|
|
266
|
+
summary: buildSummary(entries, totals, maxDepth, truncated, stoppedReason, counters),
|
|
237
267
|
};
|
|
238
268
|
}
|
|
239
269
|
export async function listDirectory(dirPath, options = {}) {
|
|
@@ -248,4 +278,3 @@ export async function listDirectory(dirPath, options = {}) {
|
|
|
248
278
|
cleanup();
|
|
249
279
|
}
|
|
250
280
|
}
|
|
251
|
-
//# sourceMappingURL=list-directory.js.map
|
|
@@ -70,4 +70,3 @@ export interface ScanError {
|
|
|
70
70
|
export type WorkerResponse = ScanResult | ScanError;
|
|
71
71
|
export declare function searchContent(basePath: string, pattern: string, options?: SearchContentOptions): Promise<SearchContentResult>;
|
|
72
72
|
export {};
|
|
73
|
-
//# sourceMappingURL=search-content.d.ts.map
|