Implement workspace-aware folder browser
This commit is contained in:
@@ -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")
|
||||
|
||||
@@ -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 }
|
||||
|
||||
Reference in New Issue
Block a user