( projectId: string, filePath: string )
| 177 | const MAX_FILE_BYTES = 500_000; // 500KB safeguard |
| 178 | |
| 179 | export async function readProjectFileContent( |
| 180 | projectId: string, |
| 181 | filePath: string |
| 182 | ): Promise<{ path: string; content: string }> { |
| 183 | const project = await getProjectById(projectId); |
| 184 | if (!project) { |
| 185 | throw new FileBrowserError('Project not found', 404); |
| 186 | } |
| 187 | |
| 188 | const repoRoot = resolveRepoRoot(project); |
| 189 | const normalizedPath = normalizeRelativePath(filePath); |
| 190 | const absolutePath = await resolveSafePath( |
| 191 | repoRoot, |
| 192 | normalizedPath === '.' ? '.' : normalizedPath |
| 193 | ); |
| 194 | |
| 195 | let stats; |
| 196 | try { |
| 197 | stats = await fs.stat(absolutePath); |
| 198 | } catch (error) { |
| 199 | throw new FileBrowserError('File not found', 404); |
| 200 | } |
| 201 | |
| 202 | if (!stats.isFile()) { |
| 203 | throw new FileBrowserError('Not a file', 400); |
| 204 | } |
| 205 | |
| 206 | if (stats.size > MAX_FILE_BYTES) { |
| 207 | throw new FileBrowserError('File too large to display', 400); |
| 208 | } |
| 209 | |
| 210 | try { |
| 211 | const content = await fs.readFile(absolutePath, 'utf-8'); |
| 212 | return { |
| 213 | path: normalizedPath.replace(/\\/g, '/'), |
| 214 | content, |
| 215 | }; |
| 216 | } catch (error) { |
| 217 | throw new FileBrowserError('Failed to read file', 500); |
| 218 | } |
| 219 | } |
| 220 | |
| 221 | const MAX_WRITE_BYTES = 1_000_000; // 1MB safeguard |
| 222 |
no test coverage detected