import { readdir, realpath, stat } from "fs/promises"; import { homedir } from "os"; import path from "path"; export interface BrowsableDirectory { name: string; path: string; } export function shouldShowWindowsDrivePicker( directory?: string, platform: NodeJS.Platform = process.platform, ): boolean { return platform === "win32" && !directory; } export function getBrowseStartDirectory(directory?: string): string { return directory || homedir(); } export function getWindowsDriveCandidates(): BrowsableDirectory[] { return "ABCDEFGHIJKLMNOPQRSTUVWXYZ".split("").map((letter) => ({ name: `${letter}:`, path: `${letter}:\\`, })); } export async function listWindowsDrives(): Promise { const candidates = await Promise.all(getWindowsDriveCandidates().map(async (drive) => { try { const driveStat = await stat(drive.path); return driveStat.isDirectory() ? drive : null; } catch { return null; } })); return candidates.filter((drive): drive is BrowsableDirectory => drive !== null); } 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.posix; 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)); }