diff --git a/.changeset/tomarkdownpath-trailing-slash.md b/.changeset/tomarkdownpath-trailing-slash.md new file mode 100644 index 0000000..513f893 --- /dev/null +++ b/.changeset/tomarkdownpath-trailing-slash.md @@ -0,0 +1,7 @@ +--- +"@dualmark/core": patch +--- + +Fix `toMarkdownPath` doubling the extension on a `.md` path with a trailing slash. + +The `.md` idempotency check ran before trailing slashes were stripped, so `toMarkdownPath("/blog/post.md/")` returned `/blog/post.md.md` (and `toMarkdownUrl` produced the same doubled path), which would 404. Trailing slashes are now stripped first, so a markdown path with a trailing slash maps back to itself. All other cases (root to `/index.md`, trailing-slash stripping, deep nesting) are unchanged. diff --git a/packages/core/src/paths.ts b/packages/core/src/paths.ts index ac4c831..4639cd5 100644 --- a/packages/core/src/paths.ts +++ b/packages/core/src/paths.ts @@ -18,9 +18,12 @@ * "/blog/x.md" → "/blog/x.md" (idempotent) */ export function toMarkdownPath(pathname: string): string { - if (pathname.endsWith(".md")) return pathname; + // Strip trailing slashes before the `.md` check, otherwise a markdown path + // with a trailing slash (e.g. "/blog/post.md/") defeats the idempotency + // guard and gets a doubled extension ("/blog/post.md.md"). const trimmed = pathname.replace(/\/+$/, ""); if (trimmed === "") return "/index.md"; + if (trimmed.endsWith(".md")) return trimmed; return trimmed + ".md"; } diff --git a/packages/core/test/paths.test.ts b/packages/core/test/paths.test.ts index 7cef20c..366db51 100644 --- a/packages/core/test/paths.test.ts +++ b/packages/core/test/paths.test.ts @@ -24,6 +24,11 @@ describe("toMarkdownPath", () => { expect(toMarkdownPath("/index.md")).toBe("/index.md"); }); + it("does not double the extension for a .md path with a trailing slash", () => { + expect(toMarkdownPath("/a.md/")).toBe("/a.md"); + expect(toMarkdownPath("/blog/post.md/")).toBe("/blog/post.md"); + }); + it("handles deep nesting", () => { expect(toMarkdownPath("/a/b/c/d/e")).toBe("/a/b/c/d/e.md"); });