in packages/core/src/utils/getFolderStructure.ts [58:206]
async function readFullStructure(
rootPath: string,
options: MergedFolderStructureOptions,
): Promise<FullFolderInfo | null> {
const rootName = path.basename(rootPath);
const rootNode: FullFolderInfo = {
name: rootName,
path: rootPath,
files: [],
subFolders: [],
totalChildren: 0,
totalFiles: 0,
};
const queue: Array<{ folderInfo: FullFolderInfo; currentPath: string }> = [
{ folderInfo: rootNode, currentPath: rootPath },
];
let currentItemCount = 0;
// Count the root node itself as one item if we are not just listing its content
const processedPaths = new Set<string>(); // To avoid processing same path if symlinks create loops
while (queue.length > 0) {
const { folderInfo, currentPath } = queue.shift()!;
if (processedPaths.has(currentPath)) {
continue;
}
processedPaths.add(currentPath);
if (currentItemCount >= options.maxItems) {
// If the root itself caused us to exceed, we can't really show anything.
// Otherwise, this folder won't be processed further.
// The parent that queued this would have set its own hasMoreSubfolders flag.
continue;
}
let entries: Dirent[];
try {
const rawEntries = await fs.readdir(currentPath, { withFileTypes: true });
// Sort entries alphabetically by name for consistent processing order
entries = rawEntries.sort((a, b) => a.name.localeCompare(b.name));
} catch (error: unknown) {
if (
isNodeError(error) &&
(error.code === 'EACCES' || error.code === 'ENOENT')
) {
console.warn(
`Warning: Could not read directory ${currentPath}: ${error.message}`,
);
if (currentPath === rootPath && error.code === 'ENOENT') {
return null; // Root directory itself not found
}
// For other EACCES/ENOENT on subdirectories, just skip them.
continue;
}
throw error;
}
const filesInCurrentDir: string[] = [];
const subFoldersInCurrentDir: FullFolderInfo[] = [];
// Process files first in the current directory
for (const entry of entries) {
if (entry.isFile()) {
if (currentItemCount >= options.maxItems) {
folderInfo.hasMoreFiles = true;
break;
}
const fileName = entry.name;
const filePath = path.join(currentPath, fileName);
if (options.respectGitIgnore && options.fileService) {
if (options.fileService.shouldGitIgnoreFile(filePath)) {
continue;
}
}
if (
!options.fileIncludePattern ||
options.fileIncludePattern.test(fileName)
) {
filesInCurrentDir.push(fileName);
currentItemCount++;
folderInfo.totalFiles++;
folderInfo.totalChildren++;
}
}
}
folderInfo.files = filesInCurrentDir;
// Then process directories and queue them
for (const entry of entries) {
if (entry.isDirectory()) {
// Check if adding this directory ITSELF would meet or exceed maxItems
// (currentItemCount refers to items *already* added before this one)
if (currentItemCount >= options.maxItems) {
folderInfo.hasMoreSubfolders = true;
break; // Already at limit, cannot add this folder or any more
}
// If adding THIS folder makes us hit the limit exactly, and it might have children,
// it's better to show '...' for the parent, unless this is the very last item slot.
// This logic is tricky. Let's try a simpler: if we can't add this item, mark and break.
const subFolderName = entry.name;
const subFolderPath = path.join(currentPath, subFolderName);
let isIgnoredByGit = false;
if (options.respectGitIgnore && options.fileService) {
if (options.fileService.shouldGitIgnoreFile(subFolderPath)) {
isIgnoredByGit = true;
}
}
if (options.ignoredFolders.has(subFolderName) || isIgnoredByGit) {
const ignoredSubFolder: FullFolderInfo = {
name: subFolderName,
path: subFolderPath,
files: [],
subFolders: [],
totalChildren: 0,
totalFiles: 0,
isIgnored: true,
};
subFoldersInCurrentDir.push(ignoredSubFolder);
currentItemCount++; // Count the ignored folder itself
folderInfo.totalChildren++; // Also counts towards parent's children
continue;
}
const subFolderNode: FullFolderInfo = {
name: subFolderName,
path: subFolderPath,
files: [],
subFolders: [],
totalChildren: 0,
totalFiles: 0,
};
subFoldersInCurrentDir.push(subFolderNode);
currentItemCount++;
folderInfo.totalChildren++; // Counts towards parent's children
// Add to queue for processing its children later
queue.push({ folderInfo: subFolderNode, currentPath: subFolderPath });
}
}
folderInfo.subFolders = subFoldersInCurrentDir;
}
return rootNode;
}