@lzehrung/codegraph 2.3.20 → 2.3.21
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/codegraph-skill/codegraph/SKILL.md +1 -0
- package/dist/agent/session.d.ts +2 -2
- package/dist/agent/session.d.ts.map +1 -1
- package/dist/agent/session.js +13 -9
- package/dist/agent/session.js.map +1 -1
- package/dist/bin/cli.js +36335 -36073
- package/dist/config.d.ts +1 -1
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +0 -1
- package/dist/config.js.map +1 -1
- package/dist/indexer/build-cache/manifest.d.ts +2 -2
- package/dist/indexer/build-cache/manifest.d.ts.map +1 -1
- package/dist/indexer/build-cache/manifest.js +8 -33
- package/dist/indexer/build-cache/manifest.js.map +1 -1
- package/dist/indexer/build-index.d.ts.map +1 -1
- package/dist/indexer/build-index.js +33 -10
- package/dist/indexer/build-index.js.map +1 -1
- package/dist/indexer/build-manifest.d.ts +3 -0
- package/dist/indexer/build-manifest.d.ts.map +1 -1
- package/dist/indexer/build-manifest.js +1 -1
- package/dist/indexer/build-manifest.js.map +1 -1
- package/dist/indexer/finalize.d.ts +2 -1
- package/dist/indexer/finalize.d.ts.map +1 -1
- package/dist/indexer/finalize.js +1 -0
- package/dist/indexer/finalize.js.map +1 -1
- package/dist/indexer/incremental-plan.d.ts +3 -2
- package/dist/indexer/incremental-plan.d.ts.map +1 -1
- package/dist/indexer/incremental-plan.js +8 -4
- package/dist/indexer/incremental-plan.js.map +1 -1
- package/dist/indexer/types.d.ts +3 -1
- package/dist/indexer/types.d.ts.map +1 -1
- package/dist/lifecycle/manifest.d.ts.map +1 -1
- package/dist/lifecycle/manifest.js +6 -4
- package/dist/lifecycle/manifest.js.map +1 -1
- package/dist/util/git.d.ts +13 -10
- package/dist/util/git.d.ts.map +1 -1
- package/dist/util/git.js +29 -25
- package/dist/util/git.js.map +1 -1
- package/dist/util/projectFiles/definitions.d.ts +19 -0
- package/dist/util/projectFiles/definitions.d.ts.map +1 -1
- package/dist/util/projectFiles/definitions.js.map +1 -1
- package/dist/util/projectFiles.d.ts +72 -21
- package/dist/util/projectFiles.d.ts.map +1 -1
- package/dist/util/projectFiles.js +279 -30
- package/dist/util/projectFiles.js.map +1 -1
- package/package.json +3 -3
|
@@ -12,7 +12,8 @@ export { isRelativePathInside, matchesDiscoveryGlob } from "./discoveryPath.js";
|
|
|
12
12
|
import { PROJECT_FILE_DEFINITIONS, } from "./projectFiles/definitions.js";
|
|
13
13
|
import { trimToNull } from "./projectFiles/parsers.js";
|
|
14
14
|
import { mapLimitSemaphore } from "./concurrency.js";
|
|
15
|
-
import {
|
|
15
|
+
import { CODEGRAPH_CONFIG_FILE } from "../config.js";
|
|
16
|
+
import { createGitDiscoveryCache, getGitRepositoryRoot, isGitProjectRootIgnored, isGitRepo, isGitTimeoutError, listGitExcludeFiles, listGitStageSpecialPaths, listGitSubmoduleDirectories, listTrackedFiles, listUntrackedFiles, } from "./git.js";
|
|
16
17
|
export const DEFAULT_PROJECT_FILE_IGNORES = [
|
|
17
18
|
"**/node_modules/**",
|
|
18
19
|
"**/.git/**",
|
|
@@ -100,6 +101,9 @@ export const DEFAULT_PROJECT_PATTERNS = [
|
|
|
100
101
|
...DEFAULT_PROJECT_MANIFESTS.map((name) => `**/${name}`),
|
|
101
102
|
];
|
|
102
103
|
const REALPATH_FILTER_CONCURRENCY = 64;
|
|
104
|
+
export function createProjectDiscoveryContext(projectRoot) {
|
|
105
|
+
return new DiscoveryContext(projectRoot);
|
|
106
|
+
}
|
|
103
107
|
function emitDiscoveryActivity(report, activity, current = 0, total = 0) {
|
|
104
108
|
report?.({ activity, current, total });
|
|
105
109
|
}
|
|
@@ -121,6 +125,35 @@ function isPresent(value) {
|
|
|
121
125
|
function normalizeGlobPattern(globPattern) {
|
|
122
126
|
return globPattern.trim().replace(/\\/g, "/");
|
|
123
127
|
}
|
|
128
|
+
const ROOT_CONFIG_NAME_MATCHERS = [...DEFAULT_PROJECT_MANIFESTS, CODEGRAPH_CONFIG_FILE].map((pattern) => picomatch(normalizeGlobPattern(pattern), { dot: true }));
|
|
129
|
+
function isRootConfigFileName(fileName) {
|
|
130
|
+
return ROOT_CONFIG_NAME_MATCHERS.some((matcher) => matcher(fileName));
|
|
131
|
+
}
|
|
132
|
+
async function listRootConfigFiles(root, state) {
|
|
133
|
+
const entries = await readDirectoryWithFileTypes(root, state);
|
|
134
|
+
const files = [];
|
|
135
|
+
for (const entry of entries) {
|
|
136
|
+
if (!isRootConfigFileName(entry.name))
|
|
137
|
+
continue;
|
|
138
|
+
const file = normalizePath(path.join(root, entry.name));
|
|
139
|
+
if (!entry.isFile()) {
|
|
140
|
+
if (!entry.isSymbolicLink())
|
|
141
|
+
continue;
|
|
142
|
+
try {
|
|
143
|
+
if (!(await fsp.stat(file)).isFile())
|
|
144
|
+
continue;
|
|
145
|
+
}
|
|
146
|
+
catch (error) {
|
|
147
|
+
const code = error.code;
|
|
148
|
+
if (code === "ENOENT" || code === "ENOTDIR")
|
|
149
|
+
continue;
|
|
150
|
+
throw error;
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
files.push(file);
|
|
154
|
+
}
|
|
155
|
+
return files;
|
|
156
|
+
}
|
|
124
157
|
const DIRECTORY_METADATA_EXTENSIONS = new Set();
|
|
125
158
|
const DIRECTORY_METADATA_BASENAMES = new Set();
|
|
126
159
|
for (const definition of PROJECT_FILE_DEFINITIONS) {
|
|
@@ -275,6 +308,126 @@ const EMPTY_GITIGNORE_INDEX = {
|
|
|
275
308
|
repositoryAliases: [],
|
|
276
309
|
byBaseDir: new Map(),
|
|
277
310
|
};
|
|
311
|
+
class DiscoveryContext {
|
|
312
|
+
projectRoot;
|
|
313
|
+
git;
|
|
314
|
+
gitCandidatesByRoot = new Map();
|
|
315
|
+
ignoreIndexByKey = new Map();
|
|
316
|
+
fileTextByPath = new Map();
|
|
317
|
+
readdirCache = new Map();
|
|
318
|
+
globFilesystem;
|
|
319
|
+
constructor(projectRoot) {
|
|
320
|
+
this.projectRoot = projectRoot;
|
|
321
|
+
this.git = createGitDiscoveryCache();
|
|
322
|
+
this.globFilesystem = { readdir: createSharedReaddir(this) };
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
function createSharedReaddir(state) {
|
|
326
|
+
function readdir(directory, optionsOrCallback, callback) {
|
|
327
|
+
const listing = readDirectoryWithFileTypes(directory, state);
|
|
328
|
+
if (typeof optionsOrCallback === "function") {
|
|
329
|
+
listing.then((entries) => optionsOrCallback(null, entries.map((entry) => entry.name)), (error) => optionsOrCallback(error, []));
|
|
330
|
+
}
|
|
331
|
+
else if (callback) {
|
|
332
|
+
listing.then((entries) => callback(null, entries), (error) => callback(error, []));
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
return readdir;
|
|
336
|
+
}
|
|
337
|
+
function globFilesystemOption(state) {
|
|
338
|
+
if (!state)
|
|
339
|
+
return {};
|
|
340
|
+
return { fs: state.globFilesystem };
|
|
341
|
+
}
|
|
342
|
+
function globFilesystemField(state) {
|
|
343
|
+
if (!state)
|
|
344
|
+
return {};
|
|
345
|
+
return { globFilesystem: state.globFilesystem };
|
|
346
|
+
}
|
|
347
|
+
async function readDirectoryWithFileTypes(directory, state) {
|
|
348
|
+
if (!state)
|
|
349
|
+
return fsp.readdir(directory, { withFileTypes: true });
|
|
350
|
+
const key = fileIdentityKey(directory);
|
|
351
|
+
const cached = state.readdirCache.get(key);
|
|
352
|
+
if (cached)
|
|
353
|
+
return cached;
|
|
354
|
+
const pending = fsp.readdir(directory, { withFileTypes: true });
|
|
355
|
+
state.readdirCache.set(key, pending);
|
|
356
|
+
return pending;
|
|
357
|
+
}
|
|
358
|
+
async function readDiscoveryFileResult(state, filePath) {
|
|
359
|
+
if (!state) {
|
|
360
|
+
try {
|
|
361
|
+
return { ok: true, text: await fsp.readFile(filePath, "utf8") };
|
|
362
|
+
}
|
|
363
|
+
catch (error) {
|
|
364
|
+
return { ok: false, error };
|
|
365
|
+
}
|
|
366
|
+
}
|
|
367
|
+
const key = fileIdentityKey(filePath);
|
|
368
|
+
const cached = state.fileTextByPath.get(key);
|
|
369
|
+
if (cached)
|
|
370
|
+
return await cached;
|
|
371
|
+
const pending = (async () => {
|
|
372
|
+
try {
|
|
373
|
+
return { ok: true, text: await fsp.readFile(filePath, "utf8") };
|
|
374
|
+
}
|
|
375
|
+
catch (error) {
|
|
376
|
+
return { ok: false, error };
|
|
377
|
+
}
|
|
378
|
+
})();
|
|
379
|
+
state.fileTextByPath.set(key, pending);
|
|
380
|
+
return await pending;
|
|
381
|
+
}
|
|
382
|
+
export async function readProjectDiscoveryFileText(discoveryContext, filePath) {
|
|
383
|
+
const result = await readDiscoveryFileResult(discoveryContext, filePath);
|
|
384
|
+
if (result.ok)
|
|
385
|
+
return result.text;
|
|
386
|
+
if (result.error instanceof Error)
|
|
387
|
+
throw result.error;
|
|
388
|
+
throw new Error(`Failed to read ${filePath}: ${stringifyUnknown(result.error)}`);
|
|
389
|
+
}
|
|
390
|
+
async function sharedGitignoreIndex(state, cacheKey, load) {
|
|
391
|
+
if (!state)
|
|
392
|
+
return await load();
|
|
393
|
+
const cached = state.ignoreIndexByKey.get(cacheKey);
|
|
394
|
+
if (cached)
|
|
395
|
+
return await cached;
|
|
396
|
+
const pending = load();
|
|
397
|
+
state.ignoreIndexByKey.set(cacheKey, pending);
|
|
398
|
+
return await pending;
|
|
399
|
+
}
|
|
400
|
+
function rememberGitCandidates(state, root, candidates) {
|
|
401
|
+
if (!state)
|
|
402
|
+
return;
|
|
403
|
+
const key = fileIdentityKey(root);
|
|
404
|
+
if (state.gitCandidatesByRoot.has(key))
|
|
405
|
+
return;
|
|
406
|
+
state.gitCandidatesByRoot.set(key, Promise.resolve(candidates));
|
|
407
|
+
}
|
|
408
|
+
async function getSharedGitCandidates(context, root, logLevel, callbacks) {
|
|
409
|
+
const state = context;
|
|
410
|
+
const gitCache = context?.git;
|
|
411
|
+
if (!state)
|
|
412
|
+
return await listGitCandidateFiles(root, logLevel, callbacks, gitCache);
|
|
413
|
+
const key = fileIdentityKey(root);
|
|
414
|
+
const cached = state.gitCandidatesByRoot.get(key);
|
|
415
|
+
if (cached)
|
|
416
|
+
return await cached;
|
|
417
|
+
const pending = listGitCandidateFiles(root, logLevel, callbacks, gitCache);
|
|
418
|
+
state.gitCandidatesByRoot.set(key, pending);
|
|
419
|
+
return await pending;
|
|
420
|
+
}
|
|
421
|
+
function gitExcludeListOptions(gitCache) {
|
|
422
|
+
if (!gitCache)
|
|
423
|
+
return undefined;
|
|
424
|
+
return { discoveryCache: gitCache };
|
|
425
|
+
}
|
|
426
|
+
function gitUntrackedListOptions(gitCache) {
|
|
427
|
+
if (!gitCache)
|
|
428
|
+
return { respectGitignore: true };
|
|
429
|
+
return { respectGitignore: true, discoveryCache: gitCache };
|
|
430
|
+
}
|
|
278
431
|
/**
|
|
279
432
|
* Parse `.gitignore` rules from an already-discovered set of `.gitignore` files.
|
|
280
433
|
*
|
|
@@ -284,7 +437,7 @@ const EMPTY_GITIGNORE_INDEX = {
|
|
|
284
437
|
* child negation could re-include files. Depth-first ordering also keeps the "last match
|
|
285
438
|
* wins" order deeper files depend on, and drops the locale sensitivity of `localeCompare`.
|
|
286
439
|
*/
|
|
287
|
-
async function buildGitignoreIndex(sources, repositoryRoots = [], repositoryAliases = []) {
|
|
440
|
+
async function buildGitignoreIndex(sources, repositoryRoots = [], repositoryAliases = [], state) {
|
|
288
441
|
const sorted = [...sources]
|
|
289
442
|
.map(({ file, baseDir, repositoryRoot, priority }) => ({
|
|
290
443
|
file: normalizePath(file),
|
|
@@ -316,13 +469,10 @@ async function buildGitignoreIndex(sources, repositoryRoots = [], repositoryAlia
|
|
|
316
469
|
if (isIgnoredByGitignore(path.dirname(file), gitignoreIndex, true)) {
|
|
317
470
|
continue;
|
|
318
471
|
}
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
raw = await fsp.readFile(file, "utf8");
|
|
322
|
-
}
|
|
323
|
-
catch {
|
|
472
|
+
const fileText = await readDiscoveryFileResult(state, file);
|
|
473
|
+
if (!fileText.ok)
|
|
324
474
|
continue;
|
|
325
|
-
|
|
475
|
+
const raw = fileText.text;
|
|
326
476
|
const baseKey = fileIdentityKey(baseDir);
|
|
327
477
|
for (const line of raw.split(/\r?\n/)) {
|
|
328
478
|
const rule = parseGitignoreRule(baseDir, line);
|
|
@@ -338,16 +488,17 @@ async function buildGitignoreIndex(sources, repositoryRoots = [], repositoryAlia
|
|
|
338
488
|
}
|
|
339
489
|
return gitignoreIndex;
|
|
340
490
|
}
|
|
341
|
-
async function findGitignoreFiles(projectRoot) {
|
|
491
|
+
async function findGitignoreFiles(projectRoot, state) {
|
|
342
492
|
return await fg(["**/.gitignore"], {
|
|
343
493
|
cwd: projectRoot,
|
|
344
494
|
absolute: true,
|
|
345
495
|
dot: true,
|
|
346
496
|
followSymbolicLinks: false,
|
|
347
497
|
ignore: DEFAULT_PROJECT_FILE_IGNORES,
|
|
498
|
+
...globFilesystemOption(state),
|
|
348
499
|
});
|
|
349
500
|
}
|
|
350
|
-
async function loadGitignoreIndexForRootAliases(projectRoot) {
|
|
501
|
+
async function loadGitignoreIndexForRootAliases(projectRoot, state) {
|
|
351
502
|
const roots = [projectRoot];
|
|
352
503
|
const realRoot = await fsp.realpath(projectRoot);
|
|
353
504
|
if (normalizePath(realRoot) !== normalizePath(projectRoot)) {
|
|
@@ -355,11 +506,11 @@ async function loadGitignoreIndexForRootAliases(projectRoot) {
|
|
|
355
506
|
}
|
|
356
507
|
const sources = [];
|
|
357
508
|
for (const root of roots) {
|
|
358
|
-
for (const file of await findGitignoreFiles(root)) {
|
|
509
|
+
for (const file of await findGitignoreFiles(root, state)) {
|
|
359
510
|
sources.push({ file, baseDir: path.dirname(file) });
|
|
360
511
|
}
|
|
361
512
|
}
|
|
362
|
-
return await buildGitignoreIndex(sources);
|
|
513
|
+
return await buildGitignoreIndex(sources, [], [], state);
|
|
363
514
|
}
|
|
364
515
|
/**
|
|
365
516
|
* Whether `.gitignore` rules ignore `absolutePath`.
|
|
@@ -513,7 +664,7 @@ async function gitignoreFileIfPresent(directory) {
|
|
|
513
664
|
* `.gitignore` files walks every gitignored tree looking for them, which times
|
|
514
665
|
* out on a large ignored data directory even after file listing already finished.
|
|
515
666
|
*/
|
|
516
|
-
async function findGitIgnoreSources(sourceRoots, candidateFiles) {
|
|
667
|
+
async function findGitIgnoreSources(sourceRoots, candidateFiles, gitCache) {
|
|
517
668
|
const roots = Array.from(new Map(sourceRoots.map(({ path: sourceRoot, repositoryRoot }) => [
|
|
518
669
|
fileIdentityKey(sourceRoot),
|
|
519
670
|
{ path: normalizePath(sourceRoot), repositoryRoot: normalizePath(repositoryRoot) },
|
|
@@ -561,7 +712,7 @@ async function findGitIgnoreSources(sourceRoots, candidateFiles) {
|
|
|
561
712
|
const file = await gitignoreFileIfPresent(dir);
|
|
562
713
|
return file ? [{ file, baseDir: dir, repositoryRoot, priority: 2 }] : [];
|
|
563
714
|
}),
|
|
564
|
-
Promise.all(uniqueRepositoryRoots.map((repositoryRoot) => listGitExcludeFiles(repositoryRoot))),
|
|
715
|
+
Promise.all(uniqueRepositoryRoots.map((repositoryRoot) => listGitExcludeFiles(repositoryRoot, gitExcludeListOptions(gitCache)))),
|
|
565
716
|
]);
|
|
566
717
|
return [
|
|
567
718
|
...gitignoreHits.flat(),
|
|
@@ -577,15 +728,15 @@ async function findGitIgnoreSources(sourceRoots, candidateFiles) {
|
|
|
577
728
|
* Tracked plus untracked-but-not-ignored files are Git candidates. Submodule files retain
|
|
578
729
|
* their own repository boundary, and source roots cover logical and physical aliases.
|
|
579
730
|
*/
|
|
580
|
-
async function listGitCandidateFiles(root, logLevel, opts) {
|
|
731
|
+
async function listGitCandidateFiles(root, logLevel, opts, gitCache) {
|
|
581
732
|
let gitListStart;
|
|
582
733
|
let gitIgnoreStart;
|
|
583
734
|
try {
|
|
584
735
|
const realRoot = normalizePath(await fsp.realpath(root));
|
|
585
|
-
const gitRoot = (await isGitRepo(root)) ? root : realRoot;
|
|
586
|
-
if (!(await isGitRepo(gitRoot)))
|
|
736
|
+
const gitRoot = (await isGitRepo(root, gitCache)) ? root : realRoot;
|
|
737
|
+
if (!(await isGitRepo(gitRoot, gitCache)))
|
|
587
738
|
return null;
|
|
588
|
-
if (await isGitProjectRootIgnored(realRoot))
|
|
739
|
+
if (await isGitProjectRootIgnored(realRoot, gitCache))
|
|
589
740
|
return null;
|
|
590
741
|
const gitRepositoryRoot = await getGitRepositoryRoot(gitRoot);
|
|
591
742
|
if (!gitRepositoryRoot)
|
|
@@ -593,9 +744,10 @@ async function listGitCandidateFiles(root, logLevel, opts) {
|
|
|
593
744
|
emitDiscoveryActivity(opts?.onDiscoveryProgress, "Listing Git files");
|
|
594
745
|
const listStartedAt = performance.now();
|
|
595
746
|
gitListStart = listStartedAt;
|
|
747
|
+
const untrackedOptions = gitUntrackedListOptions(gitCache);
|
|
596
748
|
const [tracked, untracked, stageSpecial] = await Promise.all([
|
|
597
749
|
listTrackedFiles(gitRoot, { recurseSubmodules: true, ...(logLevel === undefined ? {} : { logLevel }) }),
|
|
598
|
-
listUntrackedFiles(gitRoot,
|
|
750
|
+
listUntrackedFiles(gitRoot, untrackedOptions),
|
|
599
751
|
listGitStageSpecialPaths(gitRoot, { recurse: true }),
|
|
600
752
|
]);
|
|
601
753
|
const submoduleDirectories = stageSpecial.gitlinks;
|
|
@@ -603,7 +755,7 @@ async function listGitCandidateFiles(root, logLevel, opts) {
|
|
|
603
755
|
logicalPath: normalizePath(logicalPath),
|
|
604
756
|
physicalPath: normalizePath(await fsp.realpath(logicalPath).catch(() => logicalPath)),
|
|
605
757
|
})));
|
|
606
|
-
const submoduleUntracked = await Promise.all(submoduleRoots.map(async ({ physicalPath }) => await listUntrackedFiles(physicalPath,
|
|
758
|
+
const submoduleUntracked = await Promise.all(submoduleRoots.map(async ({ physicalPath }) => await listUntrackedFiles(physicalPath, untrackedOptions)));
|
|
607
759
|
const rawFiles = Array.from(new Set([...tracked, ...untracked, ...submoduleUntracked.flat()])).sort();
|
|
608
760
|
const needsLogicalRemap = normalizePath(gitRoot) !== normalizePath(root);
|
|
609
761
|
const files = Array.from(new Set(rawFiles.map((file) => needsLogicalRemap && isFilePathWithinRoot(realRoot, file)
|
|
@@ -629,7 +781,7 @@ async function listGitCandidateFiles(root, logLevel, opts) {
|
|
|
629
781
|
emitDiscoveryActivity(opts?.onDiscoveryProgress, "Listing Git ignore files");
|
|
630
782
|
const ignoreStartedAt = performance.now();
|
|
631
783
|
gitIgnoreStart = ignoreStartedAt;
|
|
632
|
-
const gitignoreFiles = await findGitIgnoreSources(sourceRoots, files);
|
|
784
|
+
const gitignoreFiles = await findGitIgnoreSources(sourceRoots, files, gitCache);
|
|
633
785
|
emitDiscoveryTiming(opts?.onDiscoveryTiming, "git-ignore", ignoreStartedAt);
|
|
634
786
|
gitIgnoreStart = undefined;
|
|
635
787
|
emitDiscoveryActivity(opts?.onDiscoveryProgress, "Listing Git ignore files", gitignoreFiles.length, gitignoreFiles.length);
|
|
@@ -712,8 +864,9 @@ async function listProjectFilesInternal(projectRoot, patterns = DEFAULT_PROJECT_
|
|
|
712
864
|
...(options?.onDiscoveryProgress ? { onDiscoveryProgress: options.onDiscoveryProgress } : {}),
|
|
713
865
|
...(options?.onDiscoveryTiming ? { onDiscoveryTiming: options.onDiscoveryTiming } : {}),
|
|
714
866
|
};
|
|
867
|
+
const discovery = options?.discoveryContext ?? createProjectDiscoveryContext(root);
|
|
715
868
|
const gitCandidates = attemptedGitCandidates
|
|
716
|
-
? await
|
|
869
|
+
? await getSharedGitCandidates(discovery, root, options?.logLevel, gitDiscoveryCallbacks)
|
|
717
870
|
: null;
|
|
718
871
|
// Report only when this call actually asked Git, including a null result. Callers that
|
|
719
872
|
// already enumerated candidates (build-index) can hand the same set to metadata discovery
|
|
@@ -723,13 +876,13 @@ async function listProjectFilesInternal(projectRoot, patterns = DEFAULT_PROJECT_
|
|
|
723
876
|
}
|
|
724
877
|
let gitignoreIndex = EMPTY_GITIGNORE_INDEX;
|
|
725
878
|
if (useGitignore && gitCandidates) {
|
|
726
|
-
gitignoreIndex = await buildGitignoreIndex(gitCandidates.gitignoreFiles, gitCandidates.gitignoreRoots, gitCandidates.gitignoreAliases);
|
|
879
|
+
gitignoreIndex = await sharedGitignoreIndex(discovery, `git:${fileIdentityKey(root)}`, () => buildGitignoreIndex(gitCandidates.gitignoreFiles, gitCandidates.gitignoreRoots, gitCandidates.gitignoreAliases, discovery));
|
|
727
880
|
}
|
|
728
881
|
else if (useGitignore) {
|
|
729
882
|
const gitignoreRoot = options?.gitignoreRoot
|
|
730
883
|
? await ensureDirectoryReadable(options.gitignoreRoot, "Gitignore root")
|
|
731
884
|
: root;
|
|
732
|
-
gitignoreIndex = await loadGitignoreIndexForRootAliases(gitignoreRoot);
|
|
885
|
+
gitignoreIndex = await sharedGitignoreIndex(discovery, `fs:${fileIdentityKey(gitignoreRoot)}`, () => loadGitignoreIndexForRootAliases(gitignoreRoot, discovery));
|
|
733
886
|
}
|
|
734
887
|
// Git returns a candidate set, not a filtered result: the pattern, include,
|
|
735
888
|
// default-ignore, and gitignore checks below still decide what is indexable.
|
|
@@ -746,6 +899,7 @@ async function listProjectFilesInternal(projectRoot, patterns = DEFAULT_PROJECT_
|
|
|
746
899
|
dot: true,
|
|
747
900
|
followSymbolicLinks: false,
|
|
748
901
|
ignore: fastGlobIgnoreGlobs,
|
|
902
|
+
...globFilesystemOption(discovery),
|
|
749
903
|
});
|
|
750
904
|
emitDiscoveryTiming(options?.onDiscoveryTiming, "filesystem-scan", scanStart);
|
|
751
905
|
emitDiscoveryActivity(options?.onDiscoveryProgress, "Scanning project files", files.length, files.length);
|
|
@@ -765,6 +919,7 @@ async function listProjectFilesInternal(projectRoot, patterns = DEFAULT_PROJECT_
|
|
|
765
919
|
dot: true,
|
|
766
920
|
followSymbolicLinks: false,
|
|
767
921
|
ignore: translatedUserIgnoreGlobs,
|
|
922
|
+
...globFilesystemOption(discovery),
|
|
768
923
|
})
|
|
769
924
|
: [];
|
|
770
925
|
const reportSourceSymlinkChecks = options?.onDiscoveryProgress
|
|
@@ -783,6 +938,7 @@ async function listProjectFilesInternal(projectRoot, patterns = DEFAULT_PROJECT_
|
|
|
783
938
|
? { onSymlinkDirectoriesDiscovered: options.onSymlinkDirectoriesDiscovered }
|
|
784
939
|
: {}),
|
|
785
940
|
...(reportSourceSymlinkChecks ? { onPathCheckProgress: reportSourceSymlinkChecks } : {}),
|
|
941
|
+
...globFilesystemField(discovery),
|
|
786
942
|
};
|
|
787
943
|
const safeSymlinkDirectories = await resolveSafeSymlinkDirectories(root, realRoot, symlinkProbeIgnoreGlobs, symlinkOptions);
|
|
788
944
|
const linkedFiles = await listEntriesFromSafeSymlinkDirectories(root, realRoot, patterns, fastGlobIgnoreGlobs, {
|
|
@@ -796,6 +952,7 @@ async function listProjectFilesInternal(projectRoot, patterns = DEFAULT_PROJECT_
|
|
|
796
952
|
globRoot,
|
|
797
953
|
filterIgnoreGlobs: userIgnoreGlobs,
|
|
798
954
|
resolvedSafeSymlinkDirectories: safeSymlinkDirectories,
|
|
955
|
+
...globFilesystemField(discovery),
|
|
799
956
|
});
|
|
800
957
|
// Cheap path predicates run before the realpath confinement probe. Git enumerates
|
|
801
958
|
// every tracked and untracked file it knows about, including trees this project
|
|
@@ -960,6 +1117,7 @@ async function resolveSafeSymlinkDirectories(root, realRoot, ignore, options) {
|
|
|
960
1117
|
followSymbolicLinks: false,
|
|
961
1118
|
objectMode: true,
|
|
962
1119
|
ignore,
|
|
1120
|
+
...(options.globFilesystem ? { fs: options.globFilesystem } : {}),
|
|
963
1121
|
}));
|
|
964
1122
|
const discovered = await verifySafeSymlinkDirectories(root, realRoot, entries.filter((entry) => entry.dirent.isSymbolicLink()).map((entry) => entry.path), options.onPathCheckProgress);
|
|
965
1123
|
options.onSymlinkDirectoriesDiscovered?.(discovered, "filesystem");
|
|
@@ -985,6 +1143,7 @@ async function listEntriesFromSafeSymlinkDirectories(root, realRoot, patterns, i
|
|
|
985
1143
|
ignore: locationIndependentIgnores,
|
|
986
1144
|
...(options.onlyFiles !== undefined ? { onlyFiles: options.onlyFiles } : {}),
|
|
987
1145
|
...(options.markDirectories !== undefined ? { markDirectories: options.markDirectories } : {}),
|
|
1146
|
+
...(options.globFilesystem ? { fs: options.globFilesystem } : {}),
|
|
988
1147
|
})).filter((filePath) => {
|
|
989
1148
|
const cleanPath = filePath.endsWith("/") ? filePath.slice(0, -1) : filePath;
|
|
990
1149
|
return !rootRelativeIgnoreMatchers.some((matcher) => matchesDiscoveryGlob(cleanPath, globRoot, matcher));
|
|
@@ -1019,14 +1178,14 @@ export async function filterRealPathsWithinRoot(paths, realRoot, onPathCheckProg
|
|
|
1019
1178
|
function toProjectGlob(pattern) {
|
|
1020
1179
|
return pattern.startsWith("**/") ? pattern : `**/${pattern}`;
|
|
1021
1180
|
}
|
|
1022
|
-
async function buildProjectFileInfo(def, filePath) {
|
|
1181
|
+
async function buildProjectFileInfo(def, filePath, state) {
|
|
1023
1182
|
const normalizedPath = normalizePath(filePath);
|
|
1024
1183
|
const projectRoot = normalizePath(path.dirname(filePath));
|
|
1025
1184
|
let name = null;
|
|
1026
1185
|
if (def.parseName && def.kind === "file") {
|
|
1027
1186
|
try {
|
|
1028
|
-
const
|
|
1029
|
-
name = def.parseName(
|
|
1187
|
+
const text = await readProjectDiscoveryFileText(state, filePath);
|
|
1188
|
+
name = def.parseName(text, filePath);
|
|
1030
1189
|
}
|
|
1031
1190
|
catch {
|
|
1032
1191
|
name = null;
|
|
@@ -1069,12 +1228,14 @@ async function discoverProjectFilesInternal(projectRoot, options) {
|
|
|
1069
1228
|
return matcher ? matcher.test(fileName) : pattern === fileName;
|
|
1070
1229
|
});
|
|
1071
1230
|
};
|
|
1231
|
+
const discovery = options?.discoveryContext ?? createProjectDiscoveryContext(root);
|
|
1072
1232
|
let gitCandidates;
|
|
1073
1233
|
if (options?.knownGitCandidates !== undefined) {
|
|
1074
1234
|
gitCandidates = options.knownGitCandidates;
|
|
1235
|
+
rememberGitCandidates(discovery, root, gitCandidates);
|
|
1075
1236
|
}
|
|
1076
1237
|
else {
|
|
1077
|
-
gitCandidates = await
|
|
1238
|
+
gitCandidates = await getSharedGitCandidates(discovery, root, options?.logLevel, {
|
|
1078
1239
|
...(options?.onDiscoveryProgress ? { onDiscoveryProgress: options.onDiscoveryProgress } : {}),
|
|
1079
1240
|
...(options?.onDiscoveryTiming ? { onDiscoveryTiming: options.onDiscoveryTiming } : {}),
|
|
1080
1241
|
});
|
|
@@ -1091,7 +1252,7 @@ async function discoverProjectFilesInternal(projectRoot, options) {
|
|
|
1091
1252
|
: undefined;
|
|
1092
1253
|
let rootSafeMatches;
|
|
1093
1254
|
if (gitCandidates) {
|
|
1094
|
-
const gitignoreIndex = await buildGitignoreIndex(gitCandidates.gitignoreFiles, gitCandidates.gitignoreRoots, gitCandidates.gitignoreAliases);
|
|
1255
|
+
const gitignoreIndex = await sharedGitignoreIndex(discovery, `git:${fileIdentityKey(root)}`, () => buildGitignoreIndex(gitCandidates.gitignoreFiles, gitCandidates.gitignoreRoots, gitCandidates.gitignoreAliases, discovery));
|
|
1095
1256
|
const defaultIgnoreMatchers = DEFAULT_PROJECT_FILE_IGNORES.map((globPattern) => picomatch(globPattern, { dot: true }));
|
|
1096
1257
|
const filterGitIgnoredEntries = (entries, directoryKeys = new Set()) => entries
|
|
1097
1258
|
.map(({ path: filePath, realPath }) => ({ filePath: normalizePath(filePath), realPath }))
|
|
@@ -1116,6 +1277,7 @@ async function discoverProjectFilesInternal(projectRoot, options) {
|
|
|
1116
1277
|
? { onSymlinkDirectoriesDiscovered: options.onSymlinkDirectoriesDiscovered }
|
|
1117
1278
|
: {}),
|
|
1118
1279
|
...(reportMetadataSymlinkChecks ? { onPathCheckProgress: reportMetadataSymlinkChecks } : {}),
|
|
1280
|
+
...globFilesystemField(discovery),
|
|
1119
1281
|
};
|
|
1120
1282
|
const safeSymlinkDirectories = await resolveSafeSymlinkDirectories(root, realRoot, DEFAULT_PROJECT_FILE_IGNORES, symlinkOptions);
|
|
1121
1283
|
const allPatterns = PROJECT_FILE_DEFINITIONS.flatMap((definition) => definition.patterns.map(toProjectGlob));
|
|
@@ -1123,6 +1285,7 @@ async function discoverProjectFilesInternal(projectRoot, options) {
|
|
|
1123
1285
|
markDirectories: true,
|
|
1124
1286
|
onlyFiles: false,
|
|
1125
1287
|
resolvedSafeSymlinkDirectories: safeSymlinkDirectories,
|
|
1288
|
+
...globFilesystemField(discovery),
|
|
1126
1289
|
});
|
|
1127
1290
|
const candidateFiles = Array.from(new Set(gitCandidates.files.map(normalizePath)));
|
|
1128
1291
|
// Only a candidate whose basename can name a manifest becomes a metadata entry, so
|
|
@@ -1155,6 +1318,7 @@ async function discoverProjectFilesInternal(projectRoot, options) {
|
|
|
1155
1318
|
ignore: DEFAULT_PROJECT_FILE_IGNORES,
|
|
1156
1319
|
markDirectories: true,
|
|
1157
1320
|
onlyFiles: false,
|
|
1321
|
+
...globFilesystemOption(discovery),
|
|
1158
1322
|
});
|
|
1159
1323
|
const linkedMatches = await listEntriesFromSafeSymlinkDirectories(root, realRoot, allPatterns, DEFAULT_PROJECT_FILE_IGNORES, {
|
|
1160
1324
|
markDirectories: true,
|
|
@@ -1166,6 +1330,7 @@ async function discoverProjectFilesInternal(projectRoot, options) {
|
|
|
1166
1330
|
? { onSymlinkDirectoriesDiscovered: options.onSymlinkDirectoriesDiscovered }
|
|
1167
1331
|
: {}),
|
|
1168
1332
|
...(reportMetadataSymlinkChecks ? { onPathCheckProgress: reportMetadataSymlinkChecks } : {}),
|
|
1333
|
+
...globFilesystemField(discovery),
|
|
1169
1334
|
});
|
|
1170
1335
|
rootSafeMatches = await filterRealPathsWithinRoot([...matches, ...linkedMatches].map((match) => (match.endsWith("/") ? match.slice(0, -1) : match)), realRoot, reportMetadataFileChecks);
|
|
1171
1336
|
}
|
|
@@ -1182,7 +1347,7 @@ async function discoverProjectFilesInternal(projectRoot, options) {
|
|
|
1182
1347
|
continue;
|
|
1183
1348
|
const matchesPattern = matchesDefinition(fileName, definitionIndex);
|
|
1184
1349
|
if (matchesPattern) {
|
|
1185
|
-
entries.push(await buildProjectFileInfo(def, cleanMatch));
|
|
1350
|
+
entries.push(await buildProjectFileInfo(def, cleanMatch, discovery));
|
|
1186
1351
|
}
|
|
1187
1352
|
}
|
|
1188
1353
|
});
|
|
@@ -1206,4 +1371,88 @@ async function discoverProjectFilesInternal(projectRoot, options) {
|
|
|
1206
1371
|
throw new Error(`Failed to discover project files in ${root}: ${stringifyUnknown(error)}`);
|
|
1207
1372
|
}
|
|
1208
1373
|
}
|
|
1374
|
+
async function addGitExcludeFiles(files, repositoryRoot, gitCache) {
|
|
1375
|
+
const excludes = await listGitExcludeFiles(repositoryRoot, gitExcludeListOptions(gitCache));
|
|
1376
|
+
for (const source of excludes) {
|
|
1377
|
+
files.add(normalizePath(source.file));
|
|
1378
|
+
}
|
|
1379
|
+
}
|
|
1380
|
+
async function collectAncestorGitignoreFiles(fromDir, stopDir) {
|
|
1381
|
+
const files = [];
|
|
1382
|
+
let current = normalizePath(fromDir);
|
|
1383
|
+
const stopKey = fileIdentityKey(stopDir);
|
|
1384
|
+
while (true) {
|
|
1385
|
+
const hit = await gitignoreFileIfPresent(current);
|
|
1386
|
+
if (hit)
|
|
1387
|
+
files.push(hit);
|
|
1388
|
+
if (fileIdentityKey(current) === stopKey)
|
|
1389
|
+
break;
|
|
1390
|
+
const parent = normalizePath(path.dirname(current));
|
|
1391
|
+
if (parent === current)
|
|
1392
|
+
break;
|
|
1393
|
+
if (!isFilePathWithinRoot(stopDir, parent) && fileIdentityKey(parent) !== stopKey)
|
|
1394
|
+
break;
|
|
1395
|
+
current = parent;
|
|
1396
|
+
}
|
|
1397
|
+
return files;
|
|
1398
|
+
}
|
|
1399
|
+
async function addGitignoreFilesForRoots(files, roots, state, scanned) {
|
|
1400
|
+
for (const ignoreRoot of roots) {
|
|
1401
|
+
const key = fileIdentityKey(ignoreRoot);
|
|
1402
|
+
if (scanned.has(key))
|
|
1403
|
+
continue;
|
|
1404
|
+
scanned.add(key);
|
|
1405
|
+
for (const file of await findGitignoreFiles(ignoreRoot, state)) {
|
|
1406
|
+
files.add(normalizePath(file));
|
|
1407
|
+
}
|
|
1408
|
+
}
|
|
1409
|
+
}
|
|
1410
|
+
export async function collectConfigHashInputFiles(projectRoot, discoveryContext, logLevel, callbacks) {
|
|
1411
|
+
const root = await ensureDirectoryReadable(projectRoot, "Project root");
|
|
1412
|
+
const state = discoveryContext;
|
|
1413
|
+
const files = new Set(await listRootConfigFiles(root, state));
|
|
1414
|
+
const gitCache = discoveryContext.git;
|
|
1415
|
+
const gitCandidates = await getSharedGitCandidates(discoveryContext, root, logLevel, callbacks);
|
|
1416
|
+
if (gitCandidates) {
|
|
1417
|
+
for (const source of gitCandidates.gitignoreFiles) {
|
|
1418
|
+
files.add(normalizePath(source.file));
|
|
1419
|
+
}
|
|
1420
|
+
return { files: Array.from(files).sort() };
|
|
1421
|
+
}
|
|
1422
|
+
const realRoot = normalizePath(await fsp.realpath(root));
|
|
1423
|
+
const gitRootCandidate = (await isGitRepo(root, gitCache)) ? root : realRoot;
|
|
1424
|
+
if (!(await isGitRepo(gitRootCandidate, gitCache))) {
|
|
1425
|
+
await addGitignoreFilesForRoots(files, [root], state, new Set());
|
|
1426
|
+
return { files: Array.from(files).sort() };
|
|
1427
|
+
}
|
|
1428
|
+
if (await isGitProjectRootIgnored(realRoot, gitCache)) {
|
|
1429
|
+
const scanned = new Set();
|
|
1430
|
+
await addGitignoreFilesForRoots(files, [root], state, scanned);
|
|
1431
|
+
const gitRoot = await getGitRepositoryRoot(gitRootCandidate);
|
|
1432
|
+
if (gitRoot) {
|
|
1433
|
+
await addGitExcludeFiles(files, gitRoot, gitCache);
|
|
1434
|
+
const submoduleDirectories = await listGitSubmoduleDirectories(gitRoot, { recurse: true });
|
|
1435
|
+
await addGitignoreFilesForRoots(files, [gitRoot, ...submoduleDirectories], state, scanned);
|
|
1436
|
+
for (const submodule of submoduleDirectories) {
|
|
1437
|
+
await addGitExcludeFiles(files, submodule, gitCache);
|
|
1438
|
+
}
|
|
1439
|
+
}
|
|
1440
|
+
return { files: Array.from(files).sort() };
|
|
1441
|
+
}
|
|
1442
|
+
const gitRoot = await getGitRepositoryRoot(gitRootCandidate);
|
|
1443
|
+
if (gitRoot) {
|
|
1444
|
+
for (const file of await collectAncestorGitignoreFiles(root, gitRoot)) {
|
|
1445
|
+
files.add(normalizePath(file));
|
|
1446
|
+
}
|
|
1447
|
+
await addGitExcludeFiles(files, gitRoot, gitCache);
|
|
1448
|
+
const submoduleDirectories = await listGitSubmoduleDirectories(gitRoot, { recurse: true });
|
|
1449
|
+
for (const submodule of submoduleDirectories) {
|
|
1450
|
+
await addGitExcludeFiles(files, submodule, gitCache);
|
|
1451
|
+
}
|
|
1452
|
+
}
|
|
1453
|
+
return {
|
|
1454
|
+
files: Array.from(files).sort(),
|
|
1455
|
+
error: "Failed to enumerate config files: Git listing unavailable",
|
|
1456
|
+
};
|
|
1457
|
+
}
|
|
1209
1458
|
//# sourceMappingURL=projectFiles.js.map
|