import fs from "fs/promises"; import path from "path"; const WORKSPACE_PATH = process.env.WORKSPACE_PATH || "/workspace"; export interface FileNode { name: string; path: string; type: "file" | "directory"; children?: FileNode[]; } const IGNORED_DIRS = new Set(["node_modules", ".git", ".nasher"]); export async function getDirectoryTree( repo: string, subPath: string = "", ): Promise { const fullPath = resolveRepoPath(repo, subPath); const entries = await fs.readdir(fullPath, { withFileTypes: true }); const nodes: FileNode[] = []; const sorted = entries.sort((a, b) => { if (a.isDirectory() && !b.isDirectory()) return -1; if (!a.isDirectory() && b.isDirectory()) return 1; return a.name.localeCompare(b.name); }); for (const entry of sorted) { if (entry.name.startsWith(".") && entry.name !== ".gitignore") continue; const relativePath = subPath ? `${subPath}/${entry.name}` : entry.name; if (entry.isDirectory()) { if (IGNORED_DIRS.has(entry.name)) continue; const children = await getDirectoryTree(repo, relativePath); nodes.push({ name: entry.name, path: relativePath, type: "directory", children, }); } else { nodes.push({ name: entry.name, path: relativePath, type: "file" }); } } return nodes; } export async function readFile( repo: string, filePath: string, ): Promise { const fullPath = resolveRepoPath(repo, filePath); return fs.readFile(fullPath, "utf-8"); } export async function writeFile( repo: string, filePath: string, content: string, ): Promise { const fullPath = resolveRepoPath(repo, filePath); await fs.mkdir(path.dirname(fullPath), { recursive: true }); await fs.writeFile(fullPath, content, "utf-8"); } export async function deleteFile( repo: string, filePath: string, ): Promise { const fullPath = resolveRepoPath(repo, filePath); await fs.unlink(fullPath); } function resolveRepoPath(repo: string, filePath: string): string { const repoRoot = path.resolve(path.join(WORKSPACE_PATH, "repos", repo)); const resolved = path.resolve(repoRoot, filePath); if (!resolved.startsWith(repoRoot)) { throw new Error("Path traversal detected"); } return resolved; }