fix(core): strip trailing slash before .md check in toMarkdownPath#79
Conversation
The idempotency guard ran before trailing slashes were stripped, so a markdown path with a trailing slash (e.g. /blog/post.md/) was given a doubled extension (/blog/post.md.md) and would 404. Strip trailing slashes first so an already-.md path maps back to itself.
|
@abhay-codes07 is attempting to deploy a commit to the Dodo Payments Team on Vercel. A member of the Team first needs to authorize it. |
There was a problem hiding this comment.
Pull request overview
Fixes an edge case in @dualmark/core markdown-twin path mapping where a .md pathname with a trailing slash (e.g. /blog/post.md/) would incorrectly become /blog/post.md.md, causing 404s. The update normalizes trailing slashes before performing the .md idempotency check, and adds a regression test to lock in the behavior.
Changes:
- Normalize trailing slashes before checking for an existing
.mdextension intoMarkdownPath(which also fixestoMarkdownUrl). - Add regression tests ensuring
.md/inputs do not get a doubled extension. - Add a patch changeset for
@dualmark/core.
Reviewed changes
Copilot reviewed 3 out of 3 changed files in this pull request and generated 1 comment.
| File | Description |
|---|---|
| packages/core/src/paths.ts | Reorders normalization to strip trailing slashes before .md idempotency detection to avoid .md.md outputs. |
| packages/core/test/paths.test.ts | Adds regression coverage for .md paths that include a trailing slash. |
| .changeset/tomarkdownpath-trailing-slash.md | Records a patch changeset describing the bug fix and its impact. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| // 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"). |
There was a problem hiding this comment.
Review: fix(core): strip trailing slash before .md check in toMarkdownPath
Verdict: LGTM — approving.
Reviewed end-to-end and exercised the change locally.
Bug reproduction (confirmed on main)
On main, toMarkdownPath ran the .md idempotency guard before trimming trailing slashes:
if (pathname.endsWith(".md")) return pathname; // "/blog/post.md/" doesn't end with ".md"
const trimmed = pathname.replace(/\/+$/, ""); // -> "/blog/post.md"
return trimmed + ".md"; // -> "/blog/post.md.md" ✗So toMarkdownPath("/blog/post.md/") returned /blog/post.md.md (and toMarkdownUrl produced the same doubled path), which 404s. Verified this reproduces on main.
Fix verification
Checked out the branch and ran the edge cases directly:
| Input | Output | ✓ |
|---|---|---|
/blog/post.md/ |
/blog/post.md |
✓ |
/a.md/ |
/a.md |
✓ |
/blog/post/ |
/blog/post.md |
✓ |
/ and "" |
/index.md |
✓ |
/blog/x.md |
/blog/x.md (idempotent) |
✓ |
/blog// |
/blog.md |
✓ |
toMarkdownUrl("https://x.com/blog/post.md/") |
https://x.com/blog/post.md |
✓ |
Ordering is now correct: trim trailing slashes → empty check → .md idempotency check → append. All other cases are unchanged, and this aligns with content-negotiation spec §6 (trailing slash stripped, .md twins are equivalent representations).
Tests
- Added
paths.test.tscases for.md+ trailing slash cover the exact regression. - Full
@dualmark/coresuite passes locally: 181/181.
Clean, minimal, well-commented, changeset included. No concerns.
thepushkaraj
left a comment
There was a problem hiding this comment.
Thanks for the PR @abhay-codes07!
Summary
toMarkdownPathran its.mdidempotency check before stripping trailing slashes, so a markdown path with a trailing slash (e.g./blog/post.md/) came out as/blog/post.md.mdand would 404. This strips trailing slashes first.Closes #78.
Changes
packages/core/src/paths.ts: strip trailing slashes before the.mdcheck, so an already-.mdpath maps back to itself with or without a trailing slash. This also fixestoMarkdownUrl, which delegates totoMarkdownPath./a.md/and/blog/post.md/.Other cases (root to
/index.md, trailing-slash stripping, deep nesting, query/hash preservation) are unchanged.Type
AEO_SPEC_VERSION)Verification
bun run buildpassesbun run testpasses (core: 181 tests)bun run typecheckpassesdualmark verify(no examples touched)/spec/: ran sync-spec (no spec files touched)Changeset
@dualmark/core)cc @thepushkaraj @aagarwal1012.