Merge branch 'investigate-agents-enonent'

This commit is contained in:
Mohamed Boudra
2026-02-09 13:33:49 +07:00
2 changed files with 58 additions and 2 deletions

View File

@@ -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 });
}
});
});

View File

@@ -96,15 +96,32 @@ export async function listDirectoryEntries({
const dirents = await fs.readdir(directoryPath, { withFileTypes: true });
const entries = await Promise.all(
const entriesWithNulls = await Promise.all(
dirents.map(async (dirent) => {
const targetPath = path.join(directoryPath, dirent.name);
const kind: ExplorerEntryKind = dirent.isDirectory()
? "directory"
: "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) => {
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({
root,
targetPath,