@j0hanz/filesystem-mcp 1.14.0 → 1.15.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/LICENSE +21 -0
- package/README.md +143 -71
- 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/config.js
CHANGED
|
@@ -1,16 +1,16 @@
|
|
|
1
1
|
export const ErrorCode = {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
2
|
+
ACCESS_DENIED: 'ACCESS_DENIED',
|
|
3
|
+
NOT_FOUND: 'NOT_FOUND',
|
|
4
|
+
NOT_FILE: 'NOT_FILE',
|
|
5
|
+
NOT_DIRECTORY: 'NOT_DIRECTORY',
|
|
6
|
+
TOO_LARGE: 'TOO_LARGE',
|
|
7
|
+
TIMEOUT: 'TIMEOUT',
|
|
8
|
+
CANCELLED: 'CANCELLED',
|
|
9
|
+
INVALID_PATTERN: 'INVALID_PATTERN',
|
|
10
|
+
INVALID_INPUT: 'INVALID_INPUT',
|
|
11
|
+
PERMISSION_DENIED: 'PERMISSION_DENIED',
|
|
12
|
+
SYMLINK_NOT_ALLOWED: 'SYMLINK_NOT_ALLOWED',
|
|
13
|
+
UNKNOWN: 'UNKNOWN',
|
|
14
14
|
};
|
|
15
15
|
const BYTE_UNIT_LABELS = ['B', 'KB', 'MB', 'GB', 'TB'];
|
|
16
16
|
export function formatBytes(bytes) {
|
package/dist/index.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import process from 'node:process';
|
|
3
|
+
import { createTimedAbortSignal } from './lib/abort.js';
|
|
3
4
|
import { DEFAULT_SEARCH_TIMEOUT_MS } from './lib/constants.js';
|
|
4
5
|
import { formatUnknownErrorMessage } from './lib/errors.js';
|
|
5
|
-
import { createTimedAbortSignal } from './lib/fs-helpers.js';
|
|
6
6
|
import { setAllowedDirectoriesResolved } from './lib/paths.js';
|
|
7
7
|
import { CliExitError, parseArgs } from './cli.js';
|
|
8
8
|
import { createServer, startHttpServer, startServer } from './server.js';
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export declare function assertNotAborted(signal?: AbortSignal, message?: string): void;
|
|
2
|
+
export declare function withAbort<T>(promise: Promise<T>, signal?: AbortSignal): Promise<T>;
|
|
3
|
+
export declare function createTimedAbortSignal(baseSignal: AbortSignal | undefined, timeoutMs?: number): {
|
|
4
|
+
signal: AbortSignal;
|
|
5
|
+
cleanup: () => void;
|
|
6
|
+
};
|
|
7
|
+
export declare function withTimedAbortSignal<T>(baseSignal: AbortSignal | undefined, timeoutMs: number | undefined, run: (signal: AbortSignal) => Promise<T>): Promise<T>;
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import { normalizeUnknownError } from './errors.js';
|
|
2
|
+
function createAbortError(message = 'Operation aborted') {
|
|
3
|
+
return new DOMException(message, 'AbortError');
|
|
4
|
+
}
|
|
5
|
+
const SHARED_NOOP_SIGNAL = new AbortController().signal;
|
|
6
|
+
function normalizeAbortReason(reason, message) {
|
|
7
|
+
if (reason instanceof Error)
|
|
8
|
+
return reason;
|
|
9
|
+
return createAbortError(message);
|
|
10
|
+
}
|
|
11
|
+
function isFiniteNumber(value) {
|
|
12
|
+
return typeof value === 'number' && Number.isFinite(value);
|
|
13
|
+
}
|
|
14
|
+
export function assertNotAborted(signal, message) {
|
|
15
|
+
if (!signal)
|
|
16
|
+
return;
|
|
17
|
+
try {
|
|
18
|
+
signal.throwIfAborted();
|
|
19
|
+
}
|
|
20
|
+
catch (reason) {
|
|
21
|
+
throw normalizeAbortReason(reason, message);
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
function getAbortError(signal, message) {
|
|
25
|
+
try {
|
|
26
|
+
signal.throwIfAborted();
|
|
27
|
+
}
|
|
28
|
+
catch (reason) {
|
|
29
|
+
return normalizeAbortReason(reason, message);
|
|
30
|
+
}
|
|
31
|
+
return createAbortError(message);
|
|
32
|
+
}
|
|
33
|
+
export function withAbort(promise, signal) {
|
|
34
|
+
if (!signal)
|
|
35
|
+
return promise;
|
|
36
|
+
signal.throwIfAborted();
|
|
37
|
+
return new Promise((resolve, reject) => {
|
|
38
|
+
const onAbort = () => {
|
|
39
|
+
reject(getAbortError(signal));
|
|
40
|
+
};
|
|
41
|
+
if (signal.aborted) {
|
|
42
|
+
onAbort();
|
|
43
|
+
return;
|
|
44
|
+
}
|
|
45
|
+
signal.addEventListener('abort', onAbort, { once: true });
|
|
46
|
+
promise.then((value) => {
|
|
47
|
+
signal.removeEventListener('abort', onAbort);
|
|
48
|
+
resolve(value);
|
|
49
|
+
}, (error) => {
|
|
50
|
+
signal.removeEventListener('abort', onAbort);
|
|
51
|
+
reject(normalizeUnknownError(error));
|
|
52
|
+
});
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
export function createTimedAbortSignal(baseSignal, timeoutMs) {
|
|
56
|
+
const timeoutSignal = isFiniteNumber(timeoutMs)
|
|
57
|
+
? AbortSignal.timeout(timeoutMs)
|
|
58
|
+
: undefined;
|
|
59
|
+
if (baseSignal && timeoutSignal) {
|
|
60
|
+
return {
|
|
61
|
+
signal: AbortSignal.any([baseSignal, timeoutSignal]),
|
|
62
|
+
cleanup: () => { },
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
if (baseSignal) {
|
|
66
|
+
return { signal: baseSignal, cleanup: () => { } };
|
|
67
|
+
}
|
|
68
|
+
if (timeoutSignal) {
|
|
69
|
+
return { signal: timeoutSignal, cleanup: () => { } };
|
|
70
|
+
}
|
|
71
|
+
return { signal: SHARED_NOOP_SIGNAL, cleanup: () => { } };
|
|
72
|
+
}
|
|
73
|
+
export async function withTimedAbortSignal(baseSignal, timeoutMs, run) {
|
|
74
|
+
const { signal, cleanup } = createTimedAbortSignal(baseSignal, timeoutMs);
|
|
75
|
+
try {
|
|
76
|
+
return await run(signal);
|
|
77
|
+
}
|
|
78
|
+
finally {
|
|
79
|
+
cleanup();
|
|
80
|
+
}
|
|
81
|
+
}
|
package/dist/lib/constants.d.ts
CHANGED
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
export declare function parseTrueEnvFlag(value: string | undefined): boolean;
|
|
2
2
|
export declare function parseEnvInt(envVar: string, defaultValue: number, min: number, max: number): number;
|
|
3
|
-
export declare const DEFAULT_LOG_LEVEL: "
|
|
3
|
+
export declare const DEFAULT_LOG_LEVEL: "error" | "debug" | "info" | "notice" | "warning" | "critical" | "alert" | "emergency";
|
|
4
4
|
export declare const DEFAULT_TASK_TTL_MS: number;
|
|
5
5
|
export declare const MAX_TASK_TTL_MS: number;
|
|
6
6
|
export declare const MAX_CONCURRENT_TASKS: number;
|
|
7
|
+
export declare const TASK_CANCEL_POLL_MS = 2000;
|
|
8
|
+
export declare const TASK_POLL_INTERVAL_MS = 100;
|
|
7
9
|
export declare const PARALLEL_CONCURRENCY: number;
|
|
8
10
|
export declare const MAX_SEARCHABLE_FILE_SIZE: number;
|
|
9
11
|
export declare const MAX_TEXT_FILE_SIZE: number;
|
package/dist/lib/constants.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { availableParallelism } from 'node:os';
|
|
2
|
+
import { Logger } from './logger.js';
|
|
2
3
|
const TRUE_ENV_VALUES = new Set(['1', 'true', 'yes', 'y', 'on']);
|
|
3
4
|
const FALSE_ENV_VALUES = new Set(['0', 'false', 'no', 'n', 'off']);
|
|
4
5
|
export function parseTrueEnvFlag(value) {
|
|
@@ -9,7 +10,7 @@ export function parseTrueEnvFlag(value) {
|
|
|
9
10
|
const KIB = 1024;
|
|
10
11
|
const MIB = 1024 * KIB;
|
|
11
12
|
function logInvalidEnvValue(envVar, value, expected, defaultValue) {
|
|
12
|
-
|
|
13
|
+
Logger.warn(`Invalid ${envVar} value: ${value} (must be ${expected}). Using default: ${String(defaultValue)}`);
|
|
13
14
|
}
|
|
14
15
|
// Helper for parsing environment variables (only used for configurable values)
|
|
15
16
|
export function parseEnvInt(envVar, defaultValue, min, max) {
|
|
@@ -66,7 +67,7 @@ function parseEnvLogLevel(envVar, defaultValue) {
|
|
|
66
67
|
if (VALID_LOG_LEVELS.includes(normalized)) {
|
|
67
68
|
return normalized;
|
|
68
69
|
}
|
|
69
|
-
|
|
70
|
+
Logger.warn(`Invalid ${envVar} value: ${value} (must be ${VALID_LOG_LEVELS.join('|')}). Using default: ${defaultValue}`);
|
|
70
71
|
return defaultValue;
|
|
71
72
|
}
|
|
72
73
|
export const DEFAULT_LOG_LEVEL = parseEnvLogLevel('FILESYSTEM_MCP_LOG_LEVEL', 'info');
|
|
@@ -74,6 +75,11 @@ export const DEFAULT_LOG_LEVEL = parseEnvLogLevel('FILESYSTEM_MCP_LOG_LEVEL', 'i
|
|
|
74
75
|
export const DEFAULT_TASK_TTL_MS = 5 * 60 * 1000;
|
|
75
76
|
export const MAX_TASK_TTL_MS = parseEnvInt('FILESYSTEM_MCP_MAX_TASK_TTL_MS', 60 * 60 * 1000, 1_000, 24 * 60 * 60 * 1000);
|
|
76
77
|
export const MAX_CONCURRENT_TASKS = parseEnvInt('FILESYSTEM_MCP_MAX_CONCURRENT_TASKS', 100, 1, 10_000);
|
|
78
|
+
// How often (ms) a background task checks the store for client cancellation.
|
|
79
|
+
export const TASK_CANCEL_POLL_MS = 2_000;
|
|
80
|
+
// Suggested poll interval (ms) returned to the SDK/client for automatic task polling.
|
|
81
|
+
// Filesystem operations complete quickly; a short interval avoids unnecessary latency.
|
|
82
|
+
export const TASK_POLL_INTERVAL_MS = 100;
|
|
77
83
|
// Auto-tuned parallelism based on CPU cores (no env override)
|
|
78
84
|
const BYTES_PER_PARALLEL_TASK = 64 * MIB;
|
|
79
85
|
const BYTES_PER_SEARCH_WORKER = 128 * MIB;
|
package/dist/lib/errors.d.ts
CHANGED
|
@@ -15,9 +15,13 @@ export declare function isTimeoutLikeError(error: unknown): boolean;
|
|
|
15
15
|
export declare class McpError extends Error {
|
|
16
16
|
code: ErrorCode;
|
|
17
17
|
path?: string | undefined;
|
|
18
|
-
details?: Record<string, unknown
|
|
19
|
-
constructor(code: ErrorCode, message: string, path?: string | undefined, details?: Record<string, unknown
|
|
18
|
+
details?: Record<string, unknown>;
|
|
19
|
+
constructor(code: ErrorCode, message: string, path?: string | undefined, details?: Record<string, unknown>, cause?: unknown);
|
|
20
|
+
static notFound(message: string, path?: string, details?: Record<string, unknown>, cause?: unknown): McpError;
|
|
21
|
+
static invalidInput(message: string, path?: string, details?: Record<string, unknown>, cause?: unknown): McpError;
|
|
22
|
+
static accessDenied(message: string, path?: string, details?: Record<string, unknown>, cause?: unknown): McpError;
|
|
23
|
+
static timeout(message: string, path?: string, details?: Record<string, unknown>, cause?: unknown): McpError;
|
|
20
24
|
}
|
|
21
25
|
export declare function createDetailedError(error: unknown, path?: string, additionalDetails?: Record<string, unknown>): DetailedError;
|
|
22
26
|
export declare function formatDetailedError(error: DetailedError): string;
|
|
23
|
-
export declare function getSuggestion(code: ErrorCode): string;
|
|
27
|
+
export declare function getSuggestion(code: ErrorCode): string | undefined;
|
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;
|