( projectId: string, dir = '.' )
| 98 | } |
| 99 | |
| 100 | export async function listProjectDirectory( |
| 101 | projectId: string, |
| 102 | dir = '.' |
| 103 | ): Promise<ProjectFileEntry[]> { |
| 104 | const project = await getProjectById(projectId); |
| 105 | if (!project) { |
| 106 | throw new FileBrowserError('Project not found', 404); |
| 107 | } |
| 108 | |
| 109 | const repoRoot = resolveRepoRoot(project); |
| 110 | const targetDir = normalizeRelativePath(dir); |
| 111 | const absoluteDir = await resolveSafePath(repoRoot, targetDir === '.' ? '.' : targetDir); |
| 112 | |
| 113 | let stats; |
| 114 | try { |
| 115 | stats = await fs.stat(absoluteDir); |
| 116 | } catch (error) { |
| 117 | throw new FileBrowserError('Directory not found', 404); |
| 118 | } |
| 119 | |
| 120 | if (!stats.isDirectory()) { |
| 121 | throw new FileBrowserError('Not a directory', 400); |
| 122 | } |
| 123 | |
| 124 | let dirEntries; |
| 125 | try { |
| 126 | dirEntries = await fs.readdir(absoluteDir, { withFileTypes: true }); |
| 127 | } catch (error) { |
| 128 | throw new FileBrowserError('Failed to read directory', 500); |
| 129 | } |
| 130 | |
| 131 | const entries: ProjectFileEntry[] = []; |
| 132 | |
| 133 | for (const entry of dirEntries) { |
| 134 | if (entry.isSymbolicLink()) { |
| 135 | continue; |
| 136 | } |
| 137 | if (entry.isDirectory() && EXCLUDED_DIRECTORIES.has(entry.name)) { |
| 138 | continue; |
| 139 | } |
| 140 | if (!entry.isDirectory() && EXCLUDED_FILES.has(entry.name)) { |
| 141 | continue; |
| 142 | } |
| 143 | |
| 144 | const relativePath = joinRelativePath(targetDir, entry.name); |
| 145 | const absolutePath = await resolveSafePath(repoRoot, relativePath); |
| 146 | |
| 147 | if (entry.isDirectory()) { |
| 148 | const hasChildren = await directoryHasVisibleChildren(absolutePath); |
| 149 | entries.push({ |
| 150 | name: entry.name, |
| 151 | path: relativePath.replace(/\\/g, '/'), |
| 152 | type: 'directory', |
| 153 | hasChildren, |
| 154 | }); |
| 155 | } else { |
| 156 | const fileStats = await fs.stat(absolutePath); |
| 157 | entries.push({ |
no test coverage detected