Skip to content

Commit df9b301

Browse files
committed
feat(fs-bridge): format paths with leading and trailing slashes
Updated the `createFSEntry` function to ensure all paths have a leading slash and directories have a trailing slash. This change improves path consistency across the file system bridge.
1 parent 57a5785 commit df9b301

File tree

2 files changed

+37
-26
lines changed

2 files changed

+37
-26
lines changed

packages/fs-bridge/src/bridges/node.ts

Lines changed: 19 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import type { Dirent } from "node:fs";
22
import type { FSEntry } from "../types";
33
import fsp from "node:fs/promises";
44
import nodePath from "node:path";
5-
import { trimTrailingSlash } from "@luxass/utils/path";
5+
import { appendTrailingSlash, prependLeadingSlash } from "@luxass/utils/path";
66
import { createDebugger } from "@ucdjs-internal/shared";
77
import { assertNotUNCPath } from "@ucdjs/path-utils";
88
import { z } from "zod";
@@ -75,19 +75,30 @@ const NodeFileSystemBridge = defineFileSystemBridge({
7575
async listdir(path, recursive = false) {
7676
const targetPath = resolveSafePath(basePath, path);
7777

78-
function createFSEntry(entry: Dirent): FSEntry {
79-
const pathFromName = trimTrailingSlash(entry.name);
78+
/**
79+
* Formats a relative path to match FileEntry schema requirements:
80+
* - Leading slash required for all paths
81+
* - Trailing slash required for directories
82+
*/
83+
function formatEntryPath(relativePath: string, isDirectory: boolean): string {
84+
const withLeadingSlash = prependLeadingSlash(relativePath);
85+
return isDirectory ? appendTrailingSlash(withLeadingSlash) : withLeadingSlash;
86+
}
87+
88+
function createFSEntry(entry: Dirent, relativePath?: string): FSEntry {
89+
const pathBase = relativePath ?? entry.name;
90+
const formattedPath = formatEntryPath(pathBase, entry.isDirectory());
8091
return entry.isDirectory()
8192
? {
8293
type: "directory",
8394
name: entry.name,
84-
path: pathFromName,
95+
path: formattedPath,
8596
children: [],
8697
}
8798
: {
8899
type: "file",
89100
name: entry.name,
90-
path: pathFromName,
101+
path: formattedPath,
91102
};
92103
}
93104

@@ -107,7 +118,6 @@ const NodeFileSystemBridge = defineFileSystemBridge({
107118
for (const entry of allEntries) {
108119
const entryPath = entry.parentPath || entry.path;
109120
const relativeToTarget = nodePath.relative(targetPath, entryPath);
110-
const fsEntry = createFSEntry(entry);
111121

112122
const entryRelativePath = relativeToTarget
113123
? nodePath.join(relativeToTarget, entry.name)
@@ -116,9 +126,10 @@ const NodeFileSystemBridge = defineFileSystemBridge({
116126
// Normalize path separators to forward slashes for cross-platform consistency
117127
const normalizedPath = normalizePathSeparators(entryRelativePath);
118128

119-
// Update the path to be the full relative path
120-
fsEntry.path = normalizedPath;
129+
// Create FSEntry with properly formatted path (leading /, trailing / for dirs)
130+
const fsEntry = createFSEntry(entry, normalizedPath);
121131

132+
// Use normalized path (without leading/trailing slashes) as map key for parent lookup
122133
entryMap.set(normalizedPath, fsEntry);
123134

124135
if (!relativeToTarget) {

packages/fs-bridge/test/bridges/node/node.test.ts

Lines changed: 18 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -122,9 +122,9 @@ describe("node fs-bridge", () => {
122122
const files = await bridge.listdir("");
123123
expect(files).toHaveLength(3);
124124
expect(files).toEqual([
125-
{ type: "file", name: "file1.txt", path: "file1.txt" },
126-
{ type: "file", name: "file2.txt", path: "file2.txt" },
127-
{ type: "directory", name: "subdir", path: "subdir", children: [] },
125+
{ type: "file", name: "file1.txt", path: "/file1.txt" },
126+
{ type: "file", name: "file2.txt", path: "/file2.txt" },
127+
{ type: "directory", name: "subdir", path: "/subdir/", children: [] },
128128
]);
129129
});
130130

@@ -148,9 +148,9 @@ describe("node fs-bridge", () => {
148148

149149
expect(flattened).toHaveLength(3);
150150
expect(flattened).toEqual([
151-
"dir/deep/file.txt",
152-
"dir/nested.txt",
153-
"root.txt",
151+
"/dir/deep/file.txt",
152+
"/dir/nested.txt",
153+
"/root.txt",
154154
]);
155155
expect(files.map((f) => f.name)).toContain("root.txt");
156156
});
@@ -310,7 +310,7 @@ describe("node fs-bridge", () => {
310310
expect(files[0]).toEqual({
311311
type: "file",
312312
name: "nested.txt",
313-
path: "nested.txt",
313+
path: "/nested.txt",
314314
});
315315
});
316316

@@ -436,7 +436,7 @@ describe("node fs-bridge", () => {
436436
expect(file).toEqual({
437437
type: "file",
438438
name: "file.txt",
439-
path: "file.txt",
439+
path: "/file.txt",
440440
});
441441
// Ensure no extra properties
442442
expect(Object.keys(file!)).toEqual(["type", "name", "path"]);
@@ -457,7 +457,7 @@ describe("node fs-bridge", () => {
457457
expect(dir).toEqual({
458458
type: "directory",
459459
name: "subdir",
460-
path: "subdir",
460+
path: "/subdir/",
461461
children: [], // Non-recursive should have empty children
462462
});
463463
// Ensure children is always an array for directories
@@ -489,7 +489,7 @@ describe("node fs-bridge", () => {
489489
expect(childFile).toEqual({
490490
type: "file",
491491
name: "child.txt",
492-
path: "parent/child.txt",
492+
path: "/parent/child.txt",
493493
});
494494

495495
const nestedDir = parent.children.find((c) => c.name === "nested");
@@ -499,7 +499,7 @@ describe("node fs-bridge", () => {
499499
expect(nestedDir.children[0]).toEqual({
500500
type: "file",
501501
name: "deep.txt",
502-
path: "parent/nested/deep.txt",
502+
path: "/parent/nested/deep.txt",
503503
});
504504
}
505505
}
@@ -605,11 +605,11 @@ describe("node fs-bridge", () => {
605605
const rootFiles = await bridge.listdir(".");
606606
expect(rootFiles).toHaveLength(5);
607607
expect(rootFiles).toEqual(expect.arrayContaining([
608-
{ type: "file", name: "README.md", path: "README.md" },
609-
{ type: "directory", name: "docs", path: "docs", children: [] },
610-
{ type: "file", name: "package.json", path: "package.json" },
611-
{ type: "directory", name: "src", path: "src", children: [] },
612-
{ type: "directory", name: "tests", path: "tests", children: [] },
608+
{ type: "file", name: "README.md", path: "/README.md" },
609+
{ type: "directory", name: "docs", path: "/docs/", children: [] },
610+
{ type: "file", name: "package.json", path: "/package.json" },
611+
{ type: "directory", name: "src", path: "/src/", children: [] },
612+
{ type: "directory", name: "tests", path: "/tests/", children: [] },
613613
]));
614614

615615
// verify file contents
@@ -649,8 +649,8 @@ describe("node fs-bridge", () => {
649649

650650
const flattenedPosts = flattenFilePaths(posts);
651651
expect(flattenedPosts).toHaveLength(2);
652-
expect(flattenedPosts).toContain("2024/first-post.md");
653-
expect(flattenedPosts).toContain("2024/second-post.md");
652+
expect(flattenedPosts).toContain("/2024/first-post.md");
653+
expect(flattenedPosts).toContain("/2024/second-post.md");
654654

655655
// move draft to published
656656
const draftContent = await bridge.read("drafts/upcoming.md");

0 commit comments

Comments
 (0)