@j0hanz/filesystem-mcp 1.19.0 → 2.0.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 +333 -838
- package/dist/cli-help.d.ts +3 -0
- package/dist/cli-help.d.ts.map +1 -0
- package/dist/cli-help.js +169 -0
- package/dist/cli-help.js.map +1 -0
- package/dist/cli.d.ts +28 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +168 -123
- package/dist/cli.js.map +1 -0
- package/dist/core/concurrency.d.ts +70 -0
- package/dist/core/concurrency.d.ts.map +1 -0
- package/dist/core/concurrency.js +173 -0
- package/dist/core/concurrency.js.map +1 -0
- package/dist/core/config.d.ts +19 -0
- package/dist/core/config.d.ts.map +1 -0
- package/dist/core/config.js +11 -0
- package/dist/core/config.js.map +1 -0
- package/dist/core/cursor.d.ts +23 -0
- package/dist/core/cursor.d.ts.map +1 -0
- package/dist/core/cursor.js +62 -0
- package/dist/core/cursor.js.map +1 -0
- package/dist/core/errors.d.ts +96 -0
- package/dist/core/errors.d.ts.map +1 -0
- package/dist/core/errors.js +356 -0
- package/dist/core/errors.js.map +1 -0
- package/dist/core/file-uri.d.ts +24 -0
- package/dist/core/file-uri.d.ts.map +1 -0
- package/dist/core/file-uri.js +52 -0
- package/dist/core/file-uri.js.map +1 -0
- package/dist/core/fmt.d.ts +38 -0
- package/dist/core/fmt.d.ts.map +1 -0
- package/dist/core/fmt.js +109 -0
- package/dist/core/fmt.js.map +1 -0
- package/dist/core/fs.d.ts +108 -0
- package/dist/core/fs.d.ts.map +1 -0
- package/dist/core/fs.js +243 -0
- package/dist/core/fs.js.map +1 -0
- package/dist/core/glob.d.ts +41 -0
- package/dist/core/glob.d.ts.map +1 -0
- package/dist/core/glob.js +399 -0
- package/dist/core/glob.js.map +1 -0
- package/dist/core/input-required.d.ts +147 -0
- package/dist/core/input-required.d.ts.map +1 -0
- package/dist/core/input-required.js +261 -0
- package/dist/core/input-required.js.map +1 -0
- package/dist/core/mime.d.ts +15 -0
- package/dist/core/mime.d.ts.map +1 -0
- package/dist/core/mime.js +188 -0
- package/dist/core/mime.js.map +1 -0
- package/dist/core/observability.d.ts +10 -0
- package/dist/core/observability.d.ts.map +1 -0
- package/dist/core/observability.js +76 -0
- package/dist/core/observability.js.map +1 -0
- package/dist/core/page-store.d.ts +41 -0
- package/dist/core/page-store.d.ts.map +1 -0
- package/dist/core/page-store.js +74 -0
- package/dist/core/page-store.js.map +1 -0
- package/dist/core/path-completer.d.ts +10 -0
- package/dist/core/path-completer.d.ts.map +1 -0
- package/dist/core/path-completer.js +227 -0
- package/dist/core/path-completer.js.map +1 -0
- package/dist/core/path-discovery.d.ts +8 -0
- package/dist/core/path-discovery.d.ts.map +1 -0
- package/dist/core/path-discovery.js +105 -0
- package/dist/core/path-discovery.js.map +1 -0
- package/dist/core/path-utils.d.ts +9 -0
- package/dist/core/path-utils.d.ts.map +1 -0
- package/dist/core/path-utils.js +121 -0
- package/dist/core/path-utils.js.map +1 -0
- package/dist/core/path.d.ts +148 -0
- package/dist/core/path.d.ts.map +1 -0
- package/dist/core/path.js +674 -0
- package/dist/core/path.js.map +1 -0
- package/dist/core/primitives.d.ts +23 -0
- package/dist/core/primitives.d.ts.map +1 -0
- package/dist/core/primitives.js +54 -0
- package/dist/core/primitives.js.map +1 -0
- package/dist/core/read.d.ts +74 -0
- package/dist/core/read.d.ts.map +1 -0
- package/dist/core/read.js +562 -0
- package/dist/core/read.js.map +1 -0
- package/dist/core/schema.d.ts +139 -0
- package/dist/core/schema.d.ts.map +1 -0
- package/dist/core/schema.js +345 -0
- package/dist/core/schema.js.map +1 -0
- package/dist/core/search.d.ts +100 -0
- package/dist/core/search.d.ts.map +1 -0
- package/dist/core/search.js +248 -0
- package/dist/core/search.js.map +1 -0
- package/dist/core/sensitive.d.ts +6 -0
- package/dist/core/sensitive.d.ts.map +1 -0
- package/dist/core/sensitive.js +142 -0
- package/dist/core/sensitive.js.map +1 -0
- package/dist/core/store.d.ts +57 -0
- package/dist/core/store.d.ts.map +1 -0
- package/dist/core/store.js +162 -0
- package/dist/core/store.js.map +1 -0
- package/dist/core/util.d.ts +26 -0
- package/dist/core/util.d.ts.map +1 -0
- package/dist/core/util.js +66 -0
- package/dist/core/util.js.map +1 -0
- package/dist/core/watcher-registry.d.ts +68 -0
- package/dist/core/watcher-registry.d.ts.map +1 -0
- package/dist/core/watcher-registry.js +312 -0
- package/dist/core/watcher-registry.js.map +1 -0
- package/dist/http-policy.d.ts +121 -0
- package/dist/http-policy.d.ts.map +1 -0
- package/dist/http-policy.js +340 -0
- package/dist/http-policy.js.map +1 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +77 -41
- package/dist/index.js.map +1 -0
- package/dist/instructions.d.ts +12 -0
- package/dist/instructions.d.ts.map +1 -0
- package/dist/instructions.js +71 -0
- package/dist/instructions.js.map +1 -0
- package/dist/prompts.d.ts +8 -6
- package/dist/prompts.d.ts.map +1 -0
- package/dist/prompts.js +74 -168
- package/dist/prompts.js.map +1 -0
- package/dist/resources.d.ts +73 -9
- package/dist/resources.d.ts.map +1 -0
- package/dist/resources.js +392 -163
- package/dist/resources.js.map +1 -0
- package/dist/server.d.ts +62 -1
- package/dist/server.d.ts.map +1 -0
- package/dist/server.js +139 -1
- package/dist/server.js.map +1 -0
- package/dist/tools/batch.d.ts +89 -0
- package/dist/tools/batch.d.ts.map +1 -0
- package/dist/tools/batch.js +157 -0
- package/dist/tools/batch.js.map +1 -0
- package/dist/tools/create.d.ts +2 -0
- package/dist/tools/create.d.ts.map +1 -0
- package/dist/tools/create.js +142 -0
- package/dist/tools/create.js.map +1 -0
- package/dist/tools/define.d.ts +110 -0
- package/dist/tools/define.d.ts.map +1 -0
- package/dist/tools/define.js +394 -0
- package/dist/tools/define.js.map +1 -0
- package/dist/tools/delete-file.d.ts +2 -4
- package/dist/tools/delete-file.d.ts.map +1 -0
- package/dist/tools/delete-file.js +330 -87
- package/dist/tools/delete-file.js.map +1 -0
- package/dist/tools/diff.d.ts +2 -0
- package/dist/tools/diff.d.ts.map +1 -0
- package/dist/tools/diff.js +94 -0
- package/dist/tools/diff.js.map +1 -0
- package/dist/tools/edit.d.ts +2 -0
- package/dist/tools/edit.d.ts.map +1 -0
- package/dist/tools/edit.js +442 -0
- package/dist/tools/edit.js.map +1 -0
- package/dist/tools/index.d.ts +26 -0
- package/dist/tools/index.d.ts.map +1 -0
- package/dist/tools/index.js +52 -0
- package/dist/tools/index.js.map +1 -0
- package/dist/tools/list.d.ts +2 -0
- package/dist/tools/list.d.ts.map +1 -0
- package/dist/tools/list.js +291 -0
- package/dist/tools/list.js.map +1 -0
- package/dist/tools/move.d.ts +2 -0
- package/dist/tools/move.d.ts.map +1 -0
- package/dist/tools/move.js +321 -0
- package/dist/tools/move.js.map +1 -0
- package/dist/tools/patch.d.ts +2 -0
- package/dist/tools/patch.d.ts.map +1 -0
- package/dist/tools/patch.js +155 -0
- package/dist/tools/patch.js.map +1 -0
- package/dist/tools/progress.d.ts +52 -0
- package/dist/tools/progress.d.ts.map +1 -0
- package/dist/tools/progress.js +172 -0
- package/dist/tools/progress.js.map +1 -0
- package/dist/tools/read.d.ts +2 -4
- package/dist/tools/read.d.ts.map +1 -0
- package/dist/tools/read.js +398 -152
- package/dist/tools/read.js.map +1 -0
- package/dist/tools/replace-in-files.d.ts +2 -4
- package/dist/tools/replace-in-files.d.ts.map +1 -0
- package/dist/tools/replace-in-files.js +435 -264
- package/dist/tools/replace-in-files.js.map +1 -0
- package/dist/tools/roots.d.ts +2 -4
- package/dist/tools/roots.d.ts.map +1 -0
- package/dist/tools/roots.js +29 -52
- package/dist/tools/roots.js.map +1 -0
- package/dist/tools/search-content.d.ts +2 -4
- package/dist/tools/search-content.d.ts.map +1 -0
- package/dist/tools/search-content.js +262 -228
- package/dist/tools/search-content.js.map +1 -0
- package/dist/tools/search-files.d.ts +2 -4
- package/dist/tools/search-files.d.ts.map +1 -0
- package/dist/tools/search-files.js +162 -161
- package/dist/tools/search-files.js.map +1 -0
- package/dist/tools/stat.d.ts +2 -4
- package/dist/tools/stat.d.ts.map +1 -0
- package/dist/tools/stat.js +202 -62
- package/dist/tools/stat.js.map +1 -0
- package/dist/transport/http.d.ts +5 -0
- package/dist/transport/http.d.ts.map +1 -0
- package/dist/transport/http.js +276 -0
- package/dist/transport/http.js.map +1 -0
- package/dist/transport/shared.d.ts +45 -0
- package/dist/transport/shared.d.ts.map +1 -0
- package/dist/transport/shared.js +65 -0
- package/dist/transport/shared.js.map +1 -0
- package/dist/transport/stdio.d.ts +32 -0
- package/dist/transport/stdio.d.ts.map +1 -0
- package/dist/transport/stdio.js +279 -0
- package/dist/transport/stdio.js.map +1 -0
- package/dist/transport.d.ts +5 -0
- package/dist/transport.d.ts.map +1 -0
- package/dist/transport.js +4 -0
- package/dist/transport.js.map +1 -0
- package/package.json +37 -46
- package/dist/assets/logo.svg +0 -35
- package/dist/completions.d.ts +0 -2
- package/dist/completions.js +0 -572
- package/dist/config.d.ts +0 -119
- package/dist/config.js +0 -31
- package/dist/lib/abort.d.ts +0 -7
- package/dist/lib/abort.js +0 -81
- package/dist/lib/constants.d.ts +0 -42
- package/dist/lib/constants.js +0 -313
- package/dist/lib/errors.d.ts +0 -28
- package/dist/lib/errors.js +0 -301
- package/dist/lib/file-operations/core.d.ts +0 -53
- package/dist/lib/file-operations/core.js +0 -144
- package/dist/lib/file-operations/metadata.d.ts +0 -77
- package/dist/lib/file-operations/metadata.js +0 -878
- package/dist/lib/file-operations/search.d.ts +0 -46
- package/dist/lib/file-operations/search.js +0 -1194
- package/dist/lib/file-operations/traversal.d.ts +0 -40
- package/dist/lib/file-operations/traversal.js +0 -310
- package/dist/lib/fs-helpers.d.ts +0 -46
- package/dist/lib/fs-helpers.js +0 -606
- package/dist/lib/globs.d.ts +0 -2
- package/dist/lib/globs.js +0 -19
- package/dist/lib/logger.d.ts +0 -27
- package/dist/lib/logger.js +0 -91
- package/dist/lib/observability.d.ts +0 -33
- package/dist/lib/observability.js +0 -373
- package/dist/lib/paths.d.ts +0 -35
- package/dist/lib/paths.js +0 -634
- package/dist/lib/resource-store.d.ts +0 -28
- package/dist/lib/resource-store.js +0 -195
- package/dist/lib/utils.d.ts +0 -7
- package/dist/lib/utils.js +0 -37
- package/dist/lib/zod-codecs.d.ts +0 -2
- package/dist/lib/zod-codecs.js +0 -18
- package/dist/pkg-info.d.ts +0 -7
- package/dist/pkg-info.js +0 -9
- package/dist/resources/generated-instructions.d.ts +0 -1
- package/dist/resources/generated-instructions.js +0 -105
- package/dist/resources/tool-catalog.d.ts +0 -2
- package/dist/resources/tool-catalog.js +0 -78
- package/dist/resources/tool-info.d.ts +0 -9
- package/dist/resources/tool-info.js +0 -206
- package/dist/resources/workflows.d.ts +0 -1
- package/dist/resources/workflows.js +0 -72
- package/dist/schemas.d.ts +0 -505
- package/dist/schemas.js +0 -676
- package/dist/server/bootstrap.d.ts +0 -6
- package/dist/server/bootstrap.js +0 -563
- package/dist/server/event-store.d.ts +0 -18
- package/dist/server/event-store.js +0 -71
- package/dist/server/roots-manager.d.ts +0 -28
- package/dist/server/roots-manager.js +0 -227
- package/dist/server/task-store.d.ts +0 -10
- package/dist/server/task-store.js +0 -91
- package/dist/tools/apply-patch.d.ts +0 -4
- package/dist/tools/apply-patch.js +0 -235
- package/dist/tools/calculate-hash.d.ts +0 -4
- package/dist/tools/calculate-hash.js +0 -187
- package/dist/tools/contract.d.ts +0 -50
- package/dist/tools/contract.js +0 -1
- package/dist/tools/create-directory.d.ts +0 -4
- package/dist/tools/create-directory.js +0 -68
- package/dist/tools/diff-files.d.ts +0 -4
- package/dist/tools/diff-files.js +0 -136
- package/dist/tools/edit-file.d.ts +0 -4
- package/dist/tools/edit-file.js +0 -256
- package/dist/tools/icons.d.ts +0 -15
- package/dist/tools/icons.js +0 -24
- package/dist/tools/list-directory.d.ts +0 -4
- package/dist/tools/list-directory.js +0 -218
- package/dist/tools/move-file.d.ts +0 -4
- package/dist/tools/move-file.js +0 -178
- package/dist/tools/read-multiple.d.ts +0 -4
- package/dist/tools/read-multiple.js +0 -207
- package/dist/tools/shared.d.ts +0 -172
- package/dist/tools/shared.js +0 -599
- package/dist/tools/stat-many.d.ts +0 -4
- package/dist/tools/stat-many.js +0 -104
- package/dist/tools/task-support.d.ts +0 -29
- package/dist/tools/task-support.js +0 -472
- package/dist/tools/tree.d.ts +0 -4
- package/dist/tools/tree.js +0 -102
- package/dist/tools/write-file.d.ts +0 -4
- package/dist/tools/write-file.js +0 -60
- package/dist/tools.d.ts +0 -5
- package/dist/tools.js +0 -54
|
@@ -0,0 +1,562 @@
|
|
|
1
|
+
var __addDisposableResource = (this && this.__addDisposableResource) || function (env, value, async) {
|
|
2
|
+
if (value !== null && value !== void 0) {
|
|
3
|
+
if (typeof value !== "object" && typeof value !== "function") throw new TypeError("Object expected.");
|
|
4
|
+
var dispose, inner;
|
|
5
|
+
if (async) {
|
|
6
|
+
if (!Symbol.asyncDispose) throw new TypeError("Symbol.asyncDispose is not defined.");
|
|
7
|
+
dispose = value[Symbol.asyncDispose];
|
|
8
|
+
}
|
|
9
|
+
if (dispose === void 0) {
|
|
10
|
+
if (!Symbol.dispose) throw new TypeError("Symbol.dispose is not defined.");
|
|
11
|
+
dispose = value[Symbol.dispose];
|
|
12
|
+
if (async) inner = dispose;
|
|
13
|
+
}
|
|
14
|
+
if (typeof dispose !== "function") throw new TypeError("Object not disposable.");
|
|
15
|
+
if (inner) dispose = function() { try { inner.call(this); } catch (e) { return Promise.reject(e); } };
|
|
16
|
+
env.stack.push({ value: value, dispose: dispose, async: async });
|
|
17
|
+
}
|
|
18
|
+
else if (async) {
|
|
19
|
+
env.stack.push({ async: true });
|
|
20
|
+
}
|
|
21
|
+
return value;
|
|
22
|
+
};
|
|
23
|
+
var __disposeResources = (this && this.__disposeResources) || (function (SuppressedError) {
|
|
24
|
+
return function (env) {
|
|
25
|
+
function fail(e) {
|
|
26
|
+
env.error = env.hasError ? new SuppressedError(e, env.error, "An error was suppressed during disposal.") : e;
|
|
27
|
+
env.hasError = true;
|
|
28
|
+
}
|
|
29
|
+
var r, s = 0;
|
|
30
|
+
function next() {
|
|
31
|
+
while (r = env.stack.pop()) {
|
|
32
|
+
try {
|
|
33
|
+
if (!r.async && s === 1) return s = 0, env.stack.push(r), Promise.resolve().then(next);
|
|
34
|
+
if (r.dispose) {
|
|
35
|
+
var result = r.dispose.call(r.value);
|
|
36
|
+
if (r.async) return s |= 2, Promise.resolve(result).then(next, function(e) { fail(e); return next(); });
|
|
37
|
+
}
|
|
38
|
+
else s |= 1;
|
|
39
|
+
}
|
|
40
|
+
catch (e) {
|
|
41
|
+
fail(e);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
if (s === 1) return env.hasError ? Promise.reject(env.error) : Promise.resolve();
|
|
45
|
+
if (env.hasError) throw env.error;
|
|
46
|
+
}
|
|
47
|
+
return next();
|
|
48
|
+
};
|
|
49
|
+
})(typeof SuppressedError === "function" ? SuppressedError : function (error, suppressed, message) {
|
|
50
|
+
var e = new Error(message);
|
|
51
|
+
return e.name = "SuppressedError", e.error = error, e.suppressed = suppressed, e;
|
|
52
|
+
});
|
|
53
|
+
import { open as fsOpen } from 'node:fs/promises';
|
|
54
|
+
import { StringDecoder } from 'node:string_decoder';
|
|
55
|
+
import { withAbort } from './concurrency.js';
|
|
56
|
+
import { ErrorCode, formatUnknownErrorMessage, FsError, isFsError } from './errors.js';
|
|
57
|
+
import { isBinarySample, isKnownBinaryExtension, MIME_SAMPLE_SIZE } from './mime.js';
|
|
58
|
+
import { Logger } from './observability.js';
|
|
59
|
+
import { getMaxTextFileSize } from './util.js';
|
|
60
|
+
export const STREAM_CHUNK_SIZE = 64 * 1024;
|
|
61
|
+
const READ_ONLY_FILE_FLAG = 'r';
|
|
62
|
+
function assertPositiveIntegerOption(name, value, message) {
|
|
63
|
+
if (value === undefined ||
|
|
64
|
+
(typeof value === 'number' && Number.isSafeInteger(value) && value >= 1))
|
|
65
|
+
return;
|
|
66
|
+
throw new FsError(ErrorCode.INVALID_INPUT, message ?? `${name} must be a positive integer`);
|
|
67
|
+
}
|
|
68
|
+
async function openReadableFileHandle(filePath, signal) {
|
|
69
|
+
const handlePromise = fsOpen(filePath, READ_ONLY_FILE_FLAG);
|
|
70
|
+
if (!signal)
|
|
71
|
+
return handlePromise;
|
|
72
|
+
try {
|
|
73
|
+
return await withAbort(handlePromise, signal);
|
|
74
|
+
}
|
|
75
|
+
catch (error) {
|
|
76
|
+
void handlePromise
|
|
77
|
+
.then((handle) => {
|
|
78
|
+
void handle.close().catch((closeErr) => {
|
|
79
|
+
Logger.warn(`Failed to close file handle for ${filePath} after abort: ${formatUnknownErrorMessage(closeErr)}`);
|
|
80
|
+
});
|
|
81
|
+
})
|
|
82
|
+
.catch(() => {
|
|
83
|
+
/* ignore open error */
|
|
84
|
+
});
|
|
85
|
+
throw error;
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
async function readProbe(handle, signal) {
|
|
89
|
+
const buffer = Buffer.allocUnsafe(MIME_SAMPLE_SIZE);
|
|
90
|
+
const { bytesRead } = await withAbort(handle.read(buffer, 0, MIME_SAMPLE_SIZE, 0), signal);
|
|
91
|
+
if (bytesRead === 0) {
|
|
92
|
+
return Buffer.alloc(0);
|
|
93
|
+
}
|
|
94
|
+
return buffer.subarray(0, bytesRead);
|
|
95
|
+
}
|
|
96
|
+
async function isProbablyBinary(filePath, existingHandle, signal) {
|
|
97
|
+
const env_1 = { stack: [], error: void 0, hasError: false };
|
|
98
|
+
try {
|
|
99
|
+
if (isKnownBinaryExtension(filePath)) {
|
|
100
|
+
return true;
|
|
101
|
+
}
|
|
102
|
+
if (existingHandle) {
|
|
103
|
+
const slice = await readProbe(existingHandle, signal);
|
|
104
|
+
return isBinarySample(slice);
|
|
105
|
+
}
|
|
106
|
+
const handle = __addDisposableResource(env_1, await openReadableFileHandle(filePath, signal), true);
|
|
107
|
+
const slice = await readProbe(handle, signal);
|
|
108
|
+
return isBinarySample(slice);
|
|
109
|
+
}
|
|
110
|
+
catch (e_1) {
|
|
111
|
+
env_1.error = e_1;
|
|
112
|
+
env_1.hasError = true;
|
|
113
|
+
}
|
|
114
|
+
finally {
|
|
115
|
+
const result_1 = __disposeResources(env_1);
|
|
116
|
+
if (result_1)
|
|
117
|
+
await result_1;
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
function buildBaseOptions(spec) {
|
|
121
|
+
if (spec.maxSize !== undefined) {
|
|
122
|
+
assertPositiveIntegerOption('maxSize', spec.maxSize, 'maxSize must be at least 1');
|
|
123
|
+
}
|
|
124
|
+
const maxTextFileSize = getMaxTextFileSize();
|
|
125
|
+
return {
|
|
126
|
+
encoding: spec.encoding ?? 'utf-8',
|
|
127
|
+
maxSize: Math.min(spec.maxSize ?? maxTextFileSize, maxTextFileSize),
|
|
128
|
+
skipBinary: spec.skipBinary ?? false,
|
|
129
|
+
...(spec.signal ? { signal: spec.signal } : {}),
|
|
130
|
+
};
|
|
131
|
+
}
|
|
132
|
+
function normalizeHeadSpec(spec, base) {
|
|
133
|
+
assertPositiveIntegerOption('lines', spec.lines, 'lines must be at least 1');
|
|
134
|
+
return { ...base, kind: 'head', lines: spec.lines };
|
|
135
|
+
}
|
|
136
|
+
function normalizeTailSpec(spec, base) {
|
|
137
|
+
assertPositiveIntegerOption('lines', spec.lines, 'lines must be at least 1');
|
|
138
|
+
return { ...base, kind: 'tail', lines: spec.lines };
|
|
139
|
+
}
|
|
140
|
+
function normalizeRangeSpec(spec, base) {
|
|
141
|
+
assertPositiveIntegerOption('start', spec.start, 'start must be at least 1');
|
|
142
|
+
if (spec.end !== undefined) {
|
|
143
|
+
assertPositiveIntegerOption('end', spec.end, 'end must be at least 1');
|
|
144
|
+
if (spec.end < spec.start) {
|
|
145
|
+
throw new FsError(ErrorCode.INVALID_INPUT, 'end must be greater than or equal to start (default: 1)');
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
return {
|
|
149
|
+
...base,
|
|
150
|
+
kind: 'range',
|
|
151
|
+
start: spec.start,
|
|
152
|
+
...(spec.end !== undefined ? { end: spec.end } : {}),
|
|
153
|
+
};
|
|
154
|
+
}
|
|
155
|
+
export function normalizeSpec(spec) {
|
|
156
|
+
const base = buildBaseOptions(spec);
|
|
157
|
+
spec.signal?.throwIfAborted();
|
|
158
|
+
switch (spec.kind) {
|
|
159
|
+
case 'head':
|
|
160
|
+
return normalizeHeadSpec(spec, base);
|
|
161
|
+
case 'tail':
|
|
162
|
+
return normalizeTailSpec(spec, base);
|
|
163
|
+
case 'range':
|
|
164
|
+
return normalizeRangeSpec(spec, base);
|
|
165
|
+
case 'full':
|
|
166
|
+
return { ...base, kind: 'full' };
|
|
167
|
+
default: {
|
|
168
|
+
const _exhaustive = spec;
|
|
169
|
+
return _exhaustive;
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
function buildReadContentOptions(normalized) {
|
|
174
|
+
const result = {
|
|
175
|
+
encoding: normalized.encoding,
|
|
176
|
+
maxSize: normalized.maxSize,
|
|
177
|
+
};
|
|
178
|
+
if (normalized.signal)
|
|
179
|
+
result.signal = normalized.signal;
|
|
180
|
+
return result;
|
|
181
|
+
}
|
|
182
|
+
export function createTooLargeError(bytesRead, maxSize, requestedPath) {
|
|
183
|
+
return new FsError(ErrorCode.TOO_LARGE, `File exceeds size limit (${bytesRead} > ${maxSize} bytes)`, requestedPath, { size: bytesRead, maxSize });
|
|
184
|
+
}
|
|
185
|
+
export async function readFileBufferWithLimit(handle, maxSize, requestedPath, signal) {
|
|
186
|
+
const stream = handle.createReadStream({
|
|
187
|
+
start: 0,
|
|
188
|
+
highWaterMark: STREAM_CHUNK_SIZE,
|
|
189
|
+
autoClose: false,
|
|
190
|
+
emitClose: false,
|
|
191
|
+
signal,
|
|
192
|
+
});
|
|
193
|
+
const chunks = [];
|
|
194
|
+
let totalSize = 0;
|
|
195
|
+
try {
|
|
196
|
+
for await (const chunk of stream) {
|
|
197
|
+
const buffer = chunk;
|
|
198
|
+
totalSize += buffer.length;
|
|
199
|
+
if (totalSize > maxSize) {
|
|
200
|
+
stream.destroy();
|
|
201
|
+
throw createTooLargeError(totalSize, maxSize, requestedPath);
|
|
202
|
+
}
|
|
203
|
+
chunks.push(buffer);
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
finally {
|
|
207
|
+
if (!stream.destroyed) {
|
|
208
|
+
stream.on('error', (_err) => {
|
|
209
|
+
/* suppress post-destroy error event */
|
|
210
|
+
});
|
|
211
|
+
stream.destroy();
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
return Buffer.concat(chunks, totalSize);
|
|
215
|
+
}
|
|
216
|
+
export const countLines = (content) => {
|
|
217
|
+
if (content.length === 0)
|
|
218
|
+
return 0;
|
|
219
|
+
const trimmed = content.endsWith('\n') ? content.slice(0, -1) : content;
|
|
220
|
+
return trimmed.split('\n').length;
|
|
221
|
+
};
|
|
222
|
+
async function peekHasMore(iterator) {
|
|
223
|
+
try {
|
|
224
|
+
const { done } = await iterator.next();
|
|
225
|
+
return !done;
|
|
226
|
+
}
|
|
227
|
+
catch (error) {
|
|
228
|
+
// The peek only asks "is there another line", and the caller keeps nothing
|
|
229
|
+
// it returns. An over-long line past the requested range still answers yes
|
|
230
|
+
// — failing the whole read over a line outside it would reject e.g. lines
|
|
231
|
+
// 1-2 of a file whose line 3 is one 50 KB blob.
|
|
232
|
+
if (isFsError(error) && error.code === ErrorCode.TOO_LARGE)
|
|
233
|
+
return true;
|
|
234
|
+
throw error;
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
const LF = 0x0a;
|
|
238
|
+
function stripCarriageReturn(line) {
|
|
239
|
+
return line.endsWith('\r') ? line.slice(0, -1) : line;
|
|
240
|
+
}
|
|
241
|
+
/**
|
|
242
|
+
* Line iterator bounded by bytes rather than by the decoded string, so a file
|
|
243
|
+
* that is one enormous line cannot be materialized before the size check runs.
|
|
244
|
+
* `handle.readLines` decodes the whole line first and can OOM on such a file.
|
|
245
|
+
*
|
|
246
|
+
* Yields lines from `startLine` onward, 1-indexed. Lines before `startLine` are
|
|
247
|
+
* counted and discarded without accumulating, so an over-long line the caller
|
|
248
|
+
* skipped past does not fail the read. Line endings match `readLines`: split on
|
|
249
|
+
* `\n`, with a preceding `\r` stripped, so CRLF input decodes identically.
|
|
250
|
+
*/
|
|
251
|
+
async function* readLinesBounded(handle, options, filePath, startLine) {
|
|
252
|
+
const stream = handle.createReadStream({
|
|
253
|
+
start: 0,
|
|
254
|
+
highWaterMark: STREAM_CHUNK_SIZE,
|
|
255
|
+
autoClose: false,
|
|
256
|
+
emitClose: false,
|
|
257
|
+
...(options.signal ? { signal: options.signal } : {}),
|
|
258
|
+
});
|
|
259
|
+
const decoder = new StringDecoder(options.encoding);
|
|
260
|
+
let pending = '';
|
|
261
|
+
let pendingBytes = 0;
|
|
262
|
+
let lineNumber = 0;
|
|
263
|
+
const tooLong = (bytes) => new FsError(ErrorCode.TOO_LARGE, `File too large (single line ${bytes} > ${options.maxSize} bytes). Use a narrower range or head.`, filePath, { size: bytes, maxSize: options.maxSize });
|
|
264
|
+
try {
|
|
265
|
+
for await (const chunk of stream) {
|
|
266
|
+
const buffer = chunk;
|
|
267
|
+
let offset = 0;
|
|
268
|
+
// Search loop: each iteration finds the next LF and advances offset past
|
|
269
|
+
// it. Written as while-with-assignment so the re-search is visible, rather
|
|
270
|
+
// than buried in a for "increment" slot that re-runs indexOf.
|
|
271
|
+
let nl = buffer.indexOf(LF, offset);
|
|
272
|
+
while (nl !== -1) {
|
|
273
|
+
const segment = buffer.subarray(offset, nl);
|
|
274
|
+
const lineBytes = pendingBytes + segment.length;
|
|
275
|
+
lineNumber++;
|
|
276
|
+
// Decode even when skipping: the decoder carries multi-byte
|
|
277
|
+
// continuation state into the first line the caller keeps.
|
|
278
|
+
const decoded = decoder.write(segment);
|
|
279
|
+
if (lineNumber >= startLine) {
|
|
280
|
+
if (lineBytes > options.maxSize)
|
|
281
|
+
throw tooLong(lineBytes);
|
|
282
|
+
yield stripCarriageReturn(pending + decoded);
|
|
283
|
+
}
|
|
284
|
+
pending = '';
|
|
285
|
+
pendingBytes = 0;
|
|
286
|
+
offset = nl + 1;
|
|
287
|
+
nl = buffer.indexOf(LF, offset);
|
|
288
|
+
}
|
|
289
|
+
const rest = buffer.subarray(offset);
|
|
290
|
+
pendingBytes += rest.length;
|
|
291
|
+
if (lineNumber + 1 < startLine) {
|
|
292
|
+
decoder.write(rest); // still skipping; drop the text, keep decoder state
|
|
293
|
+
}
|
|
294
|
+
else {
|
|
295
|
+
if (pendingBytes > options.maxSize)
|
|
296
|
+
throw tooLong(pendingBytes);
|
|
297
|
+
pending += decoder.write(rest);
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
// Trailing line with no final newline. A file ending in `\n` leaves
|
|
301
|
+
// pendingBytes at 0 and emits nothing, matching readLines.
|
|
302
|
+
if (pendingBytes > 0) {
|
|
303
|
+
lineNumber++;
|
|
304
|
+
if (lineNumber >= startLine) {
|
|
305
|
+
if (pendingBytes > options.maxSize)
|
|
306
|
+
throw tooLong(pendingBytes);
|
|
307
|
+
yield pending + decoder.end();
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
finally {
|
|
312
|
+
if (!stream.destroyed) {
|
|
313
|
+
stream.on('error', (_err) => {
|
|
314
|
+
/* suppress post-destroy error event */
|
|
315
|
+
});
|
|
316
|
+
stream.destroy();
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
async function readRangeContent(handle, startLine, endLine, options, filePath) {
|
|
321
|
+
options.signal?.throwIfAborted();
|
|
322
|
+
const lines = [];
|
|
323
|
+
let lineNumber = startLine - 1;
|
|
324
|
+
let estimatedBytes = 0;
|
|
325
|
+
const newlineBytes = Buffer.byteLength('\n', options.encoding);
|
|
326
|
+
const stopAt = endLine ?? Number.POSITIVE_INFINITY;
|
|
327
|
+
let hasMoreLines = false;
|
|
328
|
+
let stoppedByLimit = false;
|
|
329
|
+
// Byte-bounded: an over-long line throws TOO_LARGE while still a partial
|
|
330
|
+
// buffer, before it can be decoded into one huge string.
|
|
331
|
+
const iterator = readLinesBounded(handle, options, filePath, startLine)[Symbol.asyncIterator]();
|
|
332
|
+
try {
|
|
333
|
+
for (;;) {
|
|
334
|
+
const { value: line, done } = await iterator.next();
|
|
335
|
+
if (done) {
|
|
336
|
+
break;
|
|
337
|
+
}
|
|
338
|
+
lineNumber++;
|
|
339
|
+
if (lineNumber > stopAt) {
|
|
340
|
+
hasMoreLines = true;
|
|
341
|
+
break;
|
|
342
|
+
}
|
|
343
|
+
lines.push(line);
|
|
344
|
+
estimatedBytes += Buffer.byteLength(line, options.encoding) + newlineBytes;
|
|
345
|
+
if (estimatedBytes > options.maxSize) {
|
|
346
|
+
stoppedByLimit = true;
|
|
347
|
+
hasMoreLines = await peekHasMore(iterator);
|
|
348
|
+
break;
|
|
349
|
+
}
|
|
350
|
+
if (lineNumber === stopAt) {
|
|
351
|
+
hasMoreLines = await peekHasMore(iterator);
|
|
352
|
+
break;
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
finally {
|
|
357
|
+
await iterator.return();
|
|
358
|
+
}
|
|
359
|
+
return {
|
|
360
|
+
content: lines.join('\n'),
|
|
361
|
+
truncated: stoppedByLimit || hasMoreLines,
|
|
362
|
+
linesRead: lines.length,
|
|
363
|
+
hasMoreLines,
|
|
364
|
+
};
|
|
365
|
+
}
|
|
366
|
+
async function readTailContent(handle, tail, options, filePath) {
|
|
367
|
+
options.signal?.throwIfAborted();
|
|
368
|
+
const stats = await handle.stat();
|
|
369
|
+
const fileSize = stats.size;
|
|
370
|
+
if (fileSize === 0) {
|
|
371
|
+
return {
|
|
372
|
+
content: '',
|
|
373
|
+
truncated: false,
|
|
374
|
+
linesRead: 0,
|
|
375
|
+
hasMoreLines: false,
|
|
376
|
+
};
|
|
377
|
+
}
|
|
378
|
+
const encoding = options.encoding;
|
|
379
|
+
// Accumulate raw bytes from the end: decoding a chunk ending mid-codepoint
|
|
380
|
+
// corrupts the trailing UTF-8 sequence into U+FFFD. 0x0A is single-byte and
|
|
381
|
+
// never part of a multibyte sequence, so count newlines on the raw buffer and
|
|
382
|
+
// decode the whole bounded buffer once at the end.
|
|
383
|
+
let position = fileSize;
|
|
384
|
+
const chunks = [];
|
|
385
|
+
let totalLen = 0;
|
|
386
|
+
let newlines = 0;
|
|
387
|
+
let stoppedByLimit = false;
|
|
388
|
+
while (position > 0) {
|
|
389
|
+
options.signal?.throwIfAborted();
|
|
390
|
+
const chunkSize = Math.min(position, STREAM_CHUNK_SIZE);
|
|
391
|
+
const buffer = Buffer.allocUnsafe(chunkSize);
|
|
392
|
+
const { bytesRead } = await handle.read(buffer, 0, chunkSize, position - chunkSize);
|
|
393
|
+
if (bytesRead < chunkSize) {
|
|
394
|
+
// The file shrank under us. Bytes from `position` on are still a
|
|
395
|
+
// contiguous suffix, so keep those and stop — splicing this short chunk in
|
|
396
|
+
// would leave a hole (and, with an alloc'd buffer, NULs) mid-content.
|
|
397
|
+
// `position` stays put, so the result is reported as having more lines.
|
|
398
|
+
break;
|
|
399
|
+
}
|
|
400
|
+
position -= chunkSize;
|
|
401
|
+
for (const byte of buffer) {
|
|
402
|
+
if (byte === 0x0a)
|
|
403
|
+
newlines++;
|
|
404
|
+
}
|
|
405
|
+
chunks.unshift(buffer);
|
|
406
|
+
totalLen += chunkSize;
|
|
407
|
+
// One newline more than `tail`: the oldest segment in the buffer starts
|
|
408
|
+
// mid-line whenever we stop before the file start, and gets dropped below.
|
|
409
|
+
if (newlines > tail)
|
|
410
|
+
break;
|
|
411
|
+
if (totalLen > options.maxSize) {
|
|
412
|
+
stoppedByLimit = true;
|
|
413
|
+
break;
|
|
414
|
+
}
|
|
415
|
+
}
|
|
416
|
+
const allBytes = Buffer.concat(chunks, totalLen);
|
|
417
|
+
let lines = allBytes.toString(encoding).split('\n');
|
|
418
|
+
// A trailing newline produces a spurious empty final segment; drop it.
|
|
419
|
+
if (lines.length > 0 && lines[lines.length - 1] === '') {
|
|
420
|
+
lines = lines.slice(0, -1);
|
|
421
|
+
}
|
|
422
|
+
// Normalize CRLF.
|
|
423
|
+
lines = lines.map((l) => (l.endsWith('\r') ? l.slice(0, -1) : l));
|
|
424
|
+
// We stopped before the file start, so the first segment is the tail of a
|
|
425
|
+
// line whose beginning was never read. Drop it instead of reporting a
|
|
426
|
+
// fragment as a complete line.
|
|
427
|
+
if (position > 0 && lines.length > 0) {
|
|
428
|
+
lines = lines.slice(1);
|
|
429
|
+
}
|
|
430
|
+
if (stoppedByLimit && lines.length < tail) {
|
|
431
|
+
throw new FsError(ErrorCode.TOO_LARGE, `File too large (${totalLen} > ${options.maxSize} bytes, could not collect ${tail} lines). Use a narrower tail or head.`, filePath, { size: totalLen, maxSize: options.maxSize });
|
|
432
|
+
}
|
|
433
|
+
let hasMoreLines = stoppedByLimit || position > 0;
|
|
434
|
+
if (lines.length > tail) {
|
|
435
|
+
hasMoreLines = true;
|
|
436
|
+
lines = lines.slice(lines.length - tail);
|
|
437
|
+
}
|
|
438
|
+
const content = lines.join('\n');
|
|
439
|
+
return {
|
|
440
|
+
content,
|
|
441
|
+
truncated: hasMoreLines,
|
|
442
|
+
linesRead: lines.length,
|
|
443
|
+
hasMoreLines,
|
|
444
|
+
};
|
|
445
|
+
}
|
|
446
|
+
async function readFullContent(handle, encoding, maxSize, requestedPath, signal) {
|
|
447
|
+
const buffer = await readFileBufferWithLimit(handle, maxSize, requestedPath, signal);
|
|
448
|
+
const content = buffer.toString(encoding);
|
|
449
|
+
return { content, totalLines: countLines(content) };
|
|
450
|
+
}
|
|
451
|
+
async function assertNotBinary(validPath, filePath, handle, normalized) {
|
|
452
|
+
normalized.signal?.throwIfAborted();
|
|
453
|
+
const isBinary = await isProbablyBinary(validPath, handle, normalized.signal);
|
|
454
|
+
if (!isBinary)
|
|
455
|
+
return;
|
|
456
|
+
throw new FsError(ErrorCode.INVALID_INPUT, 'Binary file detected.', filePath);
|
|
457
|
+
}
|
|
458
|
+
function assertSizeWithinLimit(size, maxSize, filePath) {
|
|
459
|
+
if (size <= maxSize)
|
|
460
|
+
return;
|
|
461
|
+
throw new FsError(ErrorCode.TOO_LARGE, `File too large (${size} > ${maxSize} bytes). Use head to preview.`, filePath, { size, maxSize });
|
|
462
|
+
}
|
|
463
|
+
async function readHead(context, spec) {
|
|
464
|
+
const contentOptions = buildReadContentOptions(spec);
|
|
465
|
+
const { content, truncated, linesRead, hasMoreLines } = await readRangeContent(context.handle, 1, spec.lines, contentOptions, context.filePath);
|
|
466
|
+
return {
|
|
467
|
+
path: context.validPath,
|
|
468
|
+
content,
|
|
469
|
+
truncated,
|
|
470
|
+
readMode: 'head',
|
|
471
|
+
head: spec.lines,
|
|
472
|
+
linesRead,
|
|
473
|
+
hasMoreLines,
|
|
474
|
+
};
|
|
475
|
+
}
|
|
476
|
+
async function readRange(context, spec) {
|
|
477
|
+
const contentOptions = buildReadContentOptions(spec);
|
|
478
|
+
const { content, truncated, linesRead, hasMoreLines } = await readRangeContent(context.handle, spec.start, spec.end, contentOptions, context.filePath);
|
|
479
|
+
return {
|
|
480
|
+
path: context.validPath,
|
|
481
|
+
content,
|
|
482
|
+
truncated,
|
|
483
|
+
readMode: 'range',
|
|
484
|
+
startLine: spec.start,
|
|
485
|
+
...(spec.end !== undefined ? { endLine: spec.end } : {}),
|
|
486
|
+
linesRead,
|
|
487
|
+
hasMoreLines,
|
|
488
|
+
};
|
|
489
|
+
}
|
|
490
|
+
async function readFull(context, spec) {
|
|
491
|
+
assertSizeWithinLimit(context.stats.size, spec.maxSize, context.filePath);
|
|
492
|
+
const { content, totalLines } = await readFullContent(context.handle, spec.encoding, spec.maxSize, context.filePath, spec.signal);
|
|
493
|
+
return {
|
|
494
|
+
path: context.validPath,
|
|
495
|
+
content,
|
|
496
|
+
truncated: false,
|
|
497
|
+
totalLines,
|
|
498
|
+
readMode: 'full',
|
|
499
|
+
linesRead: totalLines,
|
|
500
|
+
hasMoreLines: false,
|
|
501
|
+
};
|
|
502
|
+
}
|
|
503
|
+
async function readTail(context, spec) {
|
|
504
|
+
const contentOptions = buildReadContentOptions(spec);
|
|
505
|
+
const { content, truncated, linesRead, hasMoreLines } = await readTailContent(context.handle, spec.lines, contentOptions, context.validPath);
|
|
506
|
+
return {
|
|
507
|
+
path: context.validPath,
|
|
508
|
+
content,
|
|
509
|
+
truncated,
|
|
510
|
+
readMode: 'tail',
|
|
511
|
+
tail: spec.lines,
|
|
512
|
+
linesRead,
|
|
513
|
+
hasMoreLines,
|
|
514
|
+
};
|
|
515
|
+
}
|
|
516
|
+
async function readByMode(context) {
|
|
517
|
+
switch (context.spec.kind) {
|
|
518
|
+
case 'head':
|
|
519
|
+
return readHead(context, context.spec);
|
|
520
|
+
case 'range':
|
|
521
|
+
return readRange(context, context.spec);
|
|
522
|
+
case 'full':
|
|
523
|
+
return readFull(context, context.spec);
|
|
524
|
+
case 'tail':
|
|
525
|
+
return readTail(context, context.spec);
|
|
526
|
+
default: {
|
|
527
|
+
const _exhaustive = context.spec;
|
|
528
|
+
return _exhaustive;
|
|
529
|
+
}
|
|
530
|
+
}
|
|
531
|
+
}
|
|
532
|
+
export function assertFileStats(filePath, stats) {
|
|
533
|
+
if (!stats.isFile()) {
|
|
534
|
+
throw new FsError(ErrorCode.NOT_FILE, 'Not a regular file', filePath);
|
|
535
|
+
}
|
|
536
|
+
}
|
|
537
|
+
export async function readNormalized(filePath, validPath, stats, spec) {
|
|
538
|
+
const env_2 = { stack: [], error: void 0, hasError: false };
|
|
539
|
+
try {
|
|
540
|
+
spec.signal?.throwIfAborted();
|
|
541
|
+
assertFileStats(filePath, stats);
|
|
542
|
+
const handle = __addDisposableResource(env_2, await openReadableFileHandle(validPath, spec.signal), true);
|
|
543
|
+
if (spec.skipBinary) {
|
|
544
|
+
await assertNotBinary(validPath, filePath, handle, spec);
|
|
545
|
+
}
|
|
546
|
+
spec.signal?.throwIfAborted();
|
|
547
|
+
return await readByMode({ handle, validPath, filePath, stats, spec });
|
|
548
|
+
}
|
|
549
|
+
catch (e_2) {
|
|
550
|
+
env_2.error = e_2;
|
|
551
|
+
env_2.hasError = true;
|
|
552
|
+
}
|
|
553
|
+
finally {
|
|
554
|
+
const result_2 = __disposeResources(env_2);
|
|
555
|
+
if (result_2)
|
|
556
|
+
await result_2;
|
|
557
|
+
}
|
|
558
|
+
}
|
|
559
|
+
export async function readFileWithStats(filePath, validPath, stats, spec) {
|
|
560
|
+
return readNormalized(filePath, validPath, stats, normalizeSpec(spec ?? { kind: 'full' }));
|
|
561
|
+
}
|
|
562
|
+
//# sourceMappingURL=read.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"read.js","sourceRoot":"","sources":["../../src/core/read.ts"],"names":[],"mappings":"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","sourcesContent":["// File reading pipeline split out of fs.ts. fs.ts imports from here; never the reverse.\nimport type { Stats } from 'node:fs';\nimport { open as fsOpen } from 'node:fs/promises';\nimport type { FileHandle } from 'node:fs/promises';\nimport { StringDecoder } from 'node:string_decoder';\n\nimport { withAbort } from './concurrency.js';\nimport { ErrorCode, formatUnknownErrorMessage, FsError, isFsError } from './errors.js';\nimport { isBinarySample, isKnownBinaryExtension, MIME_SAMPLE_SIZE } from './mime.js';\nimport { Logger } from './observability.js';\nimport { getMaxTextFileSize } from './util.js';\n\nexport const STREAM_CHUNK_SIZE = 64 * 1024;\n\nconst READ_ONLY_FILE_FLAG = 'r';\n\nfunction assertPositiveIntegerOption(name: string, value: unknown, message?: string): void {\n if (\n value === undefined ||\n (typeof value === 'number' && Number.isSafeInteger(value) && value >= 1)\n )\n return;\n throw new FsError(ErrorCode.INVALID_INPUT, message ?? `${name} must be a positive integer`);\n}\n\nasync function openReadableFileHandle(filePath: string, signal?: AbortSignal): Promise<FileHandle> {\n const handlePromise = fsOpen(filePath, READ_ONLY_FILE_FLAG);\n if (!signal) return handlePromise;\n try {\n return await withAbort(handlePromise, signal);\n } catch (error) {\n void handlePromise\n .then((handle) => {\n void handle.close().catch((closeErr: unknown) => {\n Logger.warn(\n `Failed to close file handle for ${filePath} after abort: ${formatUnknownErrorMessage(closeErr)}`,\n );\n });\n })\n .catch(() => {\n /* ignore open error */\n });\n throw error;\n }\n}\n\nasync function readProbe(handle: FileHandle, signal?: AbortSignal): Promise<Buffer> {\n const buffer = Buffer.allocUnsafe(MIME_SAMPLE_SIZE);\n const { bytesRead } = await withAbort(handle.read(buffer, 0, MIME_SAMPLE_SIZE, 0), signal);\n\n if (bytesRead === 0) {\n return Buffer.alloc(0);\n }\n\n return buffer.subarray(0, bytesRead);\n}\n\nasync function isProbablyBinary(\n filePath: string,\n existingHandle?: FileHandle,\n signal?: AbortSignal,\n): Promise<boolean> {\n if (isKnownBinaryExtension(filePath)) {\n return true;\n }\n\n if (existingHandle) {\n const slice = await readProbe(existingHandle, signal);\n return isBinarySample(slice);\n }\n\n await using handle = await openReadableFileHandle(filePath, signal);\n const slice = await readProbe(handle, signal);\n return isBinarySample(slice);\n}\n\nexport type ReadSpec =\n | {\n kind: 'full';\n encoding?: BufferEncoding;\n maxSize?: number;\n skipBinary?: boolean;\n signal?: AbortSignal;\n }\n | {\n kind: 'head';\n lines: number;\n encoding?: BufferEncoding;\n maxSize?: number;\n skipBinary?: boolean;\n signal?: AbortSignal;\n }\n | {\n kind: 'tail';\n lines: number;\n encoding?: BufferEncoding;\n maxSize?: number;\n skipBinary?: boolean;\n signal?: AbortSignal;\n }\n | {\n kind: 'range';\n start: number;\n end?: number;\n encoding?: BufferEncoding;\n maxSize?: number;\n skipBinary?: boolean;\n signal?: AbortSignal;\n };\n\ninterface NormalizedBase {\n encoding: BufferEncoding;\n maxSize: number;\n skipBinary: boolean;\n signal?: AbortSignal;\n}\n\ntype NormalizedSpec =\n | (NormalizedBase & { kind: 'full' })\n | (NormalizedBase & { kind: 'head'; lines: number })\n | (NormalizedBase & { kind: 'tail'; lines: number })\n | (NormalizedBase & { kind: 'range'; start: number; end?: number });\n\ninterface ReadContentOptions {\n encoding: BufferEncoding;\n maxSize: number;\n signal?: AbortSignal;\n}\n\ninterface PartialReadResult {\n content: string;\n truncated: boolean;\n linesRead: number;\n hasMoreLines: boolean;\n}\n\nexport interface ReadFileResult {\n path: string;\n content: string;\n truncated: boolean;\n totalLines?: number;\n readMode: ReadSpec['kind'];\n head?: number;\n tail?: number;\n startLine?: number;\n endLine?: number;\n linesRead?: number;\n hasMoreLines?: boolean;\n}\n\nfunction buildBaseOptions(spec: ReadSpec): NormalizedBase {\n if (spec.maxSize !== undefined) {\n assertPositiveIntegerOption('maxSize', spec.maxSize, 'maxSize must be at least 1');\n }\n const maxTextFileSize = getMaxTextFileSize();\n return {\n encoding: spec.encoding ?? 'utf-8',\n maxSize: Math.min(spec.maxSize ?? maxTextFileSize, maxTextFileSize),\n skipBinary: spec.skipBinary ?? false,\n ...(spec.signal ? { signal: spec.signal } : {}),\n };\n}\n\nfunction normalizeHeadSpec(\n spec: Extract<ReadSpec, { kind: 'head' }>,\n base: NormalizedBase,\n): NormalizedSpec {\n assertPositiveIntegerOption('lines', spec.lines, 'lines must be at least 1');\n return { ...base, kind: 'head', lines: spec.lines };\n}\n\nfunction normalizeTailSpec(\n spec: Extract<ReadSpec, { kind: 'tail' }>,\n base: NormalizedBase,\n): NormalizedSpec {\n assertPositiveIntegerOption('lines', spec.lines, 'lines must be at least 1');\n return { ...base, kind: 'tail', lines: spec.lines };\n}\n\nfunction normalizeRangeSpec(\n spec: Extract<ReadSpec, { kind: 'range' }>,\n base: NormalizedBase,\n): NormalizedSpec {\n assertPositiveIntegerOption('start', spec.start, 'start must be at least 1');\n if (spec.end !== undefined) {\n assertPositiveIntegerOption('end', spec.end, 'end must be at least 1');\n if (spec.end < spec.start) {\n throw new FsError(\n ErrorCode.INVALID_INPUT,\n 'end must be greater than or equal to start (default: 1)',\n );\n }\n }\n return {\n ...base,\n kind: 'range',\n start: spec.start,\n ...(spec.end !== undefined ? { end: spec.end } : {}),\n };\n}\n\nexport function normalizeSpec(spec: ReadSpec): NormalizedSpec {\n const base = buildBaseOptions(spec);\n spec.signal?.throwIfAborted();\n\n switch (spec.kind) {\n case 'head':\n return normalizeHeadSpec(spec, base);\n case 'tail':\n return normalizeTailSpec(spec, base);\n case 'range':\n return normalizeRangeSpec(spec, base);\n case 'full':\n return { ...base, kind: 'full' };\n default: {\n const _exhaustive: never = spec;\n return _exhaustive;\n }\n }\n}\n\nfunction buildReadContentOptions(normalized: NormalizedBase): ReadContentOptions {\n const result: ReadContentOptions = {\n encoding: normalized.encoding,\n maxSize: normalized.maxSize,\n };\n if (normalized.signal) result.signal = normalized.signal;\n return result;\n}\n\nexport function createTooLargeError(\n bytesRead: number,\n maxSize: number,\n requestedPath: string,\n): FsError {\n return new FsError(\n ErrorCode.TOO_LARGE,\n `File exceeds size limit (${bytesRead} > ${maxSize} bytes)`,\n requestedPath,\n { size: bytesRead, maxSize },\n );\n}\n\nexport async function readFileBufferWithLimit(\n handle: FileHandle,\n maxSize: number,\n requestedPath: string,\n signal?: AbortSignal,\n): Promise<Buffer> {\n const stream = handle.createReadStream({\n start: 0,\n highWaterMark: STREAM_CHUNK_SIZE,\n autoClose: false,\n emitClose: false,\n signal,\n });\n\n const chunks: Buffer[] = [];\n let totalSize = 0;\n\n try {\n for await (const chunk of stream) {\n const buffer = chunk as Buffer;\n totalSize += buffer.length;\n\n if (totalSize > maxSize) {\n stream.destroy();\n throw createTooLargeError(totalSize, maxSize, requestedPath);\n }\n\n chunks.push(buffer);\n }\n } finally {\n if (!stream.destroyed) {\n stream.on('error', (_err: unknown) => {\n /* suppress post-destroy error event */\n });\n stream.destroy();\n }\n }\n\n return Buffer.concat(chunks, totalSize);\n}\n\nexport const countLines = (content: string): number => {\n if (content.length === 0) return 0;\n const trimmed = content.endsWith('\\n') ? content.slice(0, -1) : content;\n return trimmed.split('\\n').length;\n};\n\nasync function peekHasMore(iterator: AsyncIterator<string>): Promise<boolean> {\n try {\n const { done } = await iterator.next();\n return !done;\n } catch (error) {\n // The peek only asks \"is there another line\", and the caller keeps nothing\n // it returns. An over-long line past the requested range still answers yes\n // — failing the whole read over a line outside it would reject e.g. lines\n // 1-2 of a file whose line 3 is one 50 KB blob.\n if (isFsError(error) && error.code === ErrorCode.TOO_LARGE) return true;\n throw error;\n }\n}\n\nconst LF = 0x0a;\n\nfunction stripCarriageReturn(line: string): string {\n return line.endsWith('\\r') ? line.slice(0, -1) : line;\n}\n\n/**\n * Line iterator bounded by bytes rather than by the decoded string, so a file\n * that is one enormous line cannot be materialized before the size check runs.\n * `handle.readLines` decodes the whole line first and can OOM on such a file.\n *\n * Yields lines from `startLine` onward, 1-indexed. Lines before `startLine` are\n * counted and discarded without accumulating, so an over-long line the caller\n * skipped past does not fail the read. Line endings match `readLines`: split on\n * `\\n`, with a preceding `\\r` stripped, so CRLF input decodes identically.\n */\nasync function* readLinesBounded(\n handle: FileHandle,\n options: ReadContentOptions,\n filePath: string,\n startLine: number,\n): AsyncGenerator<string, void, undefined> {\n const stream = handle.createReadStream({\n start: 0,\n highWaterMark: STREAM_CHUNK_SIZE,\n autoClose: false,\n emitClose: false,\n ...(options.signal ? { signal: options.signal } : {}),\n });\n\n const decoder = new StringDecoder(options.encoding);\n let pending = '';\n let pendingBytes = 0;\n let lineNumber = 0;\n\n const tooLong = (bytes: number): FsError =>\n new FsError(\n ErrorCode.TOO_LARGE,\n `File too large (single line ${bytes} > ${options.maxSize} bytes). Use a narrower range or head.`,\n filePath,\n { size: bytes, maxSize: options.maxSize },\n );\n\n try {\n for await (const chunk of stream) {\n const buffer = chunk as Buffer;\n let offset = 0;\n\n // Search loop: each iteration finds the next LF and advances offset past\n // it. Written as while-with-assignment so the re-search is visible, rather\n // than buried in a for \"increment\" slot that re-runs indexOf.\n let nl = buffer.indexOf(LF, offset);\n while (nl !== -1) {\n const segment = buffer.subarray(offset, nl);\n const lineBytes = pendingBytes + segment.length;\n lineNumber++;\n // Decode even when skipping: the decoder carries multi-byte\n // continuation state into the first line the caller keeps.\n const decoded = decoder.write(segment);\n if (lineNumber >= startLine) {\n if (lineBytes > options.maxSize) throw tooLong(lineBytes);\n yield stripCarriageReturn(pending + decoded);\n }\n pending = '';\n pendingBytes = 0;\n offset = nl + 1;\n nl = buffer.indexOf(LF, offset);\n }\n\n const rest = buffer.subarray(offset);\n pendingBytes += rest.length;\n if (lineNumber + 1 < startLine) {\n decoder.write(rest); // still skipping; drop the text, keep decoder state\n } else {\n if (pendingBytes > options.maxSize) throw tooLong(pendingBytes);\n pending += decoder.write(rest);\n }\n }\n\n // Trailing line with no final newline. A file ending in `\\n` leaves\n // pendingBytes at 0 and emits nothing, matching readLines.\n if (pendingBytes > 0) {\n lineNumber++;\n if (lineNumber >= startLine) {\n if (pendingBytes > options.maxSize) throw tooLong(pendingBytes);\n yield pending + decoder.end();\n }\n }\n } finally {\n if (!stream.destroyed) {\n stream.on('error', (_err: unknown) => {\n /* suppress post-destroy error event */\n });\n stream.destroy();\n }\n }\n}\n\nasync function readRangeContent(\n handle: FileHandle,\n startLine: number,\n endLine: number | undefined,\n options: ReadContentOptions,\n filePath: string,\n): Promise<PartialReadResult> {\n options.signal?.throwIfAborted();\n\n const lines: string[] = [];\n let lineNumber = startLine - 1;\n let estimatedBytes = 0;\n const newlineBytes = Buffer.byteLength('\\n', options.encoding);\n const stopAt = endLine ?? Number.POSITIVE_INFINITY;\n\n let hasMoreLines = false;\n let stoppedByLimit = false;\n\n // Byte-bounded: an over-long line throws TOO_LARGE while still a partial\n // buffer, before it can be decoded into one huge string.\n const iterator = readLinesBounded(handle, options, filePath, startLine)[Symbol.asyncIterator]();\n\n try {\n for (;;) {\n const { value: line, done } = await iterator.next();\n if (done) {\n break;\n }\n lineNumber++;\n\n if (lineNumber > stopAt) {\n hasMoreLines = true;\n break;\n }\n\n lines.push(line);\n\n estimatedBytes += Buffer.byteLength(line, options.encoding) + newlineBytes;\n if (estimatedBytes > options.maxSize) {\n stoppedByLimit = true;\n hasMoreLines = await peekHasMore(iterator);\n break;\n }\n\n if (lineNumber === stopAt) {\n hasMoreLines = await peekHasMore(iterator);\n break;\n }\n }\n } finally {\n await iterator.return();\n }\n\n return {\n content: lines.join('\\n'),\n truncated: stoppedByLimit || hasMoreLines,\n linesRead: lines.length,\n hasMoreLines,\n };\n}\n\nasync function readTailContent(\n handle: FileHandle,\n tail: number,\n options: ReadContentOptions,\n filePath: string,\n): Promise<PartialReadResult> {\n options.signal?.throwIfAborted();\n\n const stats = await handle.stat();\n const fileSize = stats.size;\n if (fileSize === 0) {\n return {\n content: '',\n truncated: false,\n linesRead: 0,\n hasMoreLines: false,\n };\n }\n\n const encoding = options.encoding;\n // Accumulate raw bytes from the end: decoding a chunk ending mid-codepoint\n // corrupts the trailing UTF-8 sequence into U+FFFD. 0x0A is single-byte and\n // never part of a multibyte sequence, so count newlines on the raw buffer and\n // decode the whole bounded buffer once at the end.\n let position = fileSize;\n const chunks: Buffer[] = [];\n let totalLen = 0;\n let newlines = 0;\n let stoppedByLimit = false;\n\n while (position > 0) {\n options.signal?.throwIfAborted();\n const chunkSize = Math.min(position, STREAM_CHUNK_SIZE);\n const buffer = Buffer.allocUnsafe(chunkSize);\n const { bytesRead } = await handle.read(buffer, 0, chunkSize, position - chunkSize);\n if (bytesRead < chunkSize) {\n // The file shrank under us. Bytes from `position` on are still a\n // contiguous suffix, so keep those and stop — splicing this short chunk in\n // would leave a hole (and, with an alloc'd buffer, NULs) mid-content.\n // `position` stays put, so the result is reported as having more lines.\n break;\n }\n position -= chunkSize;\n\n for (const byte of buffer) {\n if (byte === 0x0a) newlines++;\n }\n\n chunks.unshift(buffer);\n totalLen += chunkSize;\n\n // One newline more than `tail`: the oldest segment in the buffer starts\n // mid-line whenever we stop before the file start, and gets dropped below.\n if (newlines > tail) break;\n if (totalLen > options.maxSize) {\n stoppedByLimit = true;\n break;\n }\n }\n\n const allBytes = Buffer.concat(chunks, totalLen);\n let lines = allBytes.toString(encoding).split('\\n');\n\n // A trailing newline produces a spurious empty final segment; drop it.\n if (lines.length > 0 && lines[lines.length - 1] === '') {\n lines = lines.slice(0, -1);\n }\n // Normalize CRLF.\n lines = lines.map((l) => (l.endsWith('\\r') ? l.slice(0, -1) : l));\n\n // We stopped before the file start, so the first segment is the tail of a\n // line whose beginning was never read. Drop it instead of reporting a\n // fragment as a complete line.\n if (position > 0 && lines.length > 0) {\n lines = lines.slice(1);\n }\n\n if (stoppedByLimit && lines.length < tail) {\n throw new FsError(\n ErrorCode.TOO_LARGE,\n `File too large (${totalLen} > ${options.maxSize} bytes, could not collect ${tail} lines). Use a narrower tail or head.`,\n filePath,\n { size: totalLen, maxSize: options.maxSize },\n );\n }\n\n let hasMoreLines = stoppedByLimit || position > 0;\n if (lines.length > tail) {\n hasMoreLines = true;\n lines = lines.slice(lines.length - tail);\n }\n\n const content = lines.join('\\n');\n return {\n content,\n truncated: hasMoreLines,\n linesRead: lines.length,\n hasMoreLines,\n };\n}\n\nasync function readFullContent(\n handle: FileHandle,\n encoding: BufferEncoding,\n maxSize: number,\n requestedPath: string,\n signal?: AbortSignal,\n): Promise<{ content: string; totalLines: number }> {\n const buffer = await readFileBufferWithLimit(handle, maxSize, requestedPath, signal);\n const content = buffer.toString(encoding);\n return { content, totalLines: countLines(content) };\n}\n\nasync function assertNotBinary(\n validPath: string,\n filePath: string,\n handle: FileHandle,\n normalized: NormalizedBase,\n): Promise<void> {\n normalized.signal?.throwIfAborted();\n const isBinary = await isProbablyBinary(validPath, handle, normalized.signal);\n if (!isBinary) return;\n throw new FsError(ErrorCode.INVALID_INPUT, 'Binary file detected.', filePath);\n}\n\nfunction assertSizeWithinLimit(size: number, maxSize: number, filePath: string): void {\n if (size <= maxSize) return;\n throw new FsError(\n ErrorCode.TOO_LARGE,\n `File too large (${size} > ${maxSize} bytes). Use head to preview.`,\n filePath,\n { size, maxSize },\n );\n}\n\ninterface ReadModeContext {\n handle: FileHandle;\n validPath: string;\n filePath: string;\n stats: Stats;\n spec: NormalizedSpec;\n}\n\nasync function readHead(\n context: ReadModeContext,\n spec: Extract<NormalizedSpec, { kind: 'head' }>,\n): Promise<ReadFileResult> {\n const contentOptions = buildReadContentOptions(spec);\n const { content, truncated, linesRead, hasMoreLines } = await readRangeContent(\n context.handle,\n 1,\n spec.lines,\n contentOptions,\n context.filePath,\n );\n\n return {\n path: context.validPath,\n content,\n truncated,\n readMode: 'head',\n head: spec.lines,\n linesRead,\n hasMoreLines,\n };\n}\n\nasync function readRange(\n context: ReadModeContext,\n spec: Extract<NormalizedSpec, { kind: 'range' }>,\n): Promise<ReadFileResult> {\n const contentOptions = buildReadContentOptions(spec);\n const { content, truncated, linesRead, hasMoreLines } = await readRangeContent(\n context.handle,\n spec.start,\n spec.end,\n contentOptions,\n context.filePath,\n );\n\n return {\n path: context.validPath,\n content,\n truncated,\n readMode: 'range',\n startLine: spec.start,\n ...(spec.end !== undefined ? { endLine: spec.end } : {}),\n linesRead,\n hasMoreLines,\n };\n}\n\nasync function readFull(\n context: ReadModeContext,\n spec: Extract<NormalizedSpec, { kind: 'full' }>,\n): Promise<ReadFileResult> {\n assertSizeWithinLimit(context.stats.size, spec.maxSize, context.filePath);\n const { content, totalLines } = await readFullContent(\n context.handle,\n spec.encoding,\n spec.maxSize,\n context.filePath,\n spec.signal,\n );\n\n return {\n path: context.validPath,\n content,\n truncated: false,\n totalLines,\n readMode: 'full',\n linesRead: totalLines,\n hasMoreLines: false,\n };\n}\n\nasync function readTail(\n context: ReadModeContext,\n spec: Extract<NormalizedSpec, { kind: 'tail' }>,\n): Promise<ReadFileResult> {\n const contentOptions = buildReadContentOptions(spec);\n const { content, truncated, linesRead, hasMoreLines } = await readTailContent(\n context.handle,\n spec.lines,\n contentOptions,\n context.validPath,\n );\n\n return {\n path: context.validPath,\n content,\n truncated,\n readMode: 'tail',\n tail: spec.lines,\n linesRead,\n hasMoreLines,\n };\n}\n\nasync function readByMode(context: ReadModeContext): Promise<ReadFileResult> {\n switch (context.spec.kind) {\n case 'head':\n return readHead(context, context.spec);\n case 'range':\n return readRange(context, context.spec);\n case 'full':\n return readFull(context, context.spec);\n case 'tail':\n return readTail(context, context.spec);\n default: {\n const _exhaustive: never = context.spec;\n return _exhaustive;\n }\n }\n}\n\nexport function assertFileStats(filePath: string, stats: Stats): void {\n if (!stats.isFile()) {\n throw new FsError(ErrorCode.NOT_FILE, 'Not a regular file', filePath);\n }\n}\n\nexport async function readNormalized(\n filePath: string,\n validPath: string,\n stats: Stats,\n spec: NormalizedSpec,\n): Promise<ReadFileResult> {\n spec.signal?.throwIfAborted();\n\n assertFileStats(filePath, stats);\n\n await using handle = await openReadableFileHandle(validPath, spec.signal);\n\n if (spec.skipBinary) {\n await assertNotBinary(validPath, filePath, handle, spec);\n }\n spec.signal?.throwIfAborted();\n\n return await readByMode({ handle, validPath, filePath, stats, spec });\n}\n\nexport async function readFileWithStats(\n filePath: string,\n validPath: string,\n stats: Stats,\n spec: ReadSpec | undefined,\n): Promise<ReadFileResult> {\n return readNormalized(filePath, validPath, stats, normalizeSpec(spec ?? { kind: 'full' }));\n}\n"]}
|