@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.
- package/dist/completions.js +25 -0
- package/dist/lib/constants.d.ts +2 -0
- package/dist/lib/constants.js +2 -0
- package/dist/lib/file-operations/metadata.js +71 -142
- package/dist/lib/file-operations/search.js +119 -19
- package/dist/lib/file-operations/traversal.js +45 -102
- package/dist/lib/fs-helpers.js +175 -158
- package/dist/prompts.d.ts +1 -0
- package/dist/prompts.js +58 -0
- package/dist/resources/generated-instructions.js +11 -3
- package/dist/resources/tool-catalog.js +49 -3
- package/dist/resources/tool-info.d.ts +1 -0
- package/dist/resources/tool-info.js +139 -8
- package/dist/resources.js +2 -0
- package/dist/schemas.d.ts +2 -0
- package/dist/schemas.js +10 -4
- package/dist/server/bootstrap.js +41 -5
- package/dist/tools/apply-patch.js +30 -33
- package/dist/tools/calculate-hash.js +21 -24
- package/dist/tools/contract.d.ts +1 -1
- package/dist/tools/create-directory.js +3 -3
- package/dist/tools/delete-file.js +2 -2
- package/dist/tools/diff-files.js +32 -20
- package/dist/tools/edit-file.d.ts +4 -1
- package/dist/tools/edit-file.js +172 -104
- package/dist/tools/list-directory.js +105 -10
- package/dist/tools/move-file.js +3 -3
- package/dist/tools/read-multiple.d.ts +1 -1
- package/dist/tools/read-multiple.js +123 -99
- package/dist/tools/read.js +104 -76
- package/dist/tools/replace-in-files.d.ts +5 -2
- package/dist/tools/replace-in-files.js +142 -76
- package/dist/tools/roots.js +1 -1
- package/dist/tools/search-content.js +249 -218
- package/dist/tools/shared.js +39 -7
- package/dist/tools/stat-many.js +12 -19
- package/dist/tools/stat.js +1 -1
- package/dist/tools/task-support.d.ts +7 -0
- package/dist/tools/task-support.js +140 -101
- package/dist/tools/write-file.js +4 -4
- package/dist/tools.js +2 -2
- 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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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 =
|
|
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
|
|
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
|
-
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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
|
-
|
|
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
|
|
37
|
-
|
|
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
|
|
90
|
-
|
|
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
|
-
|
|
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
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
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
|
-
|
|
110
|
-
|
|
111
|
-
|
|
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
|
|
85
|
+
if (typeof opts[key] !== 'boolean') {
|
|
118
86
|
throw new TypeError(`globEntries: options.${key} must be a boolean`);
|
|
119
87
|
}
|
|
120
88
|
}
|
|
121
|
-
|
|
122
|
-
|
|
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
|
-
|
|
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,
|
|
102
|
+
? buildHiddenPatterns(normalizedPattern, options.maxDepth ?? DEFAULT_MAX_HIDDEN_DEPTH)
|
|
157
103
|
: [normalizedPattern];
|
|
158
104
|
const normalized = {
|
|
159
105
|
cwd,
|
|
160
106
|
patterns,
|
|
161
|
-
exclude:
|
|
162
|
-
useDirents:
|
|
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
|
-
|
|
252
|
-
|
|
253
|
-
|
|
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;
|