@j0hanz/filesystem-mcp 1.8.0 → 1.9.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 +635 -377
- package/dist/cli.js +2 -2
- package/dist/completions.js +2 -3
- package/dist/index.js +2 -2
- package/dist/lib/file-operations/{common.d.ts → core.d.ts} +6 -0
- package/dist/lib/file-operations/{common.js → core.js} +45 -0
- package/dist/lib/file-operations/metadata.d.ts +73 -0
- package/dist/lib/file-operations/metadata.js +889 -0
- package/dist/lib/file-operations/{search-content.d.ts → search.d.ts} +35 -2
- package/dist/lib/file-operations/{search-content.js → search.js} +413 -15
- package/dist/lib/file-operations/{glob-engine.d.ts → traversal.d.ts} +18 -1
- package/dist/lib/file-operations/{glob-engine.js → traversal.js} +25 -2
- package/dist/lib/fs-helpers.js +1 -2
- package/dist/lib/observability.js +1 -1
- package/dist/lib/{path-validation.d.ts → paths.d.ts} +3 -0
- package/dist/lib/{path-validation.js → paths.js} +105 -1
- package/dist/lib/utils.d.ts +15 -0
- package/dist/lib/utils.js +33 -0
- package/dist/resources/generated-instructions.js +6 -6
- package/dist/resources/tool-catalog.js +9 -9
- package/dist/resources/tool-info.js +3 -3
- package/dist/resources/workflows.js +10 -23
- package/dist/schemas.js +15 -15
- package/dist/server/bootstrap.d.ts +19 -1
- package/dist/server/bootstrap.js +103 -9
- package/dist/server/roots-manager.d.ts +2 -2
- package/dist/server/roots-manager.js +3 -3
- package/dist/tools/apply-patch.js +4 -3
- package/dist/tools/calculate-hash.js +4 -4
- package/dist/tools/create-directory.js +1 -1
- package/dist/tools/delete-file.js +3 -3
- package/dist/tools/diff-files.js +2 -2
- package/dist/tools/edit-file.js +14 -13
- package/dist/tools/list-directory.js +5 -7
- package/dist/tools/move-file.js +2 -1
- package/dist/tools/read-multiple.js +3 -4
- package/dist/tools/read.js +3 -3
- package/dist/tools/replace-in-files.js +8 -10
- package/dist/tools/roots.js +4 -6
- package/dist/tools/search-content.js +7 -9
- package/dist/tools/search-files.js +4 -6
- package/dist/tools/shared.d.ts +2 -1
- package/dist/tools/shared.js +2 -1
- package/dist/tools/stat-many.js +4 -3
- package/dist/tools/stat.js +4 -3
- package/dist/tools/task-support.js +1 -1
- package/dist/tools/tree.js +3 -4
- package/dist/tools/write-file.js +1 -1
- package/package.json +4 -4
- package/dist/lib/file-operations/file-info.d.ts +0 -10
- package/dist/lib/file-operations/file-info.js +0 -143
- package/dist/lib/file-operations/gitignore.d.ts +0 -6
- package/dist/lib/file-operations/gitignore.js +0 -45
- package/dist/lib/file-operations/glob-helpers.d.ts +0 -18
- package/dist/lib/file-operations/glob-helpers.js +0 -23
- package/dist/lib/file-operations/list-directory.d.ts +0 -14
- package/dist/lib/file-operations/list-directory.js +0 -252
- package/dist/lib/file-operations/read-multiple-files.d.ts +0 -25
- package/dist/lib/file-operations/read-multiple-files.js +0 -252
- package/dist/lib/file-operations/search-files.d.ts +0 -27
- package/dist/lib/file-operations/search-files.js +0 -216
- package/dist/lib/file-operations/search-matcher.d.ts +0 -10
- package/dist/lib/file-operations/search-matcher.js +0 -72
- package/dist/lib/file-operations/search-worker.d.ts +0 -2
- package/dist/lib/file-operations/search-worker.js +0 -131
- package/dist/lib/file-operations/tree.d.ts +0 -28
- package/dist/lib/file-operations/tree.js +0 -265
- package/dist/lib/option-utils.d.ts +0 -3
- package/dist/lib/option-utils.js +0 -15
- package/dist/lib/path-format.d.ts +0 -1
- package/dist/lib/path-format.js +0 -7
- package/dist/lib/path-policy.d.ts +0 -2
- package/dist/lib/path-policy.js +0 -100
- package/dist/lib/progress-reporting.d.ts +0 -11
- package/dist/lib/progress-reporting.js +0 -13
- package/dist/lib/type-guards.d.ts +0 -1
- package/dist/lib/type-guards.js +0 -3
- package/dist/server/capabilities.d.ts +0 -10
- package/dist/server/capabilities.js +0 -48
- package/dist/server/logging.d.ts +0 -7
- package/dist/server/logging.js +0 -41
- package/dist/server/types.d.ts +0 -4
- package/dist/server/types.js +0 -1
|
@@ -1,216 +0,0 @@
|
|
|
1
|
-
import * as path from 'node:path';
|
|
2
|
-
import { DEFAULT_SEARCH_MAX_FILES, DEFAULT_SEARCH_TIMEOUT_MS, } from '../constants.js';
|
|
3
|
-
import { withTimedAbortSignal } from '../fs-helpers.js';
|
|
4
|
-
import { isSensitivePath } from '../path-policy.js';
|
|
5
|
-
import { isPathWithinDirectories, normalizePath, validateExistingDirectory, validateExistingPathDetailed, } from '../path-validation.js';
|
|
6
|
-
import { reportPeriodicProgress } from '../progress-reporting.js';
|
|
7
|
-
import { compareOptionalNumberDesc, compareStringValues, isEntryAccessibleByType, needsStatsForSort, resolveEntryType, resolveStopReason, stableSortByDerivedString, withOptionalStoppedReason, } from './common.js';
|
|
8
|
-
import { isIgnoredByGitignore, loadRootGitignore } from './gitignore.js';
|
|
9
|
-
import { globEntries } from './glob-engine.js';
|
|
10
|
-
import { buildGlobOptions } from './glob-helpers.js';
|
|
11
|
-
// Internal default for find tool - not exposed to MCP users
|
|
12
|
-
const INTERNAL_MAX_RESULTS = 1000;
|
|
13
|
-
function normalizeOptions(options) {
|
|
14
|
-
const normalized = {
|
|
15
|
-
maxResults: options.maxResults ?? INTERNAL_MAX_RESULTS,
|
|
16
|
-
sortBy: options.sortBy ?? 'path',
|
|
17
|
-
maxFilesScanned: options.maxFilesScanned ?? DEFAULT_SEARCH_MAX_FILES,
|
|
18
|
-
timeoutMs: options.timeoutMs ?? DEFAULT_SEARCH_TIMEOUT_MS,
|
|
19
|
-
baseNameMatch: options.baseNameMatch ?? false,
|
|
20
|
-
skipSymlinks: options.skipSymlinks ?? true,
|
|
21
|
-
includeHidden: options.includeHidden ?? false,
|
|
22
|
-
respectGitignore: options.respectGitignore ?? false,
|
|
23
|
-
};
|
|
24
|
-
if (options.maxDepth !== undefined) {
|
|
25
|
-
normalized.maxDepth = options.maxDepth;
|
|
26
|
-
}
|
|
27
|
-
return normalized;
|
|
28
|
-
}
|
|
29
|
-
function buildSearchResult(entry, entryType, needsStats) {
|
|
30
|
-
let resolvedType = 'other';
|
|
31
|
-
if (entryType === 'directory') {
|
|
32
|
-
resolvedType = 'directory';
|
|
33
|
-
}
|
|
34
|
-
else if (entryType === 'file') {
|
|
35
|
-
resolvedType = 'file';
|
|
36
|
-
}
|
|
37
|
-
const size = needsStats && entry.stats?.isFile() ? entry.stats.size : undefined;
|
|
38
|
-
const modified = needsStats ? entry.stats?.mtime : undefined;
|
|
39
|
-
return {
|
|
40
|
-
path: entry.path,
|
|
41
|
-
type: resolvedType,
|
|
42
|
-
...(size !== undefined ? { size } : {}),
|
|
43
|
-
...(modified !== undefined ? { modified } : {}),
|
|
44
|
-
};
|
|
45
|
-
}
|
|
46
|
-
function shouldStopCollecting(state, normalized, signal) {
|
|
47
|
-
const stopReason = resolveStopReason({
|
|
48
|
-
signal,
|
|
49
|
-
current: state.filesScanned,
|
|
50
|
-
max: normalized.maxFilesScanned,
|
|
51
|
-
abortedReason: 'timeout',
|
|
52
|
-
maxReason: 'maxFiles',
|
|
53
|
-
});
|
|
54
|
-
if (stopReason !== undefined) {
|
|
55
|
-
state.truncated = true;
|
|
56
|
-
state.stoppedReason = stopReason;
|
|
57
|
-
return true;
|
|
58
|
-
}
|
|
59
|
-
return false;
|
|
60
|
-
}
|
|
61
|
-
function shouldIncludeEntry(entryType, normalized) {
|
|
62
|
-
return !normalized.skipSymlinks || entryType !== 'symlink';
|
|
63
|
-
}
|
|
64
|
-
function createCollectState() {
|
|
65
|
-
return {
|
|
66
|
-
results: [],
|
|
67
|
-
filesScanned: 0,
|
|
68
|
-
truncated: false,
|
|
69
|
-
skippedInaccessible: 0,
|
|
70
|
-
};
|
|
71
|
-
}
|
|
72
|
-
function buildSearchStream(root, pattern, excludePatterns, normalized, needsStats) {
|
|
73
|
-
const options = buildGlobOptions({
|
|
74
|
-
cwd: root,
|
|
75
|
-
pattern,
|
|
76
|
-
excludePatterns,
|
|
77
|
-
includeHidden: normalized.includeHidden,
|
|
78
|
-
baseNameMatch: normalized.baseNameMatch,
|
|
79
|
-
caseSensitiveMatch: true,
|
|
80
|
-
followSymbolicLinks: false,
|
|
81
|
-
onlyFiles: true,
|
|
82
|
-
stats: needsStats,
|
|
83
|
-
...(normalized.maxDepth !== undefined
|
|
84
|
-
? { maxDepth: normalized.maxDepth }
|
|
85
|
-
: {}),
|
|
86
|
-
});
|
|
87
|
-
return globEntries(options);
|
|
88
|
-
}
|
|
89
|
-
function buildCollectResult(state) {
|
|
90
|
-
const outcome = {
|
|
91
|
-
results: state.results,
|
|
92
|
-
filesScanned: state.filesScanned,
|
|
93
|
-
truncated: state.truncated,
|
|
94
|
-
skippedInaccessible: state.skippedInaccessible,
|
|
95
|
-
};
|
|
96
|
-
if (state.stoppedReason !== undefined) {
|
|
97
|
-
outcome.stoppedReason = state.stoppedReason;
|
|
98
|
-
}
|
|
99
|
-
return outcome;
|
|
100
|
-
}
|
|
101
|
-
function handleEntry(entry, entryType, needsStats, normalized, state) {
|
|
102
|
-
state.results.push(buildSearchResult(entry, entryType, needsStats));
|
|
103
|
-
if (state.results.length >= normalized.maxResults) {
|
|
104
|
-
state.truncated = true;
|
|
105
|
-
state.stoppedReason = 'maxResults';
|
|
106
|
-
}
|
|
107
|
-
}
|
|
108
|
-
async function collectFromStream(stream, root, rootDirectories, gitignoreMatcher, normalized, needsStats, state, signal, accessDeps, onProgress) {
|
|
109
|
-
for await (const entry of stream) {
|
|
110
|
-
if (shouldStopCollecting(state, normalized, signal))
|
|
111
|
-
break;
|
|
112
|
-
state.filesScanned++;
|
|
113
|
-
reportPeriodicProgress(onProgress, state.filesScanned, {
|
|
114
|
-
total: normalized.maxFilesScanned,
|
|
115
|
-
throttleModulo: 25,
|
|
116
|
-
});
|
|
117
|
-
if (isEntryIgnoredByGitignore(gitignoreMatcher, root, entry.path, entry.relativePath)) {
|
|
118
|
-
continue;
|
|
119
|
-
}
|
|
120
|
-
const entryType = resolveEntryType(entry.dirent);
|
|
121
|
-
if (!shouldIncludeEntry(entryType, normalized)) {
|
|
122
|
-
continue;
|
|
123
|
-
}
|
|
124
|
-
const isAccessible = await isEntryAccessibleByType(entry.path, entryType, rootDirectories, signal, accessDeps);
|
|
125
|
-
if (!isAccessible) {
|
|
126
|
-
state.skippedInaccessible++;
|
|
127
|
-
continue;
|
|
128
|
-
}
|
|
129
|
-
handleEntry(entry, entryType, needsStats, normalized, state);
|
|
130
|
-
if (state.truncated)
|
|
131
|
-
break;
|
|
132
|
-
}
|
|
133
|
-
reportPeriodicProgress(onProgress, state.filesScanned, {
|
|
134
|
-
total: normalized.maxFilesScanned,
|
|
135
|
-
throttleModulo: 25,
|
|
136
|
-
force: true,
|
|
137
|
-
});
|
|
138
|
-
}
|
|
139
|
-
function isEntryIgnoredByGitignore(matcher, root, entryPath, relativePath) {
|
|
140
|
-
if (!matcher)
|
|
141
|
-
return false;
|
|
142
|
-
return isIgnoredByGitignore(matcher, root, entryPath, relativePath ? { relativePath } : {});
|
|
143
|
-
}
|
|
144
|
-
async function collectSearchResults(root, pattern, excludePatterns, normalized, signal, onProgress) {
|
|
145
|
-
const needsStats = needsStatsForSort(normalized.sortBy);
|
|
146
|
-
const stream = buildSearchStream(root, pattern, excludePatterns, normalized, needsStats);
|
|
147
|
-
const state = createCollectState();
|
|
148
|
-
const rootDirectories = [root];
|
|
149
|
-
const accessDeps = {
|
|
150
|
-
normalizePath,
|
|
151
|
-
isPathWithinDirectories,
|
|
152
|
-
isSensitivePath,
|
|
153
|
-
validateSymlinkPath: validateExistingPathDetailed,
|
|
154
|
-
};
|
|
155
|
-
const gitignoreMatcher = normalized.respectGitignore
|
|
156
|
-
? await loadRootGitignore(root, signal)
|
|
157
|
-
: null;
|
|
158
|
-
await collectFromStream(stream, root, rootDirectories, gitignoreMatcher, normalized, needsStats, state, signal, accessDeps, onProgress);
|
|
159
|
-
return buildCollectResult(state);
|
|
160
|
-
}
|
|
161
|
-
function buildSearchSummary(results, filesScanned, truncated, stoppedReason, skippedInaccessible) {
|
|
162
|
-
const summary = {
|
|
163
|
-
matched: results.length,
|
|
164
|
-
truncated,
|
|
165
|
-
skippedInaccessible,
|
|
166
|
-
filesScanned,
|
|
167
|
-
};
|
|
168
|
-
return withOptionalStoppedReason(summary, stoppedReason);
|
|
169
|
-
}
|
|
170
|
-
function compareNameThenPath(a, b) {
|
|
171
|
-
const nameCompare = compareStringValues(a.name, b.name);
|
|
172
|
-
if (nameCompare !== 0)
|
|
173
|
-
return nameCompare;
|
|
174
|
-
return compareStringValues(a.path, b.path);
|
|
175
|
-
}
|
|
176
|
-
function comparePathThenName(a, b) {
|
|
177
|
-
const pathCompare = compareStringValues(a.path, b.path);
|
|
178
|
-
if (pathCompare !== 0)
|
|
179
|
-
return pathCompare;
|
|
180
|
-
return compareStringValues(a.name, b.name);
|
|
181
|
-
}
|
|
182
|
-
const SORT_COMPARATORS = {
|
|
183
|
-
size: (a, b) => compareOptionalNumberDesc(a.size, b.size, () => compareNameThenPath(a, b)),
|
|
184
|
-
modified: (a, b) => compareOptionalNumberDesc(a.modified?.getTime(), b.modified?.getTime(), () => compareNameThenPath(a, b)),
|
|
185
|
-
path: (a, b) => comparePathThenName(a, b),
|
|
186
|
-
name: (a, b) => compareNameThenPath(a, b),
|
|
187
|
-
};
|
|
188
|
-
export function sortSearchResults(results, sortBy) {
|
|
189
|
-
if (sortBy === 'name') {
|
|
190
|
-
stableSortByDerivedString(results, (item) => path.basename(item.path ?? ''), (left, right) => comparePathThenName(left, right));
|
|
191
|
-
return;
|
|
192
|
-
}
|
|
193
|
-
const comparator = SORT_COMPARATORS[sortBy];
|
|
194
|
-
results.sort(comparator);
|
|
195
|
-
}
|
|
196
|
-
async function runSearchFiles(root, pattern, excludePatterns, normalized, signal, onProgress) {
|
|
197
|
-
const { results, filesScanned, truncated, stoppedReason, skippedInaccessible, } = await collectSearchResults(root, pattern, excludePatterns, normalized, signal, onProgress);
|
|
198
|
-
sortSearchResults(results, normalized.sortBy);
|
|
199
|
-
return {
|
|
200
|
-
results,
|
|
201
|
-
summary: buildSearchSummary(results, filesScanned, truncated, stoppedReason, skippedInaccessible),
|
|
202
|
-
};
|
|
203
|
-
}
|
|
204
|
-
export async function searchFiles(basePath, pattern, excludePatterns = [], options = {}) {
|
|
205
|
-
const normalized = normalizeOptions(options);
|
|
206
|
-
return withTimedAbortSignal(options.signal, normalized.timeoutMs, async (signal) => {
|
|
207
|
-
const root = await validateExistingDirectory(basePath, signal);
|
|
208
|
-
const { results, summary } = await runSearchFiles(root, pattern, excludePatterns, normalized, signal, options.onProgress);
|
|
209
|
-
return {
|
|
210
|
-
basePath: root,
|
|
211
|
-
pattern,
|
|
212
|
-
results,
|
|
213
|
-
summary,
|
|
214
|
-
};
|
|
215
|
-
});
|
|
216
|
-
}
|
|
@@ -1,10 +0,0 @@
|
|
|
1
|
-
import { z } from 'zod';
|
|
2
|
-
export declare const MatcherOptionsSchema: z.ZodObject<{
|
|
3
|
-
caseSensitive: z.ZodBoolean;
|
|
4
|
-
wholeWord: z.ZodBoolean;
|
|
5
|
-
isLiteral: z.ZodBoolean;
|
|
6
|
-
}, z.core.$strict>;
|
|
7
|
-
export type MatcherOptions = z.infer<typeof MatcherOptionsSchema>;
|
|
8
|
-
export type Matcher = (line: string) => number;
|
|
9
|
-
export declare function validatePattern(pattern: string, options: MatcherOptions): void;
|
|
10
|
-
export declare function buildMatcher(pattern: string, options: MatcherOptions): Matcher;
|
|
@@ -1,72 +0,0 @@
|
|
|
1
|
-
import { z } from 'zod';
|
|
2
|
-
import RE2 from 're2';
|
|
3
|
-
import safeRegex from 'safe-regex2';
|
|
4
|
-
export const MatcherOptionsSchema = z.strictObject({
|
|
5
|
-
caseSensitive: z.boolean(),
|
|
6
|
-
wholeWord: z.boolean(),
|
|
7
|
-
isLiteral: z.boolean(),
|
|
8
|
-
});
|
|
9
|
-
function countRegexLineMatches(regex, line) {
|
|
10
|
-
regex.lastIndex = 0;
|
|
11
|
-
let count = 0;
|
|
12
|
-
while (regex.exec(line) !== null) {
|
|
13
|
-
count++;
|
|
14
|
-
if (regex.lastIndex === 0)
|
|
15
|
-
regex.lastIndex++;
|
|
16
|
-
}
|
|
17
|
-
return count;
|
|
18
|
-
}
|
|
19
|
-
function escapeLiteral(pattern) {
|
|
20
|
-
return pattern.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
21
|
-
}
|
|
22
|
-
function buildRegexPattern(pattern, options) {
|
|
23
|
-
const escaped = options.isLiteral ? escapeLiteral(pattern) : pattern;
|
|
24
|
-
return options.wholeWord ? `\\b${escaped}\\b` : escaped;
|
|
25
|
-
}
|
|
26
|
-
export function validatePattern(pattern, options) {
|
|
27
|
-
if (options.isLiteral && pattern.length === 0)
|
|
28
|
-
return;
|
|
29
|
-
if (options.isLiteral && !options.wholeWord)
|
|
30
|
-
return;
|
|
31
|
-
const final = buildRegexPattern(pattern, options);
|
|
32
|
-
if (!safeRegex(final)) {
|
|
33
|
-
throw new Error(`Potentially unsafe regular expression (ReDoS risk): ${pattern}`);
|
|
34
|
-
}
|
|
35
|
-
}
|
|
36
|
-
function buildLiteralMatcher(pattern, options) {
|
|
37
|
-
if (!options.caseSensitive) {
|
|
38
|
-
const final = escapeLiteral(pattern);
|
|
39
|
-
const regex = new RegExp(final, 'gi');
|
|
40
|
-
return (line) => countRegexLineMatches(regex, line);
|
|
41
|
-
}
|
|
42
|
-
// Fast path for case-sensitive literal
|
|
43
|
-
const needle = pattern;
|
|
44
|
-
if (needle.length === 0)
|
|
45
|
-
return () => 0;
|
|
46
|
-
return (line) => {
|
|
47
|
-
if (line.length === 0)
|
|
48
|
-
return 0;
|
|
49
|
-
let count = 0;
|
|
50
|
-
let pos = line.indexOf(needle);
|
|
51
|
-
while (pos !== -1) {
|
|
52
|
-
count++;
|
|
53
|
-
pos = line.indexOf(needle, pos + needle.length);
|
|
54
|
-
}
|
|
55
|
-
return count;
|
|
56
|
-
};
|
|
57
|
-
}
|
|
58
|
-
function buildRegexMatcher(final, caseSensitive) {
|
|
59
|
-
const regex = new RE2(final, caseSensitive ? 'g' : 'gi');
|
|
60
|
-
return (line) => countRegexLineMatches(regex, line);
|
|
61
|
-
}
|
|
62
|
-
export function buildMatcher(pattern, options) {
|
|
63
|
-
if (options.isLiteral && pattern.length === 0)
|
|
64
|
-
return () => 0;
|
|
65
|
-
if (options.isLiteral && !options.wholeWord) {
|
|
66
|
-
// fast path for simple literal search
|
|
67
|
-
return buildLiteralMatcher(pattern, options);
|
|
68
|
-
}
|
|
69
|
-
const final = buildRegexPattern(pattern, options);
|
|
70
|
-
validatePattern(pattern, options); // Re-validate to be safe
|
|
71
|
-
return buildRegexMatcher(final, options.caseSensitive);
|
|
72
|
-
}
|
|
@@ -1,131 +0,0 @@
|
|
|
1
|
-
import { parentPort, threadId, workerData } from 'node:worker_threads';
|
|
2
|
-
import { formatUnknownErrorMessage } from '../errors.js';
|
|
3
|
-
import { isProbablyBinary } from '../fs-helpers.js';
|
|
4
|
-
import { startPerfMeasure } from '../observability.js';
|
|
5
|
-
import { scanFileInWorker } from './search-content.js';
|
|
6
|
-
import { buildMatcher } from './search-matcher.js';
|
|
7
|
-
import {} from './search-matcher.js';
|
|
8
|
-
const matcherCache = new Map();
|
|
9
|
-
const MAX_MATCHER_CACHE_SIZE = 100;
|
|
10
|
-
function getMatcherCacheKey(pattern, options) {
|
|
11
|
-
const cs = options.caseSensitive ? '1' : '0';
|
|
12
|
-
const ww = options.wholeWord ? '1' : '0';
|
|
13
|
-
const lit = options.isLiteral ? '1' : '0';
|
|
14
|
-
return `${pattern}|${cs}|${ww}|${lit}`;
|
|
15
|
-
}
|
|
16
|
-
function getCachedMatcher(pattern, options) {
|
|
17
|
-
const key = getMatcherCacheKey(pattern, options);
|
|
18
|
-
const cached = matcherCache.get(key);
|
|
19
|
-
if (cached) {
|
|
20
|
-
refreshMatcherCacheEntry(key, cached);
|
|
21
|
-
return cached;
|
|
22
|
-
}
|
|
23
|
-
const matcher = buildMatcher(pattern, options);
|
|
24
|
-
refreshMatcherCacheEntry(key, matcher);
|
|
25
|
-
evictOldestMatcherIfNeeded();
|
|
26
|
-
return matcher;
|
|
27
|
-
}
|
|
28
|
-
function refreshMatcherCacheEntry(key, matcher) {
|
|
29
|
-
matcherCache.delete(key);
|
|
30
|
-
matcherCache.set(key, matcher);
|
|
31
|
-
}
|
|
32
|
-
function evictOldestMatcherIfNeeded() {
|
|
33
|
-
if (matcherCache.size <= MAX_MATCHER_CACHE_SIZE)
|
|
34
|
-
return;
|
|
35
|
-
const firstKey = matcherCache.keys().next().value;
|
|
36
|
-
if (firstKey !== undefined) {
|
|
37
|
-
matcherCache.delete(firstKey);
|
|
38
|
-
}
|
|
39
|
-
}
|
|
40
|
-
const cancelledRequests = new Set();
|
|
41
|
-
const activeRequests = new Set();
|
|
42
|
-
let shuttingDown = false;
|
|
43
|
-
function maybeFinishShutdown() {
|
|
44
|
-
if (!shuttingDown)
|
|
45
|
-
return;
|
|
46
|
-
if (activeRequests.size > 0)
|
|
47
|
-
return;
|
|
48
|
-
parentPort?.close();
|
|
49
|
-
}
|
|
50
|
-
function consumeCancelled(id) {
|
|
51
|
-
if (!cancelledRequests.has(id)) {
|
|
52
|
-
return false;
|
|
53
|
-
}
|
|
54
|
-
cancelledRequests.delete(id);
|
|
55
|
-
return true;
|
|
56
|
-
}
|
|
57
|
-
function markCancelledIfActive(id) {
|
|
58
|
-
if (activeRequests.has(id)) {
|
|
59
|
-
cancelledRequests.add(id);
|
|
60
|
-
}
|
|
61
|
-
}
|
|
62
|
-
function buildScanResponse(id, result) {
|
|
63
|
-
return {
|
|
64
|
-
type: 'result',
|
|
65
|
-
id,
|
|
66
|
-
result,
|
|
67
|
-
};
|
|
68
|
-
}
|
|
69
|
-
function buildErrorResponse(id, error) {
|
|
70
|
-
return {
|
|
71
|
-
type: 'error',
|
|
72
|
-
id,
|
|
73
|
-
error: formatUnknownErrorMessage(error),
|
|
74
|
-
};
|
|
75
|
-
}
|
|
76
|
-
async function handleScanRequest(request) {
|
|
77
|
-
const { id, resolvedPath, requestedPath, pattern, matcherOptions, scanOptions, maxMatches, } = request;
|
|
78
|
-
if (consumeCancelled(id))
|
|
79
|
-
return;
|
|
80
|
-
activeRequests.add(id);
|
|
81
|
-
const endMeasure = startPerfMeasure('searchWorker.scan', {
|
|
82
|
-
maxMatches,
|
|
83
|
-
});
|
|
84
|
-
let ok = false;
|
|
85
|
-
try {
|
|
86
|
-
const matcher = getCachedMatcher(pattern, matcherOptions);
|
|
87
|
-
const isCancelled = () => cancelledRequests.has(id);
|
|
88
|
-
const result = await scanFileInWorker(resolvedPath, requestedPath, matcher, scanOptions, maxMatches, isCancelled, isProbablyBinary);
|
|
89
|
-
if (consumeCancelled(id))
|
|
90
|
-
return;
|
|
91
|
-
parentPort?.postMessage(buildScanResponse(id, result));
|
|
92
|
-
ok = true;
|
|
93
|
-
}
|
|
94
|
-
catch (err) {
|
|
95
|
-
if (consumeCancelled(id))
|
|
96
|
-
return;
|
|
97
|
-
parentPort?.postMessage(buildErrorResponse(id, err));
|
|
98
|
-
}
|
|
99
|
-
finally {
|
|
100
|
-
activeRequests.delete(id);
|
|
101
|
-
cancelledRequests.delete(id);
|
|
102
|
-
endMeasure?.(ok);
|
|
103
|
-
maybeFinishShutdown();
|
|
104
|
-
}
|
|
105
|
-
}
|
|
106
|
-
function handleMessage(message) {
|
|
107
|
-
switch (message.type) {
|
|
108
|
-
case 'scan':
|
|
109
|
-
if (shuttingDown)
|
|
110
|
-
return;
|
|
111
|
-
void handleScanRequest(message);
|
|
112
|
-
break;
|
|
113
|
-
case 'cancel':
|
|
114
|
-
markCancelledIfActive(message.id);
|
|
115
|
-
break;
|
|
116
|
-
case 'shutdown':
|
|
117
|
-
shuttingDown = true;
|
|
118
|
-
for (const id of activeRequests) {
|
|
119
|
-
markCancelledIfActive(id);
|
|
120
|
-
}
|
|
121
|
-
maybeFinishShutdown();
|
|
122
|
-
break;
|
|
123
|
-
}
|
|
124
|
-
}
|
|
125
|
-
if (parentPort) {
|
|
126
|
-
parentPort.on('message', handleMessage);
|
|
127
|
-
const data = workerData;
|
|
128
|
-
if (data?.debug) {
|
|
129
|
-
console.error(`[SearchWorker] Started with threadId=${String(threadId)}`);
|
|
130
|
-
}
|
|
131
|
-
}
|
|
@@ -1,28 +0,0 @@
|
|
|
1
|
-
import type { EntryType } from './common.js';
|
|
2
|
-
interface TreeEntry {
|
|
3
|
-
name: string;
|
|
4
|
-
type: EntryType;
|
|
5
|
-
relativePath: string;
|
|
6
|
-
children?: TreeEntry[];
|
|
7
|
-
}
|
|
8
|
-
interface TreeOptions {
|
|
9
|
-
maxDepth?: number;
|
|
10
|
-
maxEntries?: number;
|
|
11
|
-
includeHidden?: boolean;
|
|
12
|
-
includeIgnored?: boolean;
|
|
13
|
-
timeoutMs?: number;
|
|
14
|
-
signal?: AbortSignal;
|
|
15
|
-
onProgress?: (progress: {
|
|
16
|
-
total?: number;
|
|
17
|
-
current: number;
|
|
18
|
-
}) => void;
|
|
19
|
-
}
|
|
20
|
-
interface TreeResult {
|
|
21
|
-
root: string;
|
|
22
|
-
tree: TreeEntry;
|
|
23
|
-
truncated: boolean;
|
|
24
|
-
totalEntries: number;
|
|
25
|
-
}
|
|
26
|
-
export declare function formatTreeAscii(tree: TreeEntry): string;
|
|
27
|
-
export declare function treeDirectory(dirPath: string, options?: TreeOptions): Promise<TreeResult>;
|
|
28
|
-
export {};
|