@j0hanz/filesystem-mcp 1.19.1 → 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.
Files changed (301) hide show
  1. package/README.md +333 -838
  2. package/dist/cli-help.d.ts +3 -0
  3. package/dist/cli-help.d.ts.map +1 -0
  4. package/dist/cli-help.js +169 -0
  5. package/dist/cli-help.js.map +1 -0
  6. package/dist/cli.d.ts +28 -0
  7. package/dist/cli.d.ts.map +1 -0
  8. package/dist/cli.js +168 -123
  9. package/dist/cli.js.map +1 -0
  10. package/dist/core/concurrency.d.ts +70 -0
  11. package/dist/core/concurrency.d.ts.map +1 -0
  12. package/dist/core/concurrency.js +173 -0
  13. package/dist/core/concurrency.js.map +1 -0
  14. package/dist/core/config.d.ts +19 -0
  15. package/dist/core/config.d.ts.map +1 -0
  16. package/dist/core/config.js +11 -0
  17. package/dist/core/config.js.map +1 -0
  18. package/dist/core/cursor.d.ts +23 -0
  19. package/dist/core/cursor.d.ts.map +1 -0
  20. package/dist/core/cursor.js +62 -0
  21. package/dist/core/cursor.js.map +1 -0
  22. package/dist/core/errors.d.ts +96 -0
  23. package/dist/core/errors.d.ts.map +1 -0
  24. package/dist/core/errors.js +356 -0
  25. package/dist/core/errors.js.map +1 -0
  26. package/dist/core/file-uri.d.ts +24 -0
  27. package/dist/core/file-uri.d.ts.map +1 -0
  28. package/dist/core/file-uri.js +52 -0
  29. package/dist/core/file-uri.js.map +1 -0
  30. package/dist/core/fmt.d.ts +38 -0
  31. package/dist/core/fmt.d.ts.map +1 -0
  32. package/dist/core/fmt.js +109 -0
  33. package/dist/core/fmt.js.map +1 -0
  34. package/dist/core/fs.d.ts +108 -0
  35. package/dist/core/fs.d.ts.map +1 -0
  36. package/dist/core/fs.js +243 -0
  37. package/dist/core/fs.js.map +1 -0
  38. package/dist/core/glob.d.ts +41 -0
  39. package/dist/core/glob.d.ts.map +1 -0
  40. package/dist/core/glob.js +399 -0
  41. package/dist/core/glob.js.map +1 -0
  42. package/dist/core/input-required.d.ts +147 -0
  43. package/dist/core/input-required.d.ts.map +1 -0
  44. package/dist/core/input-required.js +261 -0
  45. package/dist/core/input-required.js.map +1 -0
  46. package/dist/core/mime.d.ts +15 -0
  47. package/dist/core/mime.d.ts.map +1 -0
  48. package/dist/core/mime.js +188 -0
  49. package/dist/core/mime.js.map +1 -0
  50. package/dist/core/observability.d.ts +10 -0
  51. package/dist/core/observability.d.ts.map +1 -0
  52. package/dist/core/observability.js +76 -0
  53. package/dist/core/observability.js.map +1 -0
  54. package/dist/core/page-store.d.ts +41 -0
  55. package/dist/core/page-store.d.ts.map +1 -0
  56. package/dist/core/page-store.js +74 -0
  57. package/dist/core/page-store.js.map +1 -0
  58. package/dist/core/path-completer.d.ts +10 -0
  59. package/dist/core/path-completer.d.ts.map +1 -0
  60. package/dist/core/path-completer.js +227 -0
  61. package/dist/core/path-completer.js.map +1 -0
  62. package/dist/core/path-discovery.d.ts +8 -0
  63. package/dist/core/path-discovery.d.ts.map +1 -0
  64. package/dist/core/path-discovery.js +105 -0
  65. package/dist/core/path-discovery.js.map +1 -0
  66. package/dist/core/path-utils.d.ts +9 -0
  67. package/dist/core/path-utils.d.ts.map +1 -0
  68. package/dist/core/path-utils.js +121 -0
  69. package/dist/core/path-utils.js.map +1 -0
  70. package/dist/core/path.d.ts +148 -0
  71. package/dist/core/path.d.ts.map +1 -0
  72. package/dist/core/path.js +674 -0
  73. package/dist/core/path.js.map +1 -0
  74. package/dist/core/primitives.d.ts +23 -0
  75. package/dist/core/primitives.d.ts.map +1 -0
  76. package/dist/core/primitives.js +54 -0
  77. package/dist/core/primitives.js.map +1 -0
  78. package/dist/core/read.d.ts +74 -0
  79. package/dist/core/read.d.ts.map +1 -0
  80. package/dist/core/read.js +562 -0
  81. package/dist/core/read.js.map +1 -0
  82. package/dist/core/schema.d.ts +139 -0
  83. package/dist/core/schema.d.ts.map +1 -0
  84. package/dist/core/schema.js +345 -0
  85. package/dist/core/schema.js.map +1 -0
  86. package/dist/core/search.d.ts +100 -0
  87. package/dist/core/search.d.ts.map +1 -0
  88. package/dist/core/search.js +248 -0
  89. package/dist/core/search.js.map +1 -0
  90. package/dist/core/sensitive.d.ts +6 -0
  91. package/dist/core/sensitive.d.ts.map +1 -0
  92. package/dist/core/sensitive.js +142 -0
  93. package/dist/core/sensitive.js.map +1 -0
  94. package/dist/core/store.d.ts +57 -0
  95. package/dist/core/store.d.ts.map +1 -0
  96. package/dist/core/store.js +162 -0
  97. package/dist/core/store.js.map +1 -0
  98. package/dist/core/util.d.ts +26 -0
  99. package/dist/core/util.d.ts.map +1 -0
  100. package/dist/core/util.js +66 -0
  101. package/dist/core/util.js.map +1 -0
  102. package/dist/core/watcher-registry.d.ts +68 -0
  103. package/dist/core/watcher-registry.d.ts.map +1 -0
  104. package/dist/core/watcher-registry.js +312 -0
  105. package/dist/core/watcher-registry.js.map +1 -0
  106. package/dist/http-policy.d.ts +121 -0
  107. package/dist/http-policy.d.ts.map +1 -0
  108. package/dist/http-policy.js +340 -0
  109. package/dist/http-policy.js.map +1 -0
  110. package/dist/index.d.ts +1 -0
  111. package/dist/index.d.ts.map +1 -0
  112. package/dist/index.js +77 -41
  113. package/dist/index.js.map +1 -0
  114. package/dist/instructions.d.ts +12 -0
  115. package/dist/instructions.d.ts.map +1 -0
  116. package/dist/instructions.js +71 -0
  117. package/dist/instructions.js.map +1 -0
  118. package/dist/prompts.d.ts +8 -6
  119. package/dist/prompts.d.ts.map +1 -0
  120. package/dist/prompts.js +74 -168
  121. package/dist/prompts.js.map +1 -0
  122. package/dist/resources.d.ts +73 -9
  123. package/dist/resources.d.ts.map +1 -0
  124. package/dist/resources.js +392 -163
  125. package/dist/resources.js.map +1 -0
  126. package/dist/server.d.ts +62 -1
  127. package/dist/server.d.ts.map +1 -0
  128. package/dist/server.js +139 -1
  129. package/dist/server.js.map +1 -0
  130. package/dist/tools/batch.d.ts +89 -0
  131. package/dist/tools/batch.d.ts.map +1 -0
  132. package/dist/tools/batch.js +157 -0
  133. package/dist/tools/batch.js.map +1 -0
  134. package/dist/tools/create.d.ts +2 -0
  135. package/dist/tools/create.d.ts.map +1 -0
  136. package/dist/tools/create.js +142 -0
  137. package/dist/tools/create.js.map +1 -0
  138. package/dist/tools/define.d.ts +110 -0
  139. package/dist/tools/define.d.ts.map +1 -0
  140. package/dist/tools/define.js +394 -0
  141. package/dist/tools/define.js.map +1 -0
  142. package/dist/tools/delete-file.d.ts +2 -4
  143. package/dist/tools/delete-file.d.ts.map +1 -0
  144. package/dist/tools/delete-file.js +330 -87
  145. package/dist/tools/delete-file.js.map +1 -0
  146. package/dist/tools/diff.d.ts +2 -0
  147. package/dist/tools/diff.d.ts.map +1 -0
  148. package/dist/tools/diff.js +94 -0
  149. package/dist/tools/diff.js.map +1 -0
  150. package/dist/tools/edit.d.ts +2 -0
  151. package/dist/tools/edit.d.ts.map +1 -0
  152. package/dist/tools/edit.js +442 -0
  153. package/dist/tools/edit.js.map +1 -0
  154. package/dist/tools/index.d.ts +26 -0
  155. package/dist/tools/index.d.ts.map +1 -0
  156. package/dist/tools/index.js +52 -0
  157. package/dist/tools/index.js.map +1 -0
  158. package/dist/tools/list.d.ts +2 -0
  159. package/dist/tools/list.d.ts.map +1 -0
  160. package/dist/tools/list.js +291 -0
  161. package/dist/tools/list.js.map +1 -0
  162. package/dist/tools/move.d.ts +2 -0
  163. package/dist/tools/move.d.ts.map +1 -0
  164. package/dist/tools/move.js +321 -0
  165. package/dist/tools/move.js.map +1 -0
  166. package/dist/tools/patch.d.ts +2 -0
  167. package/dist/tools/patch.d.ts.map +1 -0
  168. package/dist/tools/patch.js +155 -0
  169. package/dist/tools/patch.js.map +1 -0
  170. package/dist/tools/progress.d.ts +52 -0
  171. package/dist/tools/progress.d.ts.map +1 -0
  172. package/dist/tools/progress.js +172 -0
  173. package/dist/tools/progress.js.map +1 -0
  174. package/dist/tools/read.d.ts +2 -4
  175. package/dist/tools/read.d.ts.map +1 -0
  176. package/dist/tools/read.js +398 -152
  177. package/dist/tools/read.js.map +1 -0
  178. package/dist/tools/replace-in-files.d.ts +2 -4
  179. package/dist/tools/replace-in-files.d.ts.map +1 -0
  180. package/dist/tools/replace-in-files.js +435 -264
  181. package/dist/tools/replace-in-files.js.map +1 -0
  182. package/dist/tools/roots.d.ts +2 -4
  183. package/dist/tools/roots.d.ts.map +1 -0
  184. package/dist/tools/roots.js +29 -52
  185. package/dist/tools/roots.js.map +1 -0
  186. package/dist/tools/search-content.d.ts +2 -4
  187. package/dist/tools/search-content.d.ts.map +1 -0
  188. package/dist/tools/search-content.js +262 -228
  189. package/dist/tools/search-content.js.map +1 -0
  190. package/dist/tools/search-files.d.ts +2 -4
  191. package/dist/tools/search-files.d.ts.map +1 -0
  192. package/dist/tools/search-files.js +162 -161
  193. package/dist/tools/search-files.js.map +1 -0
  194. package/dist/tools/stat.d.ts +2 -4
  195. package/dist/tools/stat.d.ts.map +1 -0
  196. package/dist/tools/stat.js +202 -62
  197. package/dist/tools/stat.js.map +1 -0
  198. package/dist/transport/http.d.ts +5 -0
  199. package/dist/transport/http.d.ts.map +1 -0
  200. package/dist/transport/http.js +276 -0
  201. package/dist/transport/http.js.map +1 -0
  202. package/dist/transport/shared.d.ts +45 -0
  203. package/dist/transport/shared.d.ts.map +1 -0
  204. package/dist/transport/shared.js +65 -0
  205. package/dist/transport/shared.js.map +1 -0
  206. package/dist/transport/stdio.d.ts +32 -0
  207. package/dist/transport/stdio.d.ts.map +1 -0
  208. package/dist/transport/stdio.js +279 -0
  209. package/dist/transport/stdio.js.map +1 -0
  210. package/dist/transport.d.ts +5 -0
  211. package/dist/transport.d.ts.map +1 -0
  212. package/dist/transport.js +4 -0
  213. package/dist/transport.js.map +1 -0
  214. package/package.json +37 -46
  215. package/dist/assets/logo.svg +0 -35
  216. package/dist/completions.d.ts +0 -2
  217. package/dist/completions.js +0 -572
  218. package/dist/config.d.ts +0 -119
  219. package/dist/config.js +0 -31
  220. package/dist/lib/abort.d.ts +0 -7
  221. package/dist/lib/abort.js +0 -81
  222. package/dist/lib/constants.d.ts +0 -42
  223. package/dist/lib/constants.js +0 -313
  224. package/dist/lib/errors.d.ts +0 -28
  225. package/dist/lib/errors.js +0 -301
  226. package/dist/lib/file-operations/core.d.ts +0 -53
  227. package/dist/lib/file-operations/core.js +0 -144
  228. package/dist/lib/file-operations/metadata.d.ts +0 -77
  229. package/dist/lib/file-operations/metadata.js +0 -878
  230. package/dist/lib/file-operations/search.d.ts +0 -46
  231. package/dist/lib/file-operations/search.js +0 -1194
  232. package/dist/lib/file-operations/traversal.d.ts +0 -40
  233. package/dist/lib/file-operations/traversal.js +0 -310
  234. package/dist/lib/fs-helpers.d.ts +0 -46
  235. package/dist/lib/fs-helpers.js +0 -606
  236. package/dist/lib/globs.d.ts +0 -2
  237. package/dist/lib/globs.js +0 -19
  238. package/dist/lib/logger.d.ts +0 -27
  239. package/dist/lib/logger.js +0 -91
  240. package/dist/lib/observability.d.ts +0 -33
  241. package/dist/lib/observability.js +0 -373
  242. package/dist/lib/paths.d.ts +0 -35
  243. package/dist/lib/paths.js +0 -634
  244. package/dist/lib/resource-store.d.ts +0 -28
  245. package/dist/lib/resource-store.js +0 -195
  246. package/dist/lib/utils.d.ts +0 -7
  247. package/dist/lib/utils.js +0 -37
  248. package/dist/lib/zod-codecs.d.ts +0 -2
  249. package/dist/lib/zod-codecs.js +0 -18
  250. package/dist/pkg-info.d.ts +0 -7
  251. package/dist/pkg-info.js +0 -9
  252. package/dist/resources/generated-instructions.d.ts +0 -1
  253. package/dist/resources/generated-instructions.js +0 -105
  254. package/dist/resources/tool-catalog.d.ts +0 -2
  255. package/dist/resources/tool-catalog.js +0 -78
  256. package/dist/resources/tool-info.d.ts +0 -9
  257. package/dist/resources/tool-info.js +0 -206
  258. package/dist/resources/workflows.d.ts +0 -1
  259. package/dist/resources/workflows.js +0 -72
  260. package/dist/schemas.d.ts +0 -505
  261. package/dist/schemas.js +0 -676
  262. package/dist/server/bootstrap.d.ts +0 -6
  263. package/dist/server/bootstrap.js +0 -585
  264. package/dist/server/event-store.d.ts +0 -18
  265. package/dist/server/event-store.js +0 -71
  266. package/dist/server/roots-manager.d.ts +0 -28
  267. package/dist/server/roots-manager.js +0 -227
  268. package/dist/server/task-store.d.ts +0 -10
  269. package/dist/server/task-store.js +0 -91
  270. package/dist/tools/apply-patch.d.ts +0 -4
  271. package/dist/tools/apply-patch.js +0 -235
  272. package/dist/tools/calculate-hash.d.ts +0 -4
  273. package/dist/tools/calculate-hash.js +0 -187
  274. package/dist/tools/contract.d.ts +0 -50
  275. package/dist/tools/contract.js +0 -1
  276. package/dist/tools/create-directory.d.ts +0 -4
  277. package/dist/tools/create-directory.js +0 -68
  278. package/dist/tools/diff-files.d.ts +0 -4
  279. package/dist/tools/diff-files.js +0 -136
  280. package/dist/tools/edit-file.d.ts +0 -4
  281. package/dist/tools/edit-file.js +0 -256
  282. package/dist/tools/icons.d.ts +0 -15
  283. package/dist/tools/icons.js +0 -24
  284. package/dist/tools/list-directory.d.ts +0 -4
  285. package/dist/tools/list-directory.js +0 -218
  286. package/dist/tools/move-file.d.ts +0 -4
  287. package/dist/tools/move-file.js +0 -178
  288. package/dist/tools/read-multiple.d.ts +0 -4
  289. package/dist/tools/read-multiple.js +0 -207
  290. package/dist/tools/shared.d.ts +0 -172
  291. package/dist/tools/shared.js +0 -599
  292. package/dist/tools/stat-many.d.ts +0 -4
  293. package/dist/tools/stat-many.js +0 -104
  294. package/dist/tools/task-support.d.ts +0 -29
  295. package/dist/tools/task-support.js +0 -473
  296. package/dist/tools/tree.d.ts +0 -4
  297. package/dist/tools/tree.js +0 -102
  298. package/dist/tools/write-file.d.ts +0 -4
  299. package/dist/tools/write-file.js +0 -60
  300. package/dist/tools.d.ts +0 -5
  301. package/dist/tools.js +0 -54
@@ -1,878 +0,0 @@
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';
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';
5
- import { isAbortError } from '../errors.js';
6
- import { getFileType, isHidden, processInParallel, readFile, readFileWithStats, } from '../fs-helpers.js';
7
- import { assertSafeGlobPattern } from '../globs.js';
8
- import { assertAllowedFileAccess, isPathWithinDirectories, isSensitivePath, normalizePath, toPosixPath, validateExistingDirectory, validateExistingPath, validateExistingPathDetailed, } from '../paths.js';
9
- import { applyIndexedErrors, applyIndexedValues, isEntryAccessibleByType, isIgnoredByGitignore, loadRootGitignore, needsStatsForSort, resolveEntryType, resolveStopReason, withOptionalStoppedReason, } from './core.js';
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
- }
51
- const ACCESS_DEPS = {
52
- normalizePath,
53
- isPathWithinDirectories,
54
- isSensitivePath,
55
- validateSymlinkPath: validateExistingPathDetailed,
56
- };
57
- const PERM_STRINGS = [
58
- '---',
59
- '--x',
60
- '-w-',
61
- '-wx',
62
- 'r--',
63
- 'r-x',
64
- 'rw-',
65
- 'rwx',
66
- ];
67
- const UNKNOWN_PATH = '(unknown)';
68
- function getPermissions(mode) {
69
- return ((PERM_STRINGS[(mode >> 6) & 0b111] ?? '---') +
70
- (PERM_STRINGS[(mode >> 3) & 0b111] ?? '---') +
71
- (PERM_STRINGS[mode & 0b111] ?? '---'));
72
- }
73
- function buildFileInfoResult(name, requestedPath, isSymlink, stats, mimeType, symlinkTarget) {
74
- const tokenEstimate = stats.isFile() ? Math.ceil(stats.size / 4) : undefined;
75
- return {
76
- name,
77
- path: requestedPath,
78
- type: isSymlink ? 'symlink' : getFileType(stats),
79
- size: stats.size,
80
- ...(tokenEstimate !== undefined ? { tokenEstimate } : {}),
81
- created: stats.birthtime,
82
- modified: stats.mtime,
83
- accessed: stats.atime,
84
- permissions: getPermissions(stats.mode),
85
- isHidden: isHidden(name),
86
- ...(mimeType !== undefined ? { mimeType } : {}),
87
- ...(symlinkTarget !== undefined ? { symlinkTarget } : {}),
88
- };
89
- }
90
- async function getSymlinkTarget(pathToRead, signal) {
91
- assertNotAborted(signal);
92
- try {
93
- return await withAbort(readlinkAsync(pathToRead), signal);
94
- }
95
- catch (error) {
96
- if (isAbortError(error))
97
- throw error;
98
- return undefined;
99
- }
100
- }
101
- export async function getFileInfo(filePath, options = {}) {
102
- const { signal } = options;
103
- assertNotAborted(signal);
104
- const { requestedPath, resolvedPath, isSymlink } = await validateExistingPathDetailed(filePath, signal);
105
- assertAllowedFileAccess(requestedPath, resolvedPath);
106
- const { base: name, ext: rawExt } = parse(requestedPath);
107
- const ext = rawExt.toLowerCase();
108
- const includeMimeType = options.includeMimeType !== false;
109
- const mimeType = includeMimeType && ext.length > 0 ? getMimeType(ext) : undefined;
110
- const symlinkTarget = isSymlink
111
- ? await getSymlinkTarget(requestedPath, signal)
112
- : undefined;
113
- const stats = await withAbort(statAsync(resolvedPath), signal);
114
- return buildFileInfoResult(name, requestedPath, isSymlink, stats, mimeType, symlinkTarget);
115
- }
116
- function buildEmptyResult() {
117
- return {
118
- results: [],
119
- summary: { total: 0, succeeded: 0, failed: 0, totalSize: 0 },
120
- };
121
- }
122
- function buildIndexedPathTasks(paths) {
123
- const tasks = [];
124
- for (let index = 0; index < paths.length; index += 1) {
125
- const filePath = paths[index];
126
- if (filePath !== undefined) {
127
- tasks.push({ filePath, index });
128
- }
129
- }
130
- return tasks;
131
- }
132
- async function readFileInfoInParallel(paths, options) {
133
- return processInParallel(buildIndexedPathTasks(paths), async ({ filePath, index }) => {
134
- const info = await getFileInfo(filePath, options);
135
- options.onProgress?.();
136
- return { index, value: { path: filePath, info } };
137
- }, PARALLEL_CONCURRENCY, options.signal);
138
- }
139
- function calculateSummary(results) {
140
- let succeeded = 0;
141
- let failed = 0;
142
- let totalSize = 0;
143
- for (const result of results) {
144
- if (result.info !== undefined) {
145
- succeeded++;
146
- totalSize += result.info.size;
147
- }
148
- else {
149
- failed++;
150
- }
151
- }
152
- return {
153
- total: results.length,
154
- succeeded,
155
- failed,
156
- totalSize,
157
- };
158
- }
159
- export async function getMultipleFileInfo(paths, options = {}) {
160
- if (paths.length === 0)
161
- return buildEmptyResult();
162
- const output = Array.from(paths, (p) => ({
163
- path: p,
164
- }));
165
- const { results, errors } = await readFileInfoInParallel(paths, options);
166
- applyIndexedValues(output, results);
167
- applyIndexedErrors({
168
- output,
169
- errors,
170
- resolveIndex: (failureIndex) => failureIndex >= 0 && failureIndex < output.length
171
- ? failureIndex
172
- : undefined,
173
- buildValue: (resolvedIndex, error) => ({
174
- path: paths[resolvedIndex] ?? UNKNOWN_PATH,
175
- error,
176
- }),
177
- });
178
- return {
179
- results: output,
180
- summary: calculateSummary(output),
181
- };
182
- }
183
- function normalizeListOptions(options) {
184
- const normalized = {
185
- includeHidden: options.includeHidden ?? false,
186
- excludePatterns: options.excludePatterns ?? [],
187
- maxDepth: options.maxDepth ?? DEFAULT_MAX_DEPTH,
188
- maxEntries: options.maxEntries ?? DEFAULT_LIST_MAX_ENTRIES,
189
- sortBy: options.sortBy ?? 'name',
190
- includeSymlinkTargets: options.includeSymlinkTargets ?? false,
191
- timeoutMs: options.timeoutMs ?? DEFAULT_SEARCH_TIMEOUT_MS,
192
- };
193
- if (options.pattern && options.pattern.length > 0) {
194
- assertSafeGlobPattern(options.pattern);
195
- normalized.pattern = options.pattern;
196
- }
197
- return normalized;
198
- }
199
- const SORT_COMPARATORS = {
200
- name: (a, b) => a.name.localeCompare(b.name),
201
- type: (a, b) => a.type.localeCompare(b.type),
202
- size: (a, b) => (a.size ?? 0) - (b.size ?? 0),
203
- modified: (a, b) => (a.modified?.getTime() ?? 0) - (b.modified?.getTime() ?? 0),
204
- };
205
- function sortEntries(entries, sortBy) {
206
- entries.sort(SORT_COMPARATORS[sortBy]);
207
- }
208
- function resolveMaxDepth(normalized) {
209
- return normalized.pattern ? normalized.maxDepth : 1;
210
- }
211
- async function* readDirectoryEntries(basePath, normalized, needsStats, signal) {
212
- const dirents = await withAbort(readdirAsync(basePath, { withFileTypes: true }), signal);
213
- if (!needsStats) {
214
- for (const dirent of dirents) {
215
- if (!normalized.includeHidden && isHidden(dirent.name))
216
- continue;
217
- yield { path: join(basePath, dirent.name), dirent };
218
- }
219
- return;
220
- }
221
- const filtered = [];
222
- for (const dirent of dirents) {
223
- if (!normalized.includeHidden && isHidden(dirent.name))
224
- continue;
225
- filtered.push({ dirent, entryPath: join(basePath, dirent.name) });
226
- }
227
- const { results, errors } = await processInParallel(filtered, async ({ entryPath, dirent }) => ({
228
- path: entryPath,
229
- dirent,
230
- stats: await withAbort(lstatAsync(entryPath), signal),
231
- }), PARALLEL_CONCURRENCY, signal);
232
- if (errors.length > 0) {
233
- throw errors[0]?.error ?? new Error('Failed to read entry stats');
234
- }
235
- yield* results;
236
- }
237
- function createEntryStream(basePath, normalized, maxDepth, needsStats, signal) {
238
- if (shouldUseFastPath(normalized, maxDepth)) {
239
- return readDirectoryEntries(basePath, normalized, needsStats, signal);
240
- }
241
- return globEntries({
242
- cwd: basePath,
243
- pattern: normalized.pattern ?? '*',
244
- excludePatterns: normalized.excludePatterns,
245
- includeHidden: normalized.includeHidden,
246
- baseNameMatch: false,
247
- caseSensitiveMatch: true,
248
- maxDepth,
249
- followSymbolicLinks: false,
250
- onlyFiles: false,
251
- stats: needsStats,
252
- });
253
- }
254
- function shouldUseFastPath(normalized, maxDepth) {
255
- return (!normalized.pattern &&
256
- normalized.excludePatterns.length === 0 &&
257
- maxDepth === 1);
258
- }
259
- function resolveRelativePath(basePath, entryPath) {
260
- return relative(basePath, entryPath) || basename(entryPath);
261
- }
262
- async function resolveSymlinkTarget(entryType, includeSymlinkTargets, entryPath) {
263
- if (entryType !== 'symlink' || !includeSymlinkTargets) {
264
- return undefined;
265
- }
266
- return readlinkAsync(entryPath).catch(() => undefined);
267
- }
268
- function updateTotals(entryType, totals) {
269
- if (entryType === 'file')
270
- totals.files += 1;
271
- if (entryType === 'directory')
272
- totals.directories += 1;
273
- }
274
- function buildDirectoryEntry(basePath, entry, entryType, needsStats, symlinkTarget) {
275
- const size = needsStats && entry.stats?.isFile() ? entry.stats.size : undefined;
276
- const modified = needsStats ? entry.stats?.mtime : undefined;
277
- return {
278
- name: basename(entry.path),
279
- path: entry.path,
280
- relativePath: resolveRelativePath(basePath, entry.path),
281
- type: entryType,
282
- ...(size !== undefined ? { size } : {}),
283
- ...(modified !== undefined ? { modified } : {}),
284
- ...(symlinkTarget !== undefined ? { symlinkTarget } : {}),
285
- };
286
- }
287
- function trackSymlink(entryType, includeSymlinkTargets, counters) {
288
- if (entryType === 'symlink' && !includeSymlinkTargets) {
289
- counters.symlinksNotFollowed += 1;
290
- }
291
- }
292
- function appendEntry(entry, entryType, symlinkTarget, ctx) {
293
- updateTotals(entryType, ctx.totals);
294
- ctx.entries.push(buildDirectoryEntry(ctx.basePath, entry, entryType, ctx.needsStats, symlinkTarget));
295
- }
296
- async function enqueueAppendEntry(entry, entryType, ctx, pending, flushPending) {
297
- if (!ctx.includeSymlinkTargets) {
298
- appendEntry(entry, entryType, undefined, ctx);
299
- return;
300
- }
301
- pending.push(resolveSymlinkTarget(entryType, true, entry.path).then((target) => {
302
- appendEntry(entry, entryType, target, ctx);
303
- }));
304
- if (pending.length >= PARALLEL_CONCURRENCY) {
305
- await flushPending();
306
- }
307
- }
308
- function buildSummary(entries, totals, maxDepth, truncated, stoppedReason, counters) {
309
- const summary = {
310
- totalEntries: entries.length,
311
- entriesScanned: entries.length,
312
- entriesVisible: entries.length,
313
- totalFiles: totals.files,
314
- totalDirectories: totals.directories,
315
- maxDepthReached: maxDepth,
316
- truncated,
317
- skippedInaccessible: counters.skippedInaccessible,
318
- symlinksNotFollowed: counters.symlinksNotFollowed,
319
- };
320
- return withOptionalStoppedReason(summary, stoppedReason);
321
- }
322
- async function collectEntries(basePath, normalized, signal, needsStats, maxDepth) {
323
- const entries = [];
324
- const totals = { files: 0, directories: 0 };
325
- const counters = { skippedInaccessible: 0, symlinksNotFollowed: 0 };
326
- const basePathDirectories = [basePath];
327
- let truncated = false;
328
- let stoppedReason;
329
- const pending = [];
330
- let acceptedCount = 0;
331
- const stream = createEntryStream(basePath, normalized, maxDepth, needsStats, signal);
332
- const flushPending = async () => {
333
- if (pending.length === 0)
334
- return;
335
- await Promise.allSettled(pending.splice(0));
336
- };
337
- const appendCtx = {
338
- basePath,
339
- needsStats,
340
- includeSymlinkTargets: normalized.includeSymlinkTargets,
341
- totals,
342
- entries,
343
- };
344
- for await (const entry of stream) {
345
- const stopReason = resolveStopReason({
346
- signal,
347
- current: acceptedCount,
348
- max: normalized.maxEntries,
349
- abortedReason: 'aborted',
350
- maxReason: 'maxEntries',
351
- });
352
- if (stopReason) {
353
- truncated = true;
354
- stoppedReason = stopReason;
355
- break;
356
- }
357
- const entryType = resolveEntryType(entry.dirent);
358
- trackSymlink(entryType, normalized.includeSymlinkTargets, counters);
359
- const accessible = await isEntryAccessibleByType(entry.path, entryType, basePathDirectories, signal, ACCESS_DEPS);
360
- if (!accessible) {
361
- counters.skippedInaccessible += 1;
362
- continue;
363
- }
364
- acceptedCount += 1;
365
- await enqueueAppendEntry(entry, entryType, appendCtx, pending, flushPending);
366
- }
367
- if (normalized.includeSymlinkTargets) {
368
- await flushPending();
369
- }
370
- return { entries, totals, truncated, stoppedReason, counters };
371
- }
372
- async function executeListDirectory(basePath, normalized, signal) {
373
- const needsStats = needsStatsForSort(normalized.sortBy);
374
- const maxDepth = resolveMaxDepth(normalized);
375
- const { entries, totals, truncated, stoppedReason, counters } = await collectEntries(basePath, normalized, signal, needsStats, maxDepth);
376
- sortEntries(entries, normalized.sortBy);
377
- return {
378
- entries,
379
- summary: buildSummary(entries, totals, maxDepth, truncated, stoppedReason, counters),
380
- };
381
- }
382
- export async function listDirectory(dirPath, options = {}) {
383
- const normalized = normalizeListOptions(options);
384
- return withTimedAbortSignal(options.signal, normalized.timeoutMs, async (signal) => {
385
- const basePath = await validateExistingDirectory(dirPath, signal);
386
- const { entries, summary } = await executeListDirectory(basePath, normalized, signal);
387
- return { path: basePath, entries, summary };
388
- });
389
- }
390
- function clampInt(value, fallback, min) {
391
- if (typeof value !== 'number' || !Number.isFinite(value))
392
- return fallback;
393
- const asInt = Math.floor(value);
394
- return asInt >= min ? asInt : fallback;
395
- }
396
- function normalizeTreeOptions(options) {
397
- return {
398
- maxDepth: clampInt(options.maxDepth, 5, 0),
399
- maxEntries: clampInt(options.maxEntries, 1000, 0),
400
- includeHidden: options.includeHidden ?? false,
401
- includeIgnored: options.includeIgnored ?? false,
402
- includeSizes: options.includeSizes ?? false,
403
- timeoutMs: clampInt(options.timeoutMs, DEFAULT_SEARCH_TIMEOUT_MS, 1),
404
- };
405
- }
406
- function ensureParentNodes(rootNode, nodeByPath, relativePath) {
407
- const normalized = toPosixPath(relativePath);
408
- if (normalized.length === 0 || normalized === '.')
409
- return rootNode;
410
- const segments = normalized.split('/').filter(Boolean);
411
- const parentSegmentCount = Math.max(0, segments.length - 1);
412
- let current = rootNode;
413
- let currentPath = '';
414
- for (let index = 0; index < parentSegmentCount; index += 1) {
415
- const segment = segments[index];
416
- if (!segment)
417
- continue;
418
- currentPath =
419
- currentPath.length === 0 ? segment : `${currentPath}/${segment}`;
420
- let child = nodeByPath.get(currentPath);
421
- if (!child) {
422
- child = {
423
- name: segment,
424
- type: 'directory',
425
- relativePath: currentPath,
426
- children: [],
427
- };
428
- nodeByPath.set(currentPath, child);
429
- current.children ??= [];
430
- current.children.push(child);
431
- }
432
- current = child;
433
- }
434
- return current;
435
- }
436
- function sortTree(node) {
437
- if (!node.children)
438
- return;
439
- node.children.sort(compareTreeEntries);
440
- for (const child of node.children) {
441
- sortTree(child);
442
- }
443
- }
444
- function compareTreeEntries(a, b) {
445
- const diff = getTreeTypeRank(a.type) - getTreeTypeRank(b.type);
446
- if (diff !== 0)
447
- return diff;
448
- return a.name.localeCompare(b.name);
449
- }
450
- const TREE_TYPE_RANKS = { directory: 0, file: 1 };
451
- function getTreeTypeRank(type) {
452
- return TREE_TYPE_RANKS[type] ?? 2;
453
- }
454
- async function resolveTreeEntry(entry, root, rootDirectories, gitignoreMatcher, signal, accessDeps) {
455
- const type = resolveEntryType(entry.dirent);
456
- const isAccessible = await isEntryAccessibleByType(entry.path, type, rootDirectories, signal, accessDeps);
457
- if (!isAccessible) {
458
- return null;
459
- }
460
- if (gitignoreMatcher &&
461
- isIgnoredByGitignore(gitignoreMatcher, root, entry.path, {
462
- isDirectory: type === 'directory',
463
- })) {
464
- return null;
465
- }
466
- const relativePosix = toPosixPath(resolveRelativePath(root, entry.path));
467
- const name = basename(entry.path);
468
- return { type, relativePosix, name };
469
- }
470
- function upsertChildNode(parent, nodeByPath, resolved, childPathIndexByParent) {
471
- const ensureDirectoryShape = (node) => {
472
- if (node.type === 'directory') {
473
- node.children ??= [];
474
- }
475
- else {
476
- delete node.children;
477
- }
478
- };
479
- const maybeUpdateType = (existing, nextType) => {
480
- if (existing.type === nextType)
481
- return;
482
- const preservePopulatedDirectory = existing.type === 'directory' &&
483
- Array.isArray(existing.children) &&
484
- existing.children.length > 0;
485
- if (preservePopulatedDirectory)
486
- return;
487
- existing.type = nextType;
488
- ensureDirectoryShape(existing);
489
- };
490
- const attachChild = (child) => {
491
- parent.children ??= [];
492
- let seen = childPathIndexByParent.get(parent);
493
- if (!seen) {
494
- seen = new Set();
495
- for (const entry of parent.children) {
496
- seen.add(entry.relativePath);
497
- }
498
- childPathIndexByParent.set(parent, seen);
499
- }
500
- const key = child.relativePath;
501
- if (seen.has(key))
502
- return;
503
- seen.add(key);
504
- parent.children.push(child);
505
- };
506
- const existing = nodeByPath.get(resolved.relativePosix);
507
- if (existing) {
508
- // Avoid duplicate directory nodes when a child file is encountered before the directory entry.
509
- // Prefer preserving an existing populated directory node over overwriting it.
510
- maybeUpdateType(existing, resolved.type);
511
- existing.name = resolved.name;
512
- existing.relativePath = resolved.relativePosix;
513
- ensureDirectoryShape(existing);
514
- attachChild(existing);
515
- return;
516
- }
517
- const node = {
518
- name: resolved.name,
519
- type: resolved.type,
520
- relativePath: resolved.relativePosix,
521
- ...(resolved.type === 'directory' ? { children: [] } : {}),
522
- };
523
- nodeByPath.set(resolved.relativePosix, node);
524
- attachChild(node);
525
- }
526
- export function formatTreeAscii(tree) {
527
- const lines = [];
528
- const walk = (node, prefix, isLast, isRoot) => {
529
- let connector = '';
530
- let linePrefix = '';
531
- if (!isRoot) {
532
- connector = isLast ? '└── ' : '├── ';
533
- linePrefix = prefix;
534
- }
535
- lines.push(`${linePrefix}${connector}${node.name}`);
536
- if (!node.children || node.children.length === 0)
537
- return;
538
- let nextPrefix = '';
539
- if (!isRoot) {
540
- const continuation = isLast ? ' ' : '│ ';
541
- nextPrefix = `${prefix}${continuation}`;
542
- }
543
- const count = node.children.length;
544
- for (let index = 0; index < count; index += 1) {
545
- const child = node.children[index];
546
- if (child) {
547
- walk(child, nextPrefix, index === count - 1, false);
548
- }
549
- }
550
- };
551
- walk(tree, '', true, true);
552
- return lines.join('\n');
553
- }
554
- export async function treeDirectory(dirPath, options = {}) {
555
- const normalized = normalizeTreeOptions(options);
556
- return withTimedAbortSignal(options.signal, normalized.timeoutMs, async (signal) => {
557
- const root = await validateExistingDirectory(dirPath, signal);
558
- const rootNormalized = normalizePath(root);
559
- const rootDirectories = [rootNormalized];
560
- const excludePatterns = normalized.includeIgnored
561
- ? []
562
- : DEFAULT_EXCLUDE_PATTERNS;
563
- const gitignoreMatcher = normalized.includeIgnored
564
- ? null
565
- : await loadRootGitignore(root, signal);
566
- const rootNode = {
567
- name: basename(root) || root,
568
- type: 'directory',
569
- relativePath: '.',
570
- children: [],
571
- };
572
- const nodeByPath = new Map();
573
- const childPathIndexByParent = new WeakMap();
574
- let totalEntries = 0;
575
- let truncated = false;
576
- const stream = globEntries({
577
- cwd: root,
578
- pattern: '**/*',
579
- excludePatterns,
580
- includeHidden: normalized.includeHidden,
581
- baseNameMatch: false,
582
- caseSensitiveMatch: true,
583
- maxDepth: normalized.maxDepth,
584
- followSymbolicLinks: false,
585
- onlyFiles: false,
586
- stats: normalized.includeSizes,
587
- suppressErrors: true,
588
- });
589
- for await (const entry of stream) {
590
- const stopReason = resolveStopReason({
591
- signal,
592
- current: totalEntries,
593
- max: normalized.maxEntries,
594
- abortedReason: 'aborted',
595
- maxReason: 'maxEntries',
596
- });
597
- if (stopReason) {
598
- truncated = true;
599
- break;
600
- }
601
- const resolved = await resolveTreeEntry(entry, root, rootDirectories, gitignoreMatcher, signal, ACCESS_DEPS);
602
- if (!resolved) {
603
- continue;
604
- }
605
- const parent = ensureParentNodes(rootNode, nodeByPath, resolved.relativePosix);
606
- upsertChildNode(parent, nodeByPath, resolved, childPathIndexByParent);
607
- if (normalized.includeSizes &&
608
- resolved.type === 'file' &&
609
- entry.stats) {
610
- const node = nodeByPath.get(resolved.relativePosix);
611
- if (node) {
612
- node.size = entry.stats.size;
613
- }
614
- }
615
- totalEntries += 1;
616
- options.onProgress?.({ current: totalEntries });
617
- }
618
- sortTree(rootNode);
619
- return {
620
- root,
621
- tree: rootNode,
622
- truncated,
623
- totalEntries,
624
- };
625
- });
626
- }
627
- function estimateReadSize(stats, maxSize) {
628
- // `readFile`/`readFileWithStats` are always invoked with a `maxSize` cap, so the
629
- // combined budget should reflect the maximum number of bytes we might actually read.
630
- return Math.min(stats.size, maxSize);
631
- }
632
- function buildReadOptions(options) {
633
- const readOptions = {
634
- encoding: options.encoding,
635
- maxSize: options.maxSize,
636
- skipBinary: true,
637
- };
638
- applyLineSelection(readOptions, options);
639
- return readOptions;
640
- }
641
- function buildReadMultipleResult(filePath, result) {
642
- const output = {
643
- path: filePath,
644
- content: result.content,
645
- truncated: result.truncated,
646
- readMode: result.readMode,
647
- };
648
- if (result.totalLines !== undefined)
649
- output.totalLines = result.totalLines;
650
- if (result.head !== undefined)
651
- output.head = result.head;
652
- if (result.tail !== undefined)
653
- output.tail = result.tail;
654
- if (result.startLine !== undefined)
655
- output.startLine = result.startLine;
656
- if (result.endLine !== undefined)
657
- output.endLine = result.endLine;
658
- if (result.linesRead !== undefined)
659
- output.linesRead = result.linesRead;
660
- if (result.hasMoreLines !== undefined) {
661
- output.hasMoreLines = result.hasMoreLines;
662
- }
663
- return output;
664
- }
665
- async function readSingleFile(task, readOptions) {
666
- const { filePath, index, validPath, stats } = task;
667
- const result = validPath && stats
668
- ? await readFileWithStats(filePath, validPath, stats, readOptions)
669
- : await readFile(filePath, readOptions);
670
- return {
671
- index,
672
- value: buildReadMultipleResult(filePath, result),
673
- };
674
- }
675
- async function readFilesInParallel(filesToProcess, options, signal, onReadComplete) {
676
- const readOptions = buildReadOptions(options);
677
- if (signal) {
678
- readOptions.signal = signal;
679
- }
680
- return processInParallel(filesToProcess, async (task) => {
681
- const result = await readSingleFile(task, readOptions);
682
- onReadComplete?.();
683
- return result;
684
- }, PARALLEL_CONCURRENCY, signal);
685
- }
686
- function normalizeReadMultipleOptions(options) {
687
- const normalized = {
688
- encoding: options.encoding ?? 'utf-8',
689
- maxSize: Math.min(options.maxSize ?? MAX_TEXT_FILE_SIZE, MAX_TEXT_FILE_SIZE),
690
- maxTotalSize: options.maxTotalSize ?? DEFAULT_READ_MANY_MAX_TOTAL_SIZE,
691
- };
692
- applyLineSelection(normalized, options);
693
- return normalized;
694
- }
695
- function applyLineSelection(target, source) {
696
- if (source.head !== undefined)
697
- target.head = source.head;
698
- if (source.tail !== undefined)
699
- target.tail = source.tail;
700
- if (source.endLine !== undefined) {
701
- target.startLine = source.startLine ?? 1;
702
- target.endLine = source.endLine;
703
- }
704
- else if (source.startLine !== undefined) {
705
- target.startLine = source.startLine;
706
- }
707
- }
708
- function resolveNormalizedReadOptions(options) {
709
- const { signal, ...rest } = options;
710
- const result = {
711
- normalized: normalizeReadMultipleOptions(rest),
712
- };
713
- if (signal)
714
- result.signal = signal;
715
- return result;
716
- }
717
- async function validateFile(filePath, index, signal) {
718
- const validPath = await validateExistingPath(filePath, signal);
719
- const stats = await withAbort(statAsync(validPath), signal);
720
- return { filePath, index, validPath, stats };
721
- }
722
- function markRemainingSkipped(startIndex, total, skippedBudget) {
723
- for (let index = startIndex; index < total; index += 1) {
724
- skippedBudget.add(index);
725
- }
726
- }
727
- async function tryValidateFile(filePath, index, signal) {
728
- try {
729
- return await validateFile(filePath, index, signal);
730
- }
731
- catch {
732
- return undefined;
733
- }
734
- }
735
- async function validateBatch(tasks, signal) {
736
- if (tasks.length === 0)
737
- return new Map();
738
- const { results } = await processInParallel(tasks, async (task) => tryValidateFile(task.filePath, task.index, signal), PARALLEL_CONCURRENCY, signal);
739
- const infos = new Map();
740
- for (const info of results) {
741
- if (!info)
742
- continue;
743
- infos.set(info.index, info);
744
- }
745
- return infos;
746
- }
747
- function applyBudgetForRange(options) {
748
- const { batchStart, batchEnd, totalFiles, maxTotalSize, maxSize, validated, skippedBudget, totalSize: startingTotalSize, } = options;
749
- let totalSize = startingTotalSize;
750
- for (let index = batchStart; index < batchEnd; index += 1) {
751
- const info = validated.get(index);
752
- if (!info)
753
- continue;
754
- const { exceeded, totalSize: nextTotalSize } = applyBudget(totalSize, estimateReadSize(info.stats, maxSize), maxTotalSize, index, totalFiles, skippedBudget);
755
- if (exceeded) {
756
- return { totalSize, exceeded: true };
757
- }
758
- totalSize = nextTotalSize;
759
- }
760
- return { totalSize, exceeded: false };
761
- }
762
- async function collectFileBudget(filePaths, maxTotalSize, maxSize, signal) {
763
- const skippedBudget = new Set();
764
- const validated = new Map();
765
- let totalSize = 0;
766
- const totalFiles = filePaths.length;
767
- for (let batchStart = 0; batchStart < totalFiles; batchStart += PARALLEL_CONCURRENCY) {
768
- const batchTasks = [];
769
- const batchEnd = Math.min(batchStart + PARALLEL_CONCURRENCY, totalFiles);
770
- for (let index = batchStart; index < batchEnd; index += 1) {
771
- const filePath = filePaths[index];
772
- if (!filePath)
773
- continue;
774
- if (validated.has(index))
775
- continue;
776
- batchTasks.push({ filePath, index });
777
- }
778
- const batchInfos = await validateBatch(batchTasks, signal);
779
- for (const [index, info] of batchInfos) {
780
- validated.set(index, info);
781
- }
782
- const budgetResult = applyBudgetForRange({
783
- batchStart,
784
- batchEnd,
785
- totalFiles,
786
- maxTotalSize,
787
- maxSize,
788
- validated,
789
- skippedBudget,
790
- totalSize,
791
- });
792
- const { exceeded, totalSize: nextTotalSize } = budgetResult;
793
- if (exceeded) {
794
- return { skippedBudget, validated };
795
- }
796
- totalSize = nextTotalSize;
797
- }
798
- return { skippedBudget, validated };
799
- }
800
- function buildOutput(filePaths) {
801
- return Array.from(filePaths, (fp) => ({ path: fp }));
802
- }
803
- function resolveErrorOriginalIndex(failureIndex, filesToProcess, totalInputFiles) {
804
- // processInParallel implementations vary: some return error indices relative to
805
- // the submitted batch (filesToProcess), others may forward the task/index.
806
- const batchIndex = filesToProcess[failureIndex]?.index;
807
- if (typeof batchIndex === 'number' &&
808
- batchIndex >= 0 &&
809
- batchIndex < totalInputFiles) {
810
- return batchIndex;
811
- }
812
- if (failureIndex >= 0 && failureIndex < totalInputFiles) {
813
- return failureIndex;
814
- }
815
- return undefined;
816
- }
817
- function buildFilesToProcess(filePaths, validated, skippedBudget) {
818
- const filesToProcess = [];
819
- for (let index = 0; index < filePaths.length; index += 1) {
820
- if (skippedBudget.has(index))
821
- continue;
822
- const filePath = filePaths[index];
823
- if (!filePath)
824
- continue;
825
- const cached = validated.get(index);
826
- if (cached) {
827
- filesToProcess.push({
828
- filePath,
829
- index,
830
- validPath: cached.validPath,
831
- stats: cached.stats,
832
- });
833
- continue;
834
- }
835
- filesToProcess.push({ filePath, index });
836
- }
837
- return filesToProcess;
838
- }
839
- function applyBudget(totalSize, estimatedSize, maxTotalSize, index, totalFiles, skippedBudget) {
840
- if (totalSize + estimatedSize > maxTotalSize) {
841
- skippedBudget.add(index);
842
- markRemainingSkipped(index + 1, totalFiles, skippedBudget);
843
- return { totalSize, exceeded: true };
844
- }
845
- return { totalSize: totalSize + estimatedSize, exceeded: false };
846
- }
847
- function applySkippedBudget(output, skippedBudget, filePaths, maxTotalSize) {
848
- for (const index of skippedBudget) {
849
- const filePath = filePaths[index];
850
- if (!filePath)
851
- continue;
852
- output[index] = {
853
- path: filePath,
854
- error: new Error(`Skipped: combined estimated read would exceed maxTotalSize (${maxTotalSize} bytes)`),
855
- };
856
- }
857
- }
858
- export async function readMultipleFiles(filePaths, options = {}) {
859
- if (filePaths.length === 0)
860
- return [];
861
- const { normalized, signal } = resolveNormalizedReadOptions(options);
862
- const output = buildOutput(filePaths);
863
- const { skippedBudget, validated } = await collectFileBudget(filePaths, normalized.maxTotalSize, normalized.maxSize, signal);
864
- const filesToProcess = buildFilesToProcess(filePaths, validated, skippedBudget);
865
- const { results, errors } = await readFilesInParallel(filesToProcess, normalized, signal, options.onReadComplete);
866
- applyIndexedValues(output, results);
867
- applyIndexedErrors({
868
- output,
869
- errors,
870
- resolveIndex: (failureIndex) => resolveErrorOriginalIndex(failureIndex, filesToProcess, filePaths.length),
871
- buildValue: (resolvedIndex, error) => ({
872
- path: filePaths[resolvedIndex] ?? UNKNOWN_PATH,
873
- error,
874
- }),
875
- });
876
- applySkippedBudget(output, skippedBudget, filePaths, normalized.maxTotalSize);
877
- return output;
878
- }