async function readFullStructure()

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;
}