( projectId: string, filePath: string, content: string )
| 221 | const MAX_WRITE_BYTES = 1_000_000; // 1MB safeguard |
| 222 | |
| 223 | export async function writeProjectFileContent( |
| 224 | projectId: string, |
| 225 | filePath: string, |
| 226 | content: string |
| 227 | ): Promise<void> { |
| 228 | const project = await getProjectById(projectId); |
| 229 | if (!project) { |
| 230 | throw new FileBrowserError('Project not found', 404); |
| 231 | } |
| 232 | |
| 233 | if (typeof content !== 'string') { |
| 234 | throw new FileBrowserError('Invalid file content', 400); |
| 235 | } |
| 236 | |
| 237 | const repoRoot = resolveRepoRoot(project); |
| 238 | const normalizedPath = normalizeRelativePath(filePath); |
| 239 | const absolutePath = await resolveSafePath( |
| 240 | repoRoot, |
| 241 | normalizedPath === '.' ? '.' : normalizedPath |
| 242 | ); |
| 243 | |
| 244 | let stats; |
| 245 | try { |
| 246 | stats = await fs.stat(absolutePath); |
| 247 | } catch (error) { |
| 248 | throw new FileBrowserError('File not found', 404); |
| 249 | } |
| 250 | |
| 251 | if (!stats.isFile()) { |
| 252 | throw new FileBrowserError('Not a file', 400); |
| 253 | } |
| 254 | |
| 255 | if (content.length > MAX_WRITE_BYTES) { |
| 256 | throw new FileBrowserError('File content too large', 400); |
| 257 | } |
| 258 | |
| 259 | try { |
| 260 | await fs.writeFile(absolutePath, content, 'utf-8'); |
| 261 | } catch (error) { |
| 262 | throw new FileBrowserError('Failed to write file', 500); |
| 263 | } |
| 264 | } |
no test coverage detected