import { readdir, realpath, stat } from "fs/promises"; import { homedir } from "os"; import path from "path"; export interface BrowsableDirectory { name: string; path: string; } export function getBrowseStartDirectory(directory?: string): string { return directory || homedir(); } export function normalizeDirectory(directory: string): string { if (directory === "~") return homedir(); if (directory.startsWith("~/")) return path.resolve(homedir(), directory.slice(2)); return path.resolve(directory); } export function getParentDirectory(directory: string): string | null { const pathApi = /^[a-zA-Z]:[\\/]/.test(directory) || directory.startsWith("\\\\") ? path.win32 : path; const normalized = pathApi.normalize(directory); const parent = pathApi.dirname(normalized); return parent === normalized ? null : parent; } export async function resolveDirectory(directory: string): Promise { return realpath(normalizeDirectory(directory)); } export async function listDirectories(directory: string): Promise { const entries = await readdir(directory, { withFileTypes: true }); // 忽略损坏、不可访问或不指向目录的符号链接。 const candidates = await Promise.all(entries.map(async (entry) => { if (entry.isDirectory()) { return { name: entry.name, path: path.join(directory, entry.name) }; } if (!entry.isSymbolicLink()) return null; try { const entryPath = path.join(directory, entry.name); const realEntryPath = await realpath(entryPath); const entryStat = await stat(realEntryPath); if (!entryStat.isDirectory()) return null; return { name: entry.name, path: entryPath }; } catch { return null; } })); return candidates .filter((entry): entry is BrowsableDirectory => entry !== null) .sort((left, right) => left.name.localeCompare(right.name)); }