@j0hanz/filesystem-mcp 1.11.0 → 1.12.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 (42) hide show
  1. package/dist/completions.js +25 -0
  2. package/dist/lib/constants.d.ts +2 -0
  3. package/dist/lib/constants.js +2 -0
  4. package/dist/lib/file-operations/metadata.js +71 -142
  5. package/dist/lib/file-operations/search.js +119 -19
  6. package/dist/lib/file-operations/traversal.js +45 -102
  7. package/dist/lib/fs-helpers.js +175 -158
  8. package/dist/prompts.d.ts +1 -0
  9. package/dist/prompts.js +58 -0
  10. package/dist/resources/generated-instructions.js +11 -3
  11. package/dist/resources/tool-catalog.js +49 -3
  12. package/dist/resources/tool-info.d.ts +1 -0
  13. package/dist/resources/tool-info.js +139 -8
  14. package/dist/resources.js +2 -0
  15. package/dist/schemas.d.ts +2 -0
  16. package/dist/schemas.js +10 -4
  17. package/dist/server/bootstrap.js +41 -5
  18. package/dist/tools/apply-patch.js +30 -33
  19. package/dist/tools/calculate-hash.js +21 -24
  20. package/dist/tools/contract.d.ts +1 -1
  21. package/dist/tools/create-directory.js +3 -3
  22. package/dist/tools/delete-file.js +2 -2
  23. package/dist/tools/diff-files.js +32 -20
  24. package/dist/tools/edit-file.d.ts +4 -1
  25. package/dist/tools/edit-file.js +172 -104
  26. package/dist/tools/list-directory.js +105 -10
  27. package/dist/tools/move-file.js +3 -3
  28. package/dist/tools/read-multiple.d.ts +1 -1
  29. package/dist/tools/read-multiple.js +123 -99
  30. package/dist/tools/read.js +104 -76
  31. package/dist/tools/replace-in-files.d.ts +5 -2
  32. package/dist/tools/replace-in-files.js +142 -76
  33. package/dist/tools/roots.js +1 -1
  34. package/dist/tools/search-content.js +249 -218
  35. package/dist/tools/shared.js +39 -7
  36. package/dist/tools/stat-many.js +12 -19
  37. package/dist/tools/stat.js +1 -1
  38. package/dist/tools/task-support.d.ts +7 -0
  39. package/dist/tools/task-support.js +140 -101
  40. package/dist/tools/write-file.js +4 -4
  41. package/dist/tools.js +2 -2
  42. package/package.json +1 -1
@@ -1,7 +1,61 @@
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
+ });
1
53
  import * as fsp from 'node:fs/promises';
2
54
  import * as path from 'node:path';
55
+ import { AsyncResource } from 'node:async_hooks';
3
56
  import { existsSync } from 'node:fs';
4
57
  import { fileURLToPath, pathToFileURL } from 'node:url';
58
+ import { debuglog } from 'node:util';
5
59
  import { parentPort, threadId, Worker, workerData } from 'node:worker_threads';
6
60
  import RE2 from 're2';
7
61
  import safeRegex from 'safe-regex2';
@@ -125,6 +179,9 @@ const DEFAULTS = {
125
179
  };
126
180
  const ERROR_SCAN_CANCELLED = 'Scan cancelled';
127
181
  const ERROR_WORKER_POOL_CLOSED = 'Worker pool closed';
182
+ const SEARCH_PROGRESS_THROTTLE_MODULO = 25;
183
+ const SEARCH_WORKER_NAME_PREFIX = 'filesystem-search';
184
+ const SEARCH_WORKER_RESOURCE_TYPE = 'SearchWorkerTask';
128
185
  // --- Helpers ---
129
186
  function resolveOptions(options) {
130
187
  const normalizedOptions = omitOptionKeys(options, ['signal', 'onProgress']);
@@ -135,6 +192,9 @@ function resolveOptions(options) {
135
192
  }
136
193
  return result.data;
137
194
  }
195
+ function getRemainingMatchCapacity(maxMatches, currentMatches, minimum = 0) {
196
+ return Math.max(minimum, maxMatches - currentMatches);
197
+ }
138
198
  /**
139
199
  * Manages a sliding window of lines and pending context-after buffers.
140
200
  */
@@ -208,6 +268,9 @@ function trimContent(line) {
208
268
  : line;
209
269
  }
210
270
  async function readMatches(handle, requestedPath, matcher, options, maxMatches, isCancelled, signal) {
271
+ if (maxMatches <= 0) {
272
+ return [];
273
+ }
211
274
  const matches = [];
212
275
  const hasContext = options.contextLines > 0;
213
276
  const ctx = hasContext ? new ContextBuffer(options.contextLines) : undefined;
@@ -259,11 +322,11 @@ async function readMatches(handle, requestedPath, matcher, options, maxMatches,
259
322
  return matches;
260
323
  }
261
324
  async function scanFileResolved(resolvedPath, requestedPath, matcher, options, signal, maxMatches = Number.POSITIVE_INFINITY, injectedBinaryDetector) {
262
- assertNotAborted(signal);
263
- const handle = await withAbort(fsp.open(resolvedPath, 'r'), signal);
325
+ const env_1 = { stack: [], error: void 0, hasError: false };
264
326
  try {
327
+ assertNotAborted(signal);
328
+ const handle = __addDisposableResource(env_1, await withAbort(fsp.open(resolvedPath, 'r'), signal), true);
265
329
  const stats = await withAbort(handle.stat(), signal);
266
- // 1. Size Check
267
330
  if (stats.size > options.maxFileSize) {
268
331
  return {
269
332
  matches: [],
@@ -272,7 +335,6 @@ async function scanFileResolved(resolvedPath, requestedPath, matcher, options, s
272
335
  skippedBinary: false,
273
336
  };
274
337
  }
275
- // 2. Binary Check
276
338
  if (options.skipBinary) {
277
339
  const detect = injectedBinaryDetector ?? isProbablyBinary;
278
340
  if (await detect(resolvedPath, handle, signal)) {
@@ -284,7 +346,6 @@ async function scanFileResolved(resolvedPath, requestedPath, matcher, options, s
284
346
  };
285
347
  }
286
348
  }
287
- // 3. Scan Content
288
349
  const matches = await readMatches(handle, requestedPath, matcher, options, maxMatches, () => Boolean(signal?.aborted), signal);
289
350
  return {
290
351
  matches,
@@ -293,8 +354,14 @@ async function scanFileResolved(resolvedPath, requestedPath, matcher, options, s
293
354
  skippedBinary: false,
294
355
  };
295
356
  }
357
+ catch (e_1) {
358
+ env_1.error = e_1;
359
+ env_1.hasError = true;
360
+ }
296
361
  finally {
297
- await handle.close();
362
+ const result_1 = __disposeResources(env_1);
363
+ if (result_1)
364
+ await result_1;
298
365
  }
299
366
  }
300
367
  function buildScanFileOptions(opts) {
@@ -360,6 +427,26 @@ const isSourceContext = currentDir.endsWith('src\\lib\\file-operations') ||
360
427
  const WORKER_SCRIPT_PATH = path.join(currentDir, isSourceContext ? 'search-worker.ts' : 'search-worker.js');
361
428
  const WORKER_SCRIPT_URL = pathToFileURL(WORKER_SCRIPT_PATH);
362
429
  const hasWorkerScript = existsSync(WORKER_SCRIPT_PATH);
430
+ class SearchWorkerTaskResource extends AsyncResource {
431
+ #settled = false;
432
+ constructor() {
433
+ super(SEARCH_WORKER_RESOURCE_TYPE);
434
+ }
435
+ resolve(resolver, result) {
436
+ this.finish(resolver, result);
437
+ }
438
+ reject(rejector, error) {
439
+ this.finish(rejector, error);
440
+ }
441
+ finish(callback, value) {
442
+ if (this.#settled) {
443
+ return;
444
+ }
445
+ this.#settled = true;
446
+ this.runInAsyncScope(callback, undefined, value);
447
+ this.emitDestroy();
448
+ }
449
+ }
363
450
  class SearchWorkerPool {
364
451
  size;
365
452
  debug;
@@ -407,6 +494,7 @@ class SearchWorkerPool {
407
494
  }
408
495
  initWorker(index) {
409
496
  const worker = new Worker(WORKER_SCRIPT_URL, {
497
+ name: `${SEARCH_WORKER_NAME_PREFIX}-${String(index)}`,
410
498
  workerData: { debug: this.debug },
411
499
  execArgv: isSourceContext ? ['--import', 'tsx/esm'] : undefined,
412
500
  });
@@ -446,13 +534,23 @@ class SearchWorkerPool {
446
534
  const worker = this.getWorker(workerIndex);
447
535
  this.workerRoundRobin++;
448
536
  const promise = new Promise((resolve, reject) => {
449
- this.pending.set(id, { resolve, reject, workerIndex });
537
+ const resource = new SearchWorkerTaskResource();
538
+ const pendingRequest = {
539
+ resolve: (result) => {
540
+ resource.resolve(resolve, result);
541
+ },
542
+ reject: (error) => {
543
+ resource.reject(reject, error);
544
+ },
545
+ workerIndex,
546
+ };
547
+ this.pending.set(id, pendingRequest);
450
548
  try {
451
549
  worker.postMessage({ type: 'scan', id, ...req });
452
550
  }
453
551
  catch (error) {
454
552
  this.pending.delete(id);
455
- reject(this.normalizeWorkerError(error, `Failed to post scan request ${String(id)} to worker ${String(workerIndex)}`));
553
+ pendingRequest.reject(this.normalizeWorkerError(error, `Failed to post scan request ${String(id)} to worker ${String(workerIndex)}`));
456
554
  this.markWorkerAsUnavailable(workerIndex, worker);
457
555
  }
458
556
  });
@@ -507,7 +605,7 @@ function getPool() {
507
605
  // --- Execution Strategies ---
508
606
  async function executeSequential(files, pattern, opts, signal, summary) {
509
607
  const matches = [];
510
- const matcher = buildMatcher(pattern, opts);
608
+ const matcher = buildMatcher(pattern, buildMatcherOptions(opts));
511
609
  const scanOpts = buildScanFileOptions(opts);
512
610
  for await (const file of files) {
513
611
  if (signal.aborted) {
@@ -520,7 +618,7 @@ async function executeSequential(files, pattern, opts, signal, summary) {
520
618
  }
521
619
  try {
522
620
  assertAllowedFileAccess(file.requestedPath, file.resolvedPath);
523
- const remaining = opts.maxResults - matches.length;
621
+ const remaining = getRemainingMatchCapacity(opts.maxResults, matches.length);
524
622
  const result = await scanFileResolved(file.resolvedPath, file.requestedPath, matcher, scanOpts, signal, remaining);
525
623
  applyScanOutcome(summary, result);
526
624
  matches.push(...result.matches);
@@ -539,7 +637,7 @@ async function fillWorkerPool(context) {
539
637
  if (result.done)
540
638
  return true;
541
639
  try {
542
- const remaining = Math.max(1, maxResults - currentMatches);
640
+ const remaining = getRemainingMatchCapacity(maxResults, currentMatches, 1);
543
641
  const task = pool.scan({
544
642
  resolvedPath: result.value.resolvedPath,
545
643
  requestedPath: result.value.requestedPath,
@@ -566,7 +664,7 @@ function processScanResult(winner, summary, matches, maxResults) {
566
664
  if (winner.result) {
567
665
  const res = winner.result;
568
666
  applyScanOutcome(summary, res);
569
- const remaining = maxResults - matches.length;
667
+ const remaining = getRemainingMatchCapacity(maxResults, matches.length);
570
668
  if (remaining > 0 && res.matches.length > 0) {
571
669
  const take = Math.min(remaining, res.matches.length);
572
670
  for (let index = 0; index < take; index += 1) {
@@ -580,13 +678,14 @@ function processScanResult(winner, summary, matches, maxResults) {
580
678
  async function waitForWinner(pending) {
581
679
  const raceCandidates = [];
582
680
  for (const task of pending) {
583
- raceCandidates.push(task.promise.then((result) => ({ task, result, error: undefined }), (err) => ({
681
+ task.racePromise ??= task.promise.then((result) => ({ task, result, error: undefined }), (err) => ({
584
682
  task,
585
683
  result: undefined,
586
684
  error: err instanceof Error
587
685
  ? err
588
686
  : new Error(formatUnknownErrorMessage(err)),
589
- })));
687
+ }));
688
+ raceCandidates.push(task.racePromise);
590
689
  }
591
690
  return Promise.race(raceCandidates);
592
691
  }
@@ -705,13 +804,13 @@ async function searchDirectory(details, opts, pattern, signal, onProgress) {
705
804
  scanned++;
706
805
  reportPeriodicProgress(onProgress, scanned, {
707
806
  total: opts.maxFilesScanned,
708
- throttleModulo: 25,
807
+ throttleModulo: SEARCH_PROGRESS_THROTTLE_MODULO,
709
808
  });
710
809
  yield { resolvedPath: normalized, requestedPath: entry.path };
711
810
  }
712
811
  reportPeriodicProgress(onProgress, scanned, {
713
812
  total: opts.maxFilesScanned,
714
- throttleModulo: 25,
813
+ throttleModulo: SEARCH_PROGRESS_THROTTLE_MODULO,
715
814
  force: true,
716
815
  });
717
816
  }
@@ -870,7 +969,7 @@ async function collectFromStream(stream, signal, context) {
870
969
  state.filesScanned++;
871
970
  reportPeriodicProgress(onProgress, state.filesScanned, {
872
971
  total: normalized.maxFilesScanned,
873
- throttleModulo: 25,
972
+ throttleModulo: SEARCH_PROGRESS_THROTTLE_MODULO,
874
973
  });
875
974
  if (isEntryIgnoredByGitignore(gitignoreMatcher, root, entry.path, entry.relativePath)) {
876
975
  continue;
@@ -890,7 +989,7 @@ async function collectFromStream(stream, signal, context) {
890
989
  }
891
990
  reportPeriodicProgress(onProgress, state.filesScanned, {
892
991
  total: normalized.maxFilesScanned,
893
- throttleModulo: 25,
992
+ throttleModulo: SEARCH_PROGRESS_THROTTLE_MODULO,
894
993
  force: true,
895
994
  });
896
995
  }
@@ -1099,10 +1198,11 @@ function handleMessage(message) {
1099
1198
  break;
1100
1199
  }
1101
1200
  }
1201
+ const log = debuglog('search-worker');
1102
1202
  if (parentPort) {
1103
1203
  parentPort.on('message', handleMessage);
1104
1204
  const data = workerData;
1105
1205
  if (data?.debug) {
1106
- console.error(`[SearchWorker] Started with threadId=${String(threadId)}`);
1206
+ log(`Started with threadId=${String(threadId)}`);
1107
1207
  }
1108
1208
  }
@@ -25,141 +25,87 @@ function normalizePattern(pattern, baseNameMatch) {
25
25
  return normalized;
26
26
  return `**/${normalized}`;
27
27
  }
28
- function normalizeIgnorePatterns(patterns) {
29
- return patterns.map(toPosixPath);
30
- }
31
28
  function splitPatternPrefix(normalizedPattern) {
32
29
  if (!GLOB_MAGIC_RE.test(normalizedPattern)) {
33
30
  return { prefix: '', remainder: normalizedPattern };
34
31
  }
35
32
  const segments = normalizedPattern.split(SEP);
36
- const len = segments.length;
37
- let splitIndex = len;
38
- for (let i = 0; i < len; i++) {
39
- const seg = segments[i];
40
- if (seg && GLOB_MAGIC_RE.test(seg)) {
41
- splitIndex = i;
42
- break;
43
- }
44
- }
45
- if (splitIndex === 0) {
33
+ const splitIndex = segments.findIndex((seg) => GLOB_MAGIC_RE.test(seg));
34
+ if (splitIndex <= 0) {
46
35
  return { prefix: '', remainder: normalizedPattern };
47
36
  }
48
- if (splitIndex >= len) {
49
- const prefix = segments.slice(0, len - 1).join(SEP);
50
- const last = segments[len - 1];
51
- return {
52
- prefix: prefix ? prefix + SEP : '',
53
- remainder: last ?? '',
54
- };
55
- }
56
37
  return {
57
38
  prefix: segments.slice(0, splitIndex).join(SEP) + SEP,
58
39
  remainder: segments.slice(splitIndex).join(SEP),
59
40
  };
60
41
  }
61
- function addDotfileCandidates(patterns, prefix, remainderSegments) {
62
- const firstCandidateIndex = remainderSegments.findIndex((segment) => segment !== '**' && segment.length > 0);
63
- if (firstCandidateIndex !== -1) {
64
- const original = remainderSegments[firstCandidateIndex];
65
- if (original && original.charCodeAt(0) !== DOT_CHAR_CODE) {
66
- const newSegments = remainderSegments.slice();
67
- newSegments[firstCandidateIndex] = `.${original}`;
68
- patterns.add(`${prefix}${newSegments.join(SEP)}`);
69
- }
70
- }
71
- }
72
- function addGlobstarCandidates(patterns, prefix, remainder, maxDepth) {
73
- const afterGlobstar = remainder.slice(3);
74
- const addDotFile = afterGlobstar.length > 0 && afterGlobstar.charCodeAt(0) !== DOT_CHAR_CODE;
75
- let depthPrefix = '';
76
- for (let depth = 0; depth <= maxDepth; depth++) {
77
- patterns.add(`${prefix}${depthPrefix}.*/**/${afterGlobstar}`);
78
- if (addDotFile) {
79
- patterns.add(`${prefix}${depthPrefix}.${afterGlobstar}`);
80
- }
81
- depthPrefix += '*/';
82
- }
83
- }
84
42
  function buildHiddenPatterns(normalizedPattern, maxDepth) {
85
- const patterns = new Set();
86
- patterns.add(normalizedPattern);
43
+ const patterns = new Set([normalizedPattern]);
87
44
  const { prefix, remainder } = splitPatternPrefix(normalizedPattern);
88
45
  if (remainder.length > 0) {
89
- const remainderSegments = remainder.split(SEP);
90
- addDotfileCandidates(patterns, prefix, remainderSegments);
46
+ const segments = remainder.split(SEP);
47
+ const idx = segments.findIndex((seg) => seg !== '**' && seg.length > 0);
48
+ if (idx !== -1) {
49
+ const original = segments[idx];
50
+ if (original && original.charCodeAt(0) !== DOT_CHAR_CODE) {
51
+ const newSegments = [...segments];
52
+ newSegments[idx] = `.${original}`;
53
+ patterns.add(`${prefix}${newSegments.join(SEP)}`);
54
+ }
55
+ }
91
56
  }
92
57
  if (remainder.startsWith('**/')) {
93
- addGlobstarCandidates(patterns, prefix, remainder, maxDepth);
58
+ const afterGlobstar = remainder.slice(3);
59
+ const addDotFile = afterGlobstar.length > 0 && afterGlobstar.charCodeAt(0) !== DOT_CHAR_CODE;
60
+ let depthPrefix = '';
61
+ for (let depth = 0; depth <= maxDepth; depth++) {
62
+ patterns.add(`${prefix}${depthPrefix}.*/**/${afterGlobstar}`);
63
+ if (addDotFile)
64
+ patterns.add(`${prefix}${depthPrefix}.${afterGlobstar}`);
65
+ depthPrefix += '*/';
66
+ }
94
67
  }
95
68
  return Array.from(patterns);
96
69
  }
97
- function shouldUseGlobDirents(options) {
98
- return !options.stats && !options.followSymbolicLinks;
99
- }
100
- function assertOptionString(options, key) {
101
- if (typeof options[key] !== 'string') {
102
- throw new TypeError(`globEntries: options.${key} must be a string`);
103
- }
104
- }
105
- function assertExcludePatternsOption(options) {
106
- if (!Array.isArray(options.excludePatterns)) {
107
- throw new TypeError('globEntries: options.excludePatterns must be an array');
70
+ function assertOptionsShape(options) {
71
+ const optsUnknown = options;
72
+ if (typeof optsUnknown !== 'object' || optsUnknown === null) {
73
+ throw new TypeError('globEntries: options must be an object');
108
74
  }
109
- for (const pattern of options.excludePatterns) {
110
- if (typeof pattern !== 'string') {
111
- throw new TypeError('globEntries: options.excludePatterns must contain only strings');
112
- }
75
+ const opts = optsUnknown;
76
+ if (typeof opts.cwd !== 'string')
77
+ throw new TypeError('globEntries: options.cwd must be a string');
78
+ if (typeof opts.pattern !== 'string')
79
+ throw new TypeError('globEntries: options.pattern must be a string');
80
+ if (!Array.isArray(opts.excludePatterns) ||
81
+ opts.excludePatterns.some((p) => typeof p !== 'string')) {
82
+ throw new TypeError('globEntries: options.excludePatterns must be an array of strings');
113
83
  }
114
- }
115
- function assertBooleanOptions(options) {
116
84
  for (const key of GLOB_BOOLEAN_OPTION_KEYS) {
117
- if (typeof options[key] !== 'boolean') {
85
+ if (typeof opts[key] !== 'boolean') {
118
86
  throw new TypeError(`globEntries: options.${key} must be a boolean`);
119
87
  }
120
88
  }
121
- }
122
- function assertOptionalMaxDepth(options) {
123
- const { maxDepth } = options;
124
- if (maxDepth === undefined)
125
- return;
126
- if (typeof maxDepth !== 'number' || !Number.isFinite(maxDepth)) {
89
+ if (opts.maxDepth !== undefined &&
90
+ (!Number.isFinite(opts.maxDepth) || typeof opts.maxDepth !== 'number')) {
127
91
  throw new TypeError('globEntries: options.maxDepth must be a finite number');
128
92
  }
129
- }
130
- function assertOptionalSuppressErrors(options) {
131
- const { suppressErrors } = options;
132
- if (suppressErrors === undefined)
133
- return;
134
- if (typeof suppressErrors !== 'boolean') {
93
+ if (opts.suppressErrors !== undefined &&
94
+ typeof opts.suppressErrors !== 'boolean') {
135
95
  throw new TypeError('globEntries: options.suppressErrors must be a boolean');
136
96
  }
137
97
  }
138
- function assertOptionsShape(options) {
139
- const unknownOptions = options;
140
- if (unknownOptions === null || typeof unknownOptions !== 'object') {
141
- throw new TypeError('globEntries: options must be an object');
142
- }
143
- const o = unknownOptions;
144
- assertOptionString(o, 'cwd');
145
- assertOptionString(o, 'pattern');
146
- assertExcludePatternsOption(o);
147
- assertBooleanOptions(o);
148
- assertOptionalMaxDepth(o);
149
- assertOptionalSuppressErrors(o);
150
- }
151
98
  function normalizeOptions(options) {
152
99
  const cwd = path.resolve(options.cwd);
153
100
  const normalizedPattern = normalizePattern(options.pattern, options.baseNameMatch);
154
- const maxHiddenDepth = options.maxDepth ?? DEFAULT_MAX_HIDDEN_DEPTH;
155
101
  const patterns = options.includeHidden
156
- ? buildHiddenPatterns(normalizedPattern, maxHiddenDepth)
102
+ ? buildHiddenPatterns(normalizedPattern, options.maxDepth ?? DEFAULT_MAX_HIDDEN_DEPTH)
157
103
  : [normalizedPattern];
158
104
  const normalized = {
159
105
  cwd,
160
106
  patterns,
161
- exclude: normalizeIgnorePatterns(options.excludePatterns),
162
- useDirents: shouldUseGlobDirents(options),
107
+ exclude: options.excludePatterns.map(toPosixPath),
108
+ useDirents: !options.stats && !options.followSymbolicLinks,
163
109
  suppressErrors: options.suppressErrors ?? false,
164
110
  };
165
111
  if (options.maxDepth !== undefined) {
@@ -248,12 +194,9 @@ async function* processIterable(iterable, context) {
248
194
  const flush = async function* () {
249
195
  if (buffer.length === 0)
250
196
  return;
251
- const requests = [];
252
- for (const match of buffer) {
253
- requests.push(resolveStringMatch(match, cwd, maxDepth, seen, onlyFiles, followSymlinks, returnStats, suppressErrors));
254
- }
255
- buffer.length = 0;
256
- const results = await Promise.all(requests);
197
+ // Process buffer concurrently
198
+ const currentBuffer = buffer.splice(0, buffer.length);
199
+ const results = await Promise.all(currentBuffer.map((match) => resolveStringMatch(match, cwd, maxDepth, seen, onlyFiles, followSymlinks, returnStats, suppressErrors)));
257
200
  for (const entry of results) {
258
201
  if (entry !== null)
259
202
  yield entry;