Update files
This commit is contained in:
@@ -0,0 +1,34 @@
|
|||||||
|
import { mkdtemp, mkdir, rm, symlink, writeFile } from "node:fs/promises";
|
||||||
|
import os from "node:os";
|
||||||
|
import path from "node:path";
|
||||||
|
import { describe, expect, it } from "vitest";
|
||||||
|
import { listDirectoryEntries } from "./service.js";
|
||||||
|
|
||||||
|
async function createTempDir(prefix: string): Promise<string> {
|
||||||
|
return mkdtemp(path.join(os.tmpdir(), prefix));
|
||||||
|
}
|
||||||
|
|
||||||
|
describe("file explorer service", () => {
|
||||||
|
it("lists directory entries even when a dangling symlink exists", async () => {
|
||||||
|
const root = await createTempDir("paseo-file-explorer-");
|
||||||
|
|
||||||
|
try {
|
||||||
|
await mkdir(path.join(root, "packages", "server"), { recursive: true });
|
||||||
|
const serverDir = path.join(root, "packages", "server");
|
||||||
|
await writeFile(path.join(serverDir, "README.md"), "# server\n", "utf-8");
|
||||||
|
await symlink("CLAUDE.md", path.join(serverDir, "AGENTS.md"));
|
||||||
|
|
||||||
|
const result = await listDirectoryEntries({
|
||||||
|
root,
|
||||||
|
relativePath: "packages/server",
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(result.path).toBe("packages/server");
|
||||||
|
const names = result.entries.map((entry) => entry.name);
|
||||||
|
expect(names).toContain("README.md");
|
||||||
|
expect(names).not.toContain("AGENTS.md");
|
||||||
|
} finally {
|
||||||
|
await rm(root, { recursive: true, force: true });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -96,15 +96,32 @@ export async function listDirectoryEntries({
|
|||||||
|
|
||||||
const dirents = await fs.readdir(directoryPath, { withFileTypes: true });
|
const dirents = await fs.readdir(directoryPath, { withFileTypes: true });
|
||||||
|
|
||||||
const entries = await Promise.all(
|
const entriesWithNulls = await Promise.all(
|
||||||
dirents.map(async (dirent) => {
|
dirents.map(async (dirent) => {
|
||||||
const targetPath = path.join(directoryPath, dirent.name);
|
const targetPath = path.join(directoryPath, dirent.name);
|
||||||
const kind: ExplorerEntryKind = dirent.isDirectory()
|
const kind: ExplorerEntryKind = dirent.isDirectory()
|
||||||
? "directory"
|
? "directory"
|
||||||
: "file";
|
: "file";
|
||||||
return buildEntryPayload({ root, targetPath, name: dirent.name, kind });
|
try {
|
||||||
|
return await buildEntryPayload({
|
||||||
|
root,
|
||||||
|
targetPath,
|
||||||
|
name: dirent.name,
|
||||||
|
kind,
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
// Directories can contain dangling links (e.g. AGENTS.md -> CLAUDE.md).
|
||||||
|
// Skip entries whose targets disappeared instead of failing the whole listing.
|
||||||
|
if (isMissingEntryError(error)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
const entries = entriesWithNulls.filter(
|
||||||
|
(entry): entry is FileExplorerEntry => entry !== null
|
||||||
|
);
|
||||||
|
|
||||||
entries.sort((a, b) => {
|
entries.sort((a, b) => {
|
||||||
const modifiedComparison =
|
const modifiedComparison =
|
||||||
@@ -236,6 +253,11 @@ async function buildEntryPayload({
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function isMissingEntryError(error: unknown): boolean {
|
||||||
|
const code = (error as NodeJS.ErrnoException | null)?.code;
|
||||||
|
return code === "ENOENT" || code === "ENOTDIR" || code === "ELOOP";
|
||||||
|
}
|
||||||
|
|
||||||
function normalizeRelativePath({
|
function normalizeRelativePath({
|
||||||
root,
|
root,
|
||||||
targetPath,
|
targetPath,
|
||||||
|
|||||||
Reference in New Issue
Block a user