import type { EntryType, FileEntry } from "../../shared/types"; import type { FileSystemProvider } from "../fs/provider"; export interface WalkedTree { /** Relative dir paths as segment arrays, top-down. [] is the root itself. */ dirs: string[][]; files: { entry: FileEntry; dirSegments: string[] }[]; totalBytes: number; skippedSymlinks: number; } /** * Depth-first walk of a directory. Segment arrays (not joined strings) so the * destination side can re-join them with its own separator. Symlinks are * skipped — following them risks cycles and surprising copies. */ export async function walkTree( fs: FileSystemProvider, rootDir: string, ): Promise { const tree: WalkedTree = { dirs: [[]], files: [], totalBytes: 0, skippedSymlinks: 0 }; async function visit(dir: string, segments: string[]): Promise { for (const entry of await fs.list(dir)) { if (entry.type === "symlink") { tree.skippedSymlinks++; } else if (entry.type === "dir") { const childSegments = [...segments, entry.name]; tree.dirs.push(childSegments); await visit(entry.path, childSegments); } else { tree.files.push({ entry, dirSegments: segments }); tree.totalBytes += entry.size; } } } await visit(rootDir, []); return tree; } /** Post-order recursive delete, provider-generic (works locally and over SFTP). */ export async function removeRecursive( fs: FileSystemProvider, path: string, type: EntryType, ): Promise { if (type !== "dir") { await fs.deleteFile(path); return; } for (const entry of await fs.list(path)) { await removeRecursive(fs, entry.path, entry.type); } await fs.deleteEmptyDir(path); }