@j0hanz/filesystem-mcp 1.14.0 → 1.14.1
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 +47 -47
- package/dist/cli.js +2 -2
- package/dist/completions.js +54 -51
- package/dist/config.d.ts +13 -13
- package/dist/config.js +12 -12
- package/dist/index.js +1 -1
- package/dist/lib/abort.d.ts +7 -0
- package/dist/lib/abort.js +81 -0
- package/dist/lib/constants.d.ts +3 -1
- package/dist/lib/constants.js +8 -2
- package/dist/lib/errors.d.ts +7 -3
- package/dist/lib/errors.js +59 -39
- package/dist/lib/file-operations/core.d.ts +3 -3
- package/dist/lib/file-operations/core.js +23 -20
- package/dist/lib/file-operations/metadata.d.ts +2 -2
- package/dist/lib/file-operations/metadata.js +60 -19
- package/dist/lib/file-operations/search.js +83 -84
- package/dist/lib/file-operations/traversal.js +13 -15
- package/dist/lib/fs-helpers.d.ts +3 -10
- package/dist/lib/fs-helpers.js +20 -98
- package/dist/lib/globs.js +1 -1
- package/dist/lib/logger.d.ts +28 -0
- package/dist/lib/logger.js +91 -0
- package/dist/lib/observability.d.ts +7 -0
- package/dist/lib/observability.js +19 -9
- package/dist/lib/paths.js +55 -55
- package/dist/lib/resource-store.js +4 -4
- package/dist/lib/utils.d.ts +0 -12
- package/dist/lib/utils.js +0 -13
- package/dist/resources/generated-instructions.js +40 -31
- package/dist/resources/tool-catalog.js +32 -26
- package/dist/resources/tool-info.js +34 -29
- package/dist/resources/workflows.js +39 -18
- package/dist/resources.d.ts +1 -1
- package/dist/resources.js +4 -4
- package/dist/schemas.d.ts +66 -66
- package/dist/schemas.js +21 -44
- package/dist/server/bootstrap.d.ts +12 -11
- package/dist/server/bootstrap.js +95 -86
- package/dist/server/roots-manager.d.ts +5 -2
- package/dist/server/roots-manager.js +8 -6
- package/dist/server/task-store.d.ts +10 -0
- package/dist/server/task-store.js +73 -0
- package/dist/tools/apply-patch.js +26 -18
- package/dist/tools/calculate-hash.js +11 -22
- package/dist/tools/create-directory.js +10 -8
- package/dist/tools/delete-file.js +17 -15
- package/dist/tools/diff-files.js +15 -15
- package/dist/tools/edit-file.js +7 -4
- package/dist/tools/list-directory.js +6 -6
- package/dist/tools/move-file.js +98 -81
- package/dist/tools/read-multiple.js +4 -4
- package/dist/tools/read.js +5 -5
- package/dist/tools/replace-in-files.js +20 -23
- package/dist/tools/roots.js +1 -1
- package/dist/tools/search-content.js +32 -41
- package/dist/tools/search-files.js +54 -41
- package/dist/tools/shared.d.ts +3 -0
- package/dist/tools/shared.js +70 -28
- package/dist/tools/stat-many.js +11 -7
- package/dist/tools/stat.js +4 -4
- package/dist/tools/task-support.d.ts +10 -9
- package/dist/tools/task-support.js +94 -23
- package/dist/tools/tree.js +3 -3
- package/dist/tools/write-file.js +10 -7
- package/package.json +9 -9
package/dist/lib/errors.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { constants as osConstants } from 'node:os';
|
|
2
2
|
import { getSystemErrorMap, getSystemErrorName, inspect } from 'node:util';
|
|
3
3
|
import { ErrorCode, joinLines } from '../config.js';
|
|
4
|
+
import { getTraceContext } from './observability.js';
|
|
4
5
|
export { ErrorCode };
|
|
5
6
|
function isNativeError(error) {
|
|
6
7
|
const candidate = Error;
|
|
@@ -100,20 +101,20 @@ export function normalizeUnknownError(error) {
|
|
|
100
101
|
: new Error(formatUnknownErrorMessage(error));
|
|
101
102
|
}
|
|
102
103
|
const NODE_ERROR_CODE_MAP = {
|
|
103
|
-
ENOENT: ErrorCode.
|
|
104
|
-
EACCES: ErrorCode.
|
|
105
|
-
EPERM: ErrorCode.
|
|
106
|
-
ENOTDIR: ErrorCode.
|
|
107
|
-
EISDIR: ErrorCode.
|
|
108
|
-
ELOOP: ErrorCode.
|
|
109
|
-
ENAMETOOLONG: ErrorCode.
|
|
110
|
-
ETIMEDOUT: ErrorCode.
|
|
111
|
-
EMFILE: ErrorCode.
|
|
112
|
-
ENFILE: ErrorCode.
|
|
113
|
-
EBUSY: ErrorCode.
|
|
114
|
-
ENOTEMPTY: ErrorCode.
|
|
115
|
-
EEXIST: ErrorCode.
|
|
116
|
-
EINVAL: ErrorCode.
|
|
104
|
+
ENOENT: ErrorCode.NOT_FOUND,
|
|
105
|
+
EACCES: ErrorCode.PERMISSION_DENIED,
|
|
106
|
+
EPERM: ErrorCode.PERMISSION_DENIED,
|
|
107
|
+
ENOTDIR: ErrorCode.NOT_DIRECTORY,
|
|
108
|
+
EISDIR: ErrorCode.NOT_FILE,
|
|
109
|
+
ELOOP: ErrorCode.SYMLINK_NOT_ALLOWED,
|
|
110
|
+
ENAMETOOLONG: ErrorCode.INVALID_INPUT,
|
|
111
|
+
ETIMEDOUT: ErrorCode.TIMEOUT,
|
|
112
|
+
EMFILE: ErrorCode.TIMEOUT,
|
|
113
|
+
ENFILE: ErrorCode.TIMEOUT,
|
|
114
|
+
EBUSY: ErrorCode.PERMISSION_DENIED,
|
|
115
|
+
ENOTEMPTY: ErrorCode.NOT_DIRECTORY,
|
|
116
|
+
EEXIST: ErrorCode.INVALID_INPUT,
|
|
117
|
+
EINVAL: ErrorCode.INVALID_INPUT,
|
|
117
118
|
};
|
|
118
119
|
function isKnownNodeErrorCode(code) {
|
|
119
120
|
return code in NODE_ERROR_CODE_MAP;
|
|
@@ -169,24 +170,39 @@ export class McpError extends Error {
|
|
|
169
170
|
super(message, { cause });
|
|
170
171
|
this.code = code;
|
|
171
172
|
this.path = path;
|
|
172
|
-
this.details = details;
|
|
173
173
|
this.name = 'McpError';
|
|
174
174
|
Object.setPrototypeOf(this, McpError.prototype);
|
|
175
|
+
const trace = getTraceContext();
|
|
176
|
+
if (trace?.traceparent || details) {
|
|
177
|
+
this.details = { ...trace, ...details };
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
static notFound(message, path, details, cause) {
|
|
181
|
+
return new McpError(ErrorCode.NOT_FOUND, message, path, details, cause);
|
|
182
|
+
}
|
|
183
|
+
static invalidInput(message, path, details, cause) {
|
|
184
|
+
return new McpError(ErrorCode.INVALID_INPUT, message, path, details, cause);
|
|
185
|
+
}
|
|
186
|
+
static accessDenied(message, path, details, cause) {
|
|
187
|
+
return new McpError(ErrorCode.ACCESS_DENIED, message, path, details, cause);
|
|
188
|
+
}
|
|
189
|
+
static timeout(message, path, details, cause) {
|
|
190
|
+
return new McpError(ErrorCode.TIMEOUT, message, path, details, cause);
|
|
175
191
|
}
|
|
176
192
|
}
|
|
177
193
|
const ERROR_SUGGESTIONS = {
|
|
178
|
-
[ErrorCode.
|
|
179
|
-
[ErrorCode.
|
|
180
|
-
[ErrorCode.
|
|
181
|
-
[ErrorCode.
|
|
182
|
-
[ErrorCode.
|
|
183
|
-
[ErrorCode.
|
|
184
|
-
[ErrorCode.
|
|
185
|
-
[ErrorCode.
|
|
186
|
-
[ErrorCode.
|
|
187
|
-
[ErrorCode.
|
|
188
|
-
[ErrorCode.
|
|
189
|
-
[ErrorCode.
|
|
194
|
+
[ErrorCode.ACCESS_DENIED]: 'Run roots to list allowed directories.',
|
|
195
|
+
[ErrorCode.NOT_FOUND]: 'Run ls or find to verify the path.',
|
|
196
|
+
[ErrorCode.NOT_FILE]: 'Target is a directory, not a file.',
|
|
197
|
+
[ErrorCode.NOT_DIRECTORY]: 'Target is a file, not a directory.',
|
|
198
|
+
[ErrorCode.TOO_LARGE]: 'Use head/tail or line ranges to read partially.',
|
|
199
|
+
[ErrorCode.TIMEOUT]: 'Reduce scope, depth, or maxResults.',
|
|
200
|
+
[ErrorCode.CANCELLED]: undefined,
|
|
201
|
+
[ErrorCode.INVALID_PATTERN]: 'Check syntax and escape special characters.',
|
|
202
|
+
[ErrorCode.INVALID_INPUT]: undefined,
|
|
203
|
+
[ErrorCode.PERMISSION_DENIED]: 'Check OS file permissions.',
|
|
204
|
+
[ErrorCode.SYMLINK_NOT_ALLOWED]: 'Symlink escapes allowed directories.',
|
|
205
|
+
[ErrorCode.UNKNOWN]: undefined,
|
|
190
206
|
};
|
|
191
207
|
const NOT_FOUND_PATTERNS = [
|
|
192
208
|
'no such file or directory',
|
|
@@ -207,16 +223,16 @@ function classifyMessageError(error) {
|
|
|
207
223
|
const message = isNativeError(error) ? error.message : String(error);
|
|
208
224
|
const lower = message.toLowerCase();
|
|
209
225
|
if (messageIncludesAny(lower, NOT_FOUND_PATTERNS)) {
|
|
210
|
-
return ErrorCode.
|
|
226
|
+
return ErrorCode.NOT_FOUND;
|
|
211
227
|
}
|
|
212
228
|
if (messageIncludesAny(lower, PERMISSION_DENIED_PATTERNS)) {
|
|
213
|
-
return ErrorCode.
|
|
229
|
+
return ErrorCode.PERMISSION_DENIED;
|
|
214
230
|
}
|
|
215
231
|
if (lower.includes('not a directory')) {
|
|
216
|
-
return ErrorCode.
|
|
232
|
+
return ErrorCode.NOT_DIRECTORY;
|
|
217
233
|
}
|
|
218
234
|
if (lower.includes('is a directory')) {
|
|
219
|
-
return ErrorCode.
|
|
235
|
+
return ErrorCode.NOT_FILE;
|
|
220
236
|
}
|
|
221
237
|
return undefined;
|
|
222
238
|
}
|
|
@@ -225,16 +241,16 @@ function classifyError(error) {
|
|
|
225
241
|
let fallbackCode;
|
|
226
242
|
const terminalCode = walkErrorChain(error, (candidate) => {
|
|
227
243
|
if (isAbortErrorSingle(candidate)) {
|
|
228
|
-
return ErrorCode.
|
|
244
|
+
return ErrorCode.CANCELLED;
|
|
229
245
|
}
|
|
230
246
|
if (timeoutCode === undefined && isTimeoutErrorSingle(candidate)) {
|
|
231
|
-
timeoutCode = ErrorCode.
|
|
247
|
+
timeoutCode = ErrorCode.TIMEOUT;
|
|
232
248
|
}
|
|
233
249
|
fallbackCode ??=
|
|
234
250
|
getDirectErrorCode(candidate) ?? classifyMessageError(candidate);
|
|
235
251
|
return undefined;
|
|
236
252
|
});
|
|
237
|
-
return terminalCode ?? timeoutCode ?? fallbackCode ?? ErrorCode.
|
|
253
|
+
return terminalCode ?? timeoutCode ?? fallbackCode ?? ErrorCode.UNKNOWN;
|
|
238
254
|
}
|
|
239
255
|
export function createDetailedError(error, path, additionalDetails) {
|
|
240
256
|
const message = formatUnknownErrorMessage(error);
|
|
@@ -242,7 +258,11 @@ export function createDetailedError(error, path, additionalDetails) {
|
|
|
242
258
|
const suggestion = ERROR_SUGGESTIONS[code];
|
|
243
259
|
const resolvedPath = resolveErrorPath(error, path);
|
|
244
260
|
const details = mergeErrorDetails(error, additionalDetails);
|
|
245
|
-
const result = {
|
|
261
|
+
const result = {
|
|
262
|
+
code,
|
|
263
|
+
message,
|
|
264
|
+
...(suggestion ? { suggestion } : {}),
|
|
265
|
+
};
|
|
246
266
|
if (resolvedPath)
|
|
247
267
|
result.path = resolvedPath;
|
|
248
268
|
if (details)
|
|
@@ -267,12 +287,12 @@ function mergeErrorDetails(error, additionalDetails) {
|
|
|
267
287
|
return mergedDetails;
|
|
268
288
|
}
|
|
269
289
|
export function formatDetailedError(error) {
|
|
270
|
-
const lines = [
|
|
271
|
-
if (error.path) {
|
|
272
|
-
lines.push(
|
|
290
|
+
const lines = [`${error.code}: ${error.message}`];
|
|
291
|
+
if (error.path && !error.message.includes(error.path)) {
|
|
292
|
+
lines.push(error.path);
|
|
273
293
|
}
|
|
274
294
|
if (error.suggestion) {
|
|
275
|
-
lines.push(
|
|
295
|
+
lines.push(error.suggestion);
|
|
276
296
|
}
|
|
277
297
|
return joinLines(lines);
|
|
278
298
|
}
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { type Ignore } from 'ignore';
|
|
2
2
|
export declare function needsStatsForSort(sortBy: string): boolean;
|
|
3
|
-
export declare function withOptionalStoppedReason<T extends object, R extends string>(summary: T, stoppedReason: R | undefined): T
|
|
4
|
-
stoppedReason
|
|
5
|
-
}
|
|
3
|
+
export declare function withOptionalStoppedReason<T extends object, R extends string>(summary: T, stoppedReason: R | undefined): T & {
|
|
4
|
+
stoppedReason?: R;
|
|
5
|
+
};
|
|
6
6
|
export interface DirentLike {
|
|
7
7
|
isDirectory(): boolean;
|
|
8
8
|
isFile(): boolean;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import
|
|
2
|
-
import
|
|
1
|
+
import { readFile } from 'node:fs/promises';
|
|
2
|
+
import { join, relative } from 'node:path';
|
|
3
3
|
import ignore, {} from 'ignore';
|
|
4
4
|
import { isNodeError } from '../errors.js';
|
|
5
5
|
import { toPosixPath } from '../paths.js';
|
|
@@ -40,7 +40,8 @@ export function compareOptionalNumberDesc(left, right, tieBreak) {
|
|
|
40
40
|
}
|
|
41
41
|
export function stableSortByDerivedString(items, derive, tieBreak) {
|
|
42
42
|
const decorated = [];
|
|
43
|
-
|
|
43
|
+
const len = items.length;
|
|
44
|
+
for (let index = 0; index < len; index++) {
|
|
44
45
|
const item = items[index];
|
|
45
46
|
if (item === undefined)
|
|
46
47
|
continue;
|
|
@@ -59,7 +60,8 @@ export function stableSortByDerivedString(items, derive, tieBreak) {
|
|
|
59
60
|
return tiedCompare;
|
|
60
61
|
return left.index - right.index;
|
|
61
62
|
});
|
|
62
|
-
|
|
63
|
+
const decoratedLen = decorated.length;
|
|
64
|
+
for (let index = 0; index < decoratedLen; index++) {
|
|
63
65
|
const entry = decorated[index];
|
|
64
66
|
if (!entry)
|
|
65
67
|
continue;
|
|
@@ -74,13 +76,14 @@ export function applyIndexedValues(output, results) {
|
|
|
74
76
|
}
|
|
75
77
|
}
|
|
76
78
|
export function applyIndexedErrors(options) {
|
|
77
|
-
|
|
78
|
-
|
|
79
|
+
const { output, errors, resolveIndex, buildValue } = options;
|
|
80
|
+
for (const failure of errors) {
|
|
81
|
+
const resolvedIndex = resolveIndex(failure.index);
|
|
79
82
|
if (resolvedIndex === undefined)
|
|
80
83
|
continue;
|
|
81
|
-
if (resolvedIndex < 0 || resolvedIndex >=
|
|
84
|
+
if (resolvedIndex < 0 || resolvedIndex >= output.length)
|
|
82
85
|
continue;
|
|
83
|
-
|
|
86
|
+
output[resolvedIndex] = buildValue(resolvedIndex, failure.error);
|
|
84
87
|
}
|
|
85
88
|
}
|
|
86
89
|
export async function isEntryAccessibleByType(entryPath, entryType, rootDirectories, signal, deps) {
|
|
@@ -101,8 +104,9 @@ export async function isEntryAccessibleByType(entryPath, entryType, rootDirector
|
|
|
101
104
|
}
|
|
102
105
|
function parseGitignoreLines(contents) {
|
|
103
106
|
const lines = [];
|
|
104
|
-
|
|
105
|
-
|
|
107
|
+
const parts = contents.split(/\r?\n/u);
|
|
108
|
+
for (const part of parts) {
|
|
109
|
+
const trimmed = part.trim();
|
|
106
110
|
if (trimmed.length > 0) {
|
|
107
111
|
lines.push(trimmed);
|
|
108
112
|
}
|
|
@@ -110,13 +114,15 @@ function parseGitignoreLines(contents) {
|
|
|
110
114
|
return lines;
|
|
111
115
|
}
|
|
112
116
|
export async function loadRootGitignore(root, signal) {
|
|
113
|
-
const gitignorePath =
|
|
114
|
-
let contents;
|
|
117
|
+
const gitignorePath = join(root, '.gitignore');
|
|
115
118
|
try {
|
|
116
|
-
contents = await
|
|
119
|
+
const contents = await readFile(gitignorePath, {
|
|
117
120
|
encoding: 'utf-8',
|
|
118
121
|
signal,
|
|
119
122
|
});
|
|
123
|
+
const matcher = ignore();
|
|
124
|
+
matcher.add(parseGitignoreLines(contents));
|
|
125
|
+
return matcher;
|
|
120
126
|
}
|
|
121
127
|
catch (error) {
|
|
122
128
|
if (isNodeError(error) && error.code === 'ENOENT') {
|
|
@@ -124,16 +130,13 @@ export async function loadRootGitignore(root, signal) {
|
|
|
124
130
|
}
|
|
125
131
|
throw error;
|
|
126
132
|
}
|
|
127
|
-
const matcher = ignore();
|
|
128
|
-
matcher.add(parseGitignoreLines(contents));
|
|
129
|
-
return matcher;
|
|
130
133
|
}
|
|
131
134
|
export function isIgnoredByGitignore(matcher, root, absolutePath, options = {}) {
|
|
132
|
-
let
|
|
133
|
-
|
|
134
|
-
if (
|
|
135
|
+
let { relativePath } = options;
|
|
136
|
+
relativePath ??= relative(root, absolutePath);
|
|
137
|
+
if (relativePath.length === 0)
|
|
135
138
|
return false;
|
|
136
|
-
const normalized = toPosixPath(
|
|
139
|
+
const normalized = toPosixPath(relativePath);
|
|
137
140
|
if (options.isDirectory) {
|
|
138
141
|
return matcher.ignores(normalized.endsWith('/') ? normalized : `${normalized}/`);
|
|
139
142
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { FileInfo, GetMultipleFileInfoResult, ListDirectoryResult } from '../../config.js';
|
|
2
|
-
import type
|
|
2
|
+
import { type EntryType } from './core.js';
|
|
3
3
|
interface FileInfoOptions {
|
|
4
4
|
includeMimeType?: boolean | undefined;
|
|
5
5
|
signal?: AbortSignal | undefined;
|
|
@@ -60,7 +60,7 @@ interface ReadMultipleResult {
|
|
|
60
60
|
endLine?: number;
|
|
61
61
|
linesRead?: number;
|
|
62
62
|
hasMoreLines?: boolean;
|
|
63
|
-
error?:
|
|
63
|
+
error?: Error;
|
|
64
64
|
}
|
|
65
65
|
interface ReadMultipleOptions {
|
|
66
66
|
encoding?: BufferEncoding;
|
|
@@ -1,12 +1,53 @@
|
|
|
1
|
-
import
|
|
2
|
-
import
|
|
1
|
+
import { lstat, readdir, readlink, stat, } from 'node:fs';
|
|
2
|
+
import { basename, join, parse, relative } from 'node:path';
|
|
3
|
+
import { assertNotAborted, withAbort, withTimedAbortSignal } from '../abort.js';
|
|
3
4
|
import { DEFAULT_EXCLUDE_PATTERNS, DEFAULT_LIST_MAX_ENTRIES, DEFAULT_MAX_DEPTH, DEFAULT_READ_MANY_MAX_TOTAL_SIZE, DEFAULT_SEARCH_TIMEOUT_MS, getMimeType, MAX_TEXT_FILE_SIZE, PARALLEL_CONCURRENCY, } from '../constants.js';
|
|
4
5
|
import { isAbortError } from '../errors.js';
|
|
5
|
-
import {
|
|
6
|
+
import { getFileType, isHidden, processInParallel, readFile, readFileWithStats, } from '../fs-helpers.js';
|
|
6
7
|
import { assertSafeGlobPattern } from '../globs.js';
|
|
7
8
|
import { assertAllowedFileAccess, isPathWithinDirectories, isSensitivePath, normalizePath, toPosixPath, validateExistingDirectory, validateExistingPath, validateExistingPathDetailed, } from '../paths.js';
|
|
8
9
|
import { applyIndexedErrors, applyIndexedValues, isEntryAccessibleByType, isIgnoredByGitignore, loadRootGitignore, needsStatsForSort, resolveEntryType, resolveStopReason, withOptionalStoppedReason, } from './core.js';
|
|
9
10
|
import { globEntries } from './traversal.js';
|
|
11
|
+
function statAsync(filePath) {
|
|
12
|
+
return new Promise((resolve, reject) => {
|
|
13
|
+
stat(filePath, (err, stats) => {
|
|
14
|
+
if (err)
|
|
15
|
+
reject(err);
|
|
16
|
+
else
|
|
17
|
+
resolve(stats);
|
|
18
|
+
});
|
|
19
|
+
});
|
|
20
|
+
}
|
|
21
|
+
function readlinkAsync(filePath) {
|
|
22
|
+
return new Promise((resolve, reject) => {
|
|
23
|
+
readlink(filePath, (err, linkString) => {
|
|
24
|
+
if (err)
|
|
25
|
+
reject(err);
|
|
26
|
+
else
|
|
27
|
+
resolve(linkString);
|
|
28
|
+
});
|
|
29
|
+
});
|
|
30
|
+
}
|
|
31
|
+
function readdirAsync(dirPath, options) {
|
|
32
|
+
return new Promise((resolve, reject) => {
|
|
33
|
+
readdir(dirPath, options, (err, files) => {
|
|
34
|
+
if (err)
|
|
35
|
+
reject(err);
|
|
36
|
+
else
|
|
37
|
+
resolve(files);
|
|
38
|
+
});
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
function lstatAsync(filePath) {
|
|
42
|
+
return new Promise((resolve, reject) => {
|
|
43
|
+
lstat(filePath, (err, stats) => {
|
|
44
|
+
if (err)
|
|
45
|
+
reject(err);
|
|
46
|
+
else
|
|
47
|
+
resolve(stats);
|
|
48
|
+
});
|
|
49
|
+
});
|
|
50
|
+
}
|
|
10
51
|
const ACCESS_DEPS = {
|
|
11
52
|
normalizePath,
|
|
12
53
|
isPathWithinDirectories,
|
|
@@ -49,7 +90,7 @@ function buildFileInfoResult(name, requestedPath, isSymlink, stats, mimeType, sy
|
|
|
49
90
|
async function getSymlinkTarget(pathToRead, signal) {
|
|
50
91
|
assertNotAborted(signal);
|
|
51
92
|
try {
|
|
52
|
-
return await withAbort(
|
|
93
|
+
return await withAbort(readlinkAsync(pathToRead), signal);
|
|
53
94
|
}
|
|
54
95
|
catch (error) {
|
|
55
96
|
if (isAbortError(error))
|
|
@@ -62,14 +103,14 @@ export async function getFileInfo(filePath, options = {}) {
|
|
|
62
103
|
assertNotAborted(signal);
|
|
63
104
|
const { requestedPath, resolvedPath, isSymlink } = await validateExistingPathDetailed(filePath, signal);
|
|
64
105
|
assertAllowedFileAccess(requestedPath, resolvedPath);
|
|
65
|
-
const { base: name, ext: rawExt } =
|
|
106
|
+
const { base: name, ext: rawExt } = parse(requestedPath);
|
|
66
107
|
const ext = rawExt.toLowerCase();
|
|
67
108
|
const includeMimeType = options.includeMimeType !== false;
|
|
68
109
|
const mimeType = includeMimeType && ext.length > 0 ? getMimeType(ext) : undefined;
|
|
69
110
|
const symlinkTarget = isSymlink
|
|
70
111
|
? await getSymlinkTarget(requestedPath, signal)
|
|
71
112
|
: undefined;
|
|
72
|
-
const stats = await withAbort(
|
|
113
|
+
const stats = await withAbort(statAsync(resolvedPath), signal);
|
|
73
114
|
return buildFileInfoResult(name, requestedPath, isSymlink, stats, mimeType, symlinkTarget);
|
|
74
115
|
}
|
|
75
116
|
function buildEmptyResult() {
|
|
@@ -131,7 +172,7 @@ export async function getMultipleFileInfo(paths, options = {}) {
|
|
|
131
172
|
: undefined,
|
|
132
173
|
buildValue: (resolvedIndex, error) => ({
|
|
133
174
|
path: paths[resolvedIndex] ?? UNKNOWN_PATH,
|
|
134
|
-
error
|
|
175
|
+
error,
|
|
135
176
|
}),
|
|
136
177
|
});
|
|
137
178
|
return {
|
|
@@ -168,12 +209,12 @@ function resolveMaxDepth(normalized) {
|
|
|
168
209
|
return normalized.pattern ? normalized.maxDepth : 1;
|
|
169
210
|
}
|
|
170
211
|
async function* readDirectoryEntries(basePath, normalized, needsStats, signal) {
|
|
171
|
-
const dirents = await withAbort(
|
|
212
|
+
const dirents = await withAbort(readdirAsync(basePath, { withFileTypes: true }), signal);
|
|
172
213
|
if (!needsStats) {
|
|
173
214
|
for (const dirent of dirents) {
|
|
174
215
|
if (!normalized.includeHidden && isHidden(dirent.name))
|
|
175
216
|
continue;
|
|
176
|
-
yield { path:
|
|
217
|
+
yield { path: join(basePath, dirent.name), dirent };
|
|
177
218
|
}
|
|
178
219
|
return;
|
|
179
220
|
}
|
|
@@ -181,12 +222,12 @@ async function* readDirectoryEntries(basePath, normalized, needsStats, signal) {
|
|
|
181
222
|
for (const dirent of dirents) {
|
|
182
223
|
if (!normalized.includeHidden && isHidden(dirent.name))
|
|
183
224
|
continue;
|
|
184
|
-
filtered.push({ dirent, entryPath:
|
|
225
|
+
filtered.push({ dirent, entryPath: join(basePath, dirent.name) });
|
|
185
226
|
}
|
|
186
227
|
const { results, errors } = await processInParallel(filtered, async ({ entryPath, dirent }) => ({
|
|
187
228
|
path: entryPath,
|
|
188
229
|
dirent,
|
|
189
|
-
stats: await withAbort(
|
|
230
|
+
stats: await withAbort(lstatAsync(entryPath), signal),
|
|
190
231
|
}), PARALLEL_CONCURRENCY, signal);
|
|
191
232
|
if (errors.length > 0) {
|
|
192
233
|
throw errors[0]?.error ?? new Error('Failed to read entry stats');
|
|
@@ -216,13 +257,13 @@ function shouldUseFastPath(normalized, maxDepth) {
|
|
|
216
257
|
maxDepth === 1);
|
|
217
258
|
}
|
|
218
259
|
function resolveRelativePath(basePath, entryPath) {
|
|
219
|
-
return
|
|
260
|
+
return relative(basePath, entryPath) || basename(entryPath);
|
|
220
261
|
}
|
|
221
262
|
async function resolveSymlinkTarget(entryType, includeSymlinkTargets, entryPath) {
|
|
222
263
|
if (entryType !== 'symlink' || !includeSymlinkTargets) {
|
|
223
264
|
return undefined;
|
|
224
265
|
}
|
|
225
|
-
return
|
|
266
|
+
return readlinkAsync(entryPath).catch(() => undefined);
|
|
226
267
|
}
|
|
227
268
|
function updateTotals(entryType, totals) {
|
|
228
269
|
if (entryType === 'file')
|
|
@@ -234,7 +275,7 @@ function buildDirectoryEntry(basePath, entry, entryType, needsStats, symlinkTarg
|
|
|
234
275
|
const size = needsStats && entry.stats?.isFile() ? entry.stats.size : undefined;
|
|
235
276
|
const modified = needsStats ? entry.stats?.mtime : undefined;
|
|
236
277
|
return {
|
|
237
|
-
name:
|
|
278
|
+
name: basename(entry.path),
|
|
238
279
|
path: entry.path,
|
|
239
280
|
relativePath: resolveRelativePath(basePath, entry.path),
|
|
240
281
|
type: entryType,
|
|
@@ -423,7 +464,7 @@ async function resolveTreeEntry(entry, root, rootDirectories, gitignoreMatcher,
|
|
|
423
464
|
return null;
|
|
424
465
|
}
|
|
425
466
|
const relativePosix = toPosixPath(resolveRelativePath(root, entry.path));
|
|
426
|
-
const name =
|
|
467
|
+
const name = basename(entry.path);
|
|
427
468
|
return { type, relativePosix, name };
|
|
428
469
|
}
|
|
429
470
|
function upsertChildNode(parent, nodeByPath, resolved, childPathIndexByParent) {
|
|
@@ -523,7 +564,7 @@ export async function treeDirectory(dirPath, options = {}) {
|
|
|
523
564
|
? null
|
|
524
565
|
: await loadRootGitignore(root, signal);
|
|
525
566
|
const rootNode = {
|
|
526
|
-
name:
|
|
567
|
+
name: basename(root) || root,
|
|
527
568
|
type: 'directory',
|
|
528
569
|
relativePath: '.',
|
|
529
570
|
children: [],
|
|
@@ -675,7 +716,7 @@ function resolveNormalizedReadOptions(options) {
|
|
|
675
716
|
}
|
|
676
717
|
async function validateFile(filePath, index, signal) {
|
|
677
718
|
const validPath = await validateExistingPath(filePath, signal);
|
|
678
|
-
const stats = await withAbort(
|
|
719
|
+
const stats = await withAbort(statAsync(validPath), signal);
|
|
679
720
|
return { filePath, index, validPath, stats };
|
|
680
721
|
}
|
|
681
722
|
function markRemainingSkipped(startIndex, total, skippedBudget) {
|
|
@@ -810,7 +851,7 @@ function applySkippedBudget(output, skippedBudget, filePaths, maxTotalSize) {
|
|
|
810
851
|
continue;
|
|
811
852
|
output[index] = {
|
|
812
853
|
path: filePath,
|
|
813
|
-
error: `Skipped: combined estimated read would exceed maxTotalSize (${maxTotalSize} bytes)
|
|
854
|
+
error: new Error(`Skipped: combined estimated read would exceed maxTotalSize (${maxTotalSize} bytes)`),
|
|
814
855
|
};
|
|
815
856
|
}
|
|
816
857
|
}
|
|
@@ -829,7 +870,7 @@ export async function readMultipleFiles(filePaths, options = {}) {
|
|
|
829
870
|
resolveIndex: (failureIndex) => resolveErrorOriginalIndex(failureIndex, filesToProcess, filePaths.length),
|
|
830
871
|
buildValue: (resolvedIndex, error) => ({
|
|
831
872
|
path: filePaths[resolvedIndex] ?? UNKNOWN_PATH,
|
|
832
|
-
error
|
|
873
|
+
error,
|
|
833
874
|
}),
|
|
834
875
|
});
|
|
835
876
|
applySkippedBudget(output, skippedBudget, filePaths, normalized.maxTotalSize);
|