Implement workspace-aware folder browser

This commit is contained in:
Shantur Rathore
2025-11-17 22:05:38 +00:00
parent f53564bb06
commit 40e8c90bab
8 changed files with 526 additions and 14 deletions

View File

@@ -13,14 +13,17 @@ export class FileSystemBrowser {
this.root = path.resolve(options.rootDir)
}
list(relativePath: string, depth = 2): FileSystemEntry[] {
list(relativePath: string, options: { depth?: number; includeFiles?: boolean } = {}): FileSystemEntry[] {
const depth = options.depth ?? 2
const includeFiles = options.includeFiles ?? true
if (depth < 1) {
throw new Error("Depth must be at least 1")
}
return this.walk(relativePath, depth)
const normalizedPath = this.normalizeRelativePath(relativePath)
return this.walk(normalizedPath, depth, includeFiles)
}
private walk(relativePath: string, remainingDepth: number): FileSystemEntry[] {
private walk(relativePath: string, remainingDepth: number, includeFiles: boolean): FileSystemEntry[] {
const resolved = this.toAbsolute(relativePath)
const entries = fs.readdirSync(resolved, { withFileTypes: true })
@@ -31,21 +34,39 @@ export class FileSystemBrowser {
const current: FileSystemEntry = {
name: entry.name,
path: entryPath,
path: this.normalizeRelativePath(entryPath),
type: entry.isDirectory() ? "directory" : "file",
size: entry.isDirectory() ? undefined : stats.size,
modifiedAt: stats.mtime.toISOString(),
}
if (entry.isDirectory() && remainingDepth > 1) {
const nested = this.walk(entryPath, remainingDepth - 1)
const nested = this.walk(entryPath, remainingDepth - 1, includeFiles)
return [current, ...nested]
}
if (!entry.isDirectory() && !includeFiles) {
return []
}
return [current]
})
}
private normalizeRelativePath(input: string | undefined) {
if (!input || input === "." || input === "./" || input === "/") {
return "."
}
let normalized = input.replace(/\\+/g, "/")
if (normalized.startsWith("./")) {
normalized = normalized.replace(/^\.\/+/, "")
}
if (normalized.startsWith("/")) {
normalized = normalized.replace(/^\/+/g, "")
}
return normalized === "" ? "." : normalized
}
readFile(relativePath: string): string {
const resolved = this.toAbsolute(relativePath)
return fs.readFileSync(resolved, "utf-8")

View File

@@ -9,6 +9,7 @@ interface RouteDeps {
const FilesystemQuerySchema = z.object({
path: z.string().optional(),
depth: z.coerce.number().int().min(1).max(10).default(2),
includeFiles: z.coerce.boolean().default(true),
})
export function registerFilesystemRoutes(app: FastifyInstance, deps: RouteDeps) {
@@ -17,7 +18,10 @@ export function registerFilesystemRoutes(app: FastifyInstance, deps: RouteDeps)
const targetPath = query.path ?? "."
try {
return deps.fileSystemBrowser.list(targetPath, query.depth)
return deps.fileSystemBrowser.list(targetPath, {
depth: query.depth,
includeFiles: query.includeFiles,
})
} catch (error) {
reply.code(400)
return { error: (error as Error).message }