From be3fd493d6d0821c5d44b32d8dbbc89d585f49b6 Mon Sep 17 00:00:00 2001 From: Your Name Date: Mon, 15 Jun 2026 05:35:21 +0530 Subject: [PATCH 1/5] fix(docs): respect dark theme in badge style dropdown --- docs/app/components/BadgeGeneratorParameters.vue | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/docs/app/components/BadgeGeneratorParameters.vue b/docs/app/components/BadgeGeneratorParameters.vue index bf0a4e5d6e..ba5420cdc8 100644 --- a/docs/app/components/BadgeGeneratorParameters.vue +++ b/docs/app/components/BadgeGeneratorParameters.vue @@ -253,9 +253,11 @@ const copyToClipboard = async () => { > From 8c7bbaee329a2774c0bb655da4260607546b24ea Mon Sep 17 00:00:00 2001 From: Your Name Date: Wed, 17 Jun 2026 17:18:58 +0530 Subject: [PATCH 2/5] fix(readme): handle root-relative markdown file links --- server/utils/readme.ts | 13 +++++++----- test/unit/server/utils/readme.spec.ts | 30 +++++++++++++++++++++++++++ 2 files changed, 38 insertions(+), 5 deletions(-) diff --git a/server/utils/readme.ts b/server/utils/readme.ts index 6e220f7827..d5f1a8d327 100644 --- a/server/utils/readme.ts +++ b/server/utils/readme.ts @@ -338,8 +338,14 @@ function resolveUrl(url: string, packageName: string, repoInfo?: RepositoryInfo) const normalizedFragment = slugify(decodeHashFragment(fragment)) return toUserContentHash(normalizedFragment || fragment) } - // Absolute paths (e.g. /package/foo from a previous npmjs redirect) are already resolved - if (url.startsWith('/')) return url + // Check if this is a markdown file link + const isMarkdownFile = /\.md$/i.test(url.split('?')[0]?.split('#')[0] ?? '') + + // Root-relative markdown links in READMEs point to files at the repository root. + // Keep non-markdown absolute paths local (e.g. /package/foo from an npmjs redirect). + if (url.startsWith('/')) { + return isMarkdownFile && repoInfo?.blobBaseUrl ? `${repoInfo.blobBaseUrl}${url}` : url + } if (hasProtocol(url, { acceptRelative: true })) { try { const parsed = new URL(url, 'https://example.com') @@ -360,9 +366,6 @@ function resolveUrl(url: string, packageName: string, repoInfo?: RepositoryInfo) // for non-HTTP protocols (javascript:, data:, etc.), don't return, treat as relative } - // Check if this is a markdown file link - const isMarkdownFile = /\.md$/i.test(url.split('?')[0]?.split('#')[0] ?? '') - // Use provider's URL base when repository info is available // This handles assets that exist in the repo but not in the npm tarball if (repoInfo?.rawBaseUrl) { diff --git a/test/unit/server/utils/readme.spec.ts b/test/unit/server/utils/readme.spec.ts index dacc2653b7..4c39104009 100644 --- a/test/unit/server/utils/readme.spec.ts +++ b/test/unit/server/utils/readme.spec.ts @@ -249,6 +249,36 @@ describe('Markdown File URL Resolution', () => { 'href="https://github.com/test-owner/test-repo/blob/HEAD/CONTRIBUTING.md"', ) }) + + it('resolves root-relative .md links to the repository root blob URL', async () => { + const repoInfo = createRepoInfo({ + directory: 'packages/core', + }) + const markdown = `[Root Contributing](/CONTRIBUTING.md)` + const result = await renderReadmeHtml(markdown, 'test-pkg', repoInfo) + + expect(result.html).toContain( + 'href="https://github.com/test-owner/test-repo/blob/HEAD/CONTRIBUTING.md"', + ) + }) + + it('resolves root-relative .md links in raw HTML anchors', async () => { + const repoInfo = createRepoInfo() + const markdown = `Contributing` + const result = await renderReadmeHtml(markdown, 'test-pkg', repoInfo) + + expect(result.html).toContain( + 'href="https://github.com/test-owner/test-repo/blob/HEAD/CONTRIBUTING.md"', + ) + }) + + it('keeps root-relative non-markdown links local', async () => { + const repoInfo = createRepoInfo() + const markdown = `[Package](/package/test-pkg)` + const result = await renderReadmeHtml(markdown, 'test-pkg', repoInfo) + + expect(result.html).toContain('href="/package/test-pkg"') + }) }) describe('without repository info', () => { From 5e7897bd3a73169483f4fb3b9f2fbbd63595bcc3 Mon Sep 17 00:00:00 2001 From: Your Name Date: Thu, 18 Jun 2026 02:30:22 +0530 Subject: [PATCH 3/5] refactor: clean up root-relative link handling --- server/utils/readme.ts | 22 ++++++++++++++--- test/unit/server/utils/readme.spec.ts | 34 ++++++++++++++++++++++++++- 2 files changed, 52 insertions(+), 4 deletions(-) diff --git a/server/utils/readme.ts b/server/utils/readme.ts index d5f1a8d327..ad36a073ce 100644 --- a/server/utils/readme.ts +++ b/server/utils/readme.ts @@ -315,6 +315,19 @@ export const isNpmJsUrlThatCanBeRedirected = (url: URL) => { return true } +function isLocalNpmxPath(url: string): boolean { + return ( + url === '/package' || + url.startsWith('/package/') || + url === '/org' || + url.startsWith('/org/') || + url === '/search' || + url.startsWith('/search?') || + url.startsWith('/search#') || + url.startsWith('/~') + ) +} + /** * Resolve a relative URL to an absolute URL. * If repository info is available, resolve to provider's raw file URLs. @@ -341,10 +354,13 @@ function resolveUrl(url: string, packageName: string, repoInfo?: RepositoryInfo) // Check if this is a markdown file link const isMarkdownFile = /\.md$/i.test(url.split('?')[0]?.split('#')[0] ?? '') - // Root-relative markdown links in READMEs point to files at the repository root. - // Keep non-markdown absolute paths local (e.g. /package/foo from an npmjs redirect). if (url.startsWith('/')) { - return isMarkdownFile && repoInfo?.blobBaseUrl ? `${repoInfo.blobBaseUrl}${url}` : url + if (isLocalNpmxPath(url) || !repoInfo?.rawBaseUrl) { + return url + } + + const baseUrl = isMarkdownFile ? repoInfo.blobBaseUrl : repoInfo.rawBaseUrl + return `${baseUrl}${url}` } if (hasProtocol(url, { acceptRelative: true })) { try { diff --git a/test/unit/server/utils/readme.spec.ts b/test/unit/server/utils/readme.spec.ts index 4c39104009..629b47f176 100644 --- a/test/unit/server/utils/readme.spec.ts +++ b/test/unit/server/utils/readme.spec.ts @@ -272,13 +272,45 @@ describe('Markdown File URL Resolution', () => { ) }) - it('keeps root-relative non-markdown links local', async () => { + it('resolves root-relative non-.md links to the repository root raw URL', async () => { + const repoInfo = createRepoInfo({ + directory: 'packages/core', + }) + const markdown = `[Logo](/assets/logo.png)` + const result = await renderReadmeHtml(markdown, 'test-pkg', repoInfo) + + expect(result.html).toContain( + 'href="https://raw.githubusercontent.com/test-owner/test-repo/HEAD/assets/logo.png"', + ) + }) + + it('keeps local npmx paths unchanged', async () => { const repoInfo = createRepoInfo() const markdown = `[Package](/package/test-pkg)` const result = await renderReadmeHtml(markdown, 'test-pkg', repoInfo) expect(result.html).toContain('href="/package/test-pkg"') }) + + it('keeps npmjs redirects local when repository info is available', async () => { + const repoInfo = createRepoInfo() + const markdown = `[Package](https://www.npmjs.com/package/test-pkg)` + const result = await renderReadmeHtml(markdown, 'test-pkg', repoInfo) + + expect(result.html).toContain('href="/package/test-pkg"') + }) + + it('keeps npmjs route roots local when repository info is available', async () => { + const repoInfo = createRepoInfo() + const markdown = [ + `[Packages](https://www.npmjs.com/package)`, + `[Organizations](https://www.npmjs.com/org)`, + ].join('\n') + const result = await renderReadmeHtml(markdown, 'test-pkg', repoInfo) + + expect(result.html).toContain('href="/package"') + expect(result.html).toContain('href="/org"') + }) }) describe('without repository info', () => { From a0b738c38f4b4dbd781782a1ed1ce556b90d3147 Mon Sep 17 00:00:00 2001 From: Your Name Date: Thu, 18 Jun 2026 02:58:50 +0530 Subject: [PATCH 4/5] fix: preserve npmjs rewritten routes without path matching --- server/utils/readme.ts | 21 ++++++++------------- test/unit/server/utils/readme.spec.ts | 6 ++++-- 2 files changed, 12 insertions(+), 15 deletions(-) diff --git a/server/utils/readme.ts b/server/utils/readme.ts index ad36a073ce..723418de3f 100644 --- a/server/utils/readme.ts +++ b/server/utils/readme.ts @@ -269,6 +269,7 @@ const reservedPathsNpmJs = [ const npmJsHosts = new Set(['www.npmjs.com', 'npmjs.com', 'www.npmjs.org', 'npmjs.org']) const USER_CONTENT_PREFIX = 'user-content-' +const LOCAL_NPMX_REDIRECT_PREFIX = '$npmx-local:' function withUserContentPrefix(value: string): string { return value.startsWith(USER_CONTENT_PREFIX) ? value : `${USER_CONTENT_PREFIX}${value}` @@ -315,17 +316,8 @@ export const isNpmJsUrlThatCanBeRedirected = (url: URL) => { return true } -function isLocalNpmxPath(url: string): boolean { - return ( - url === '/package' || - url.startsWith('/package/') || - url === '/org' || - url.startsWith('/org/') || - url === '/search' || - url.startsWith('/search?') || - url.startsWith('/search#') || - url.startsWith('/~') - ) +function toLocalNpmxRedirect(path: string): string { + return `${LOCAL_NPMX_REDIRECT_PREFIX}${path}` } /** @@ -336,6 +328,9 @@ function isLocalNpmxPath(url: string): boolean { */ function resolveUrl(url: string, packageName: string, repoInfo?: RepositoryInfo): string { if (!url) return url + if (url.startsWith(LOCAL_NPMX_REDIRECT_PREFIX)) { + return url.slice(LOCAL_NPMX_REDIRECT_PREFIX.length) + } if (url.startsWith('#')) { // Prefix anchor links to match heading IDs (avoids collision with page IDs) // Normalize markdown-style heading fragments to the same slug format used @@ -355,7 +350,7 @@ function resolveUrl(url: string, packageName: string, repoInfo?: RepositoryInfo) const isMarkdownFile = /\.md$/i.test(url.split('?')[0]?.split('#')[0] ?? '') if (url.startsWith('/')) { - if (isLocalNpmxPath(url) || !repoInfo?.rawBaseUrl) { + if (!repoInfo?.rawBaseUrl) { return url } @@ -368,7 +363,7 @@ function resolveUrl(url: string, packageName: string, repoInfo?: RepositoryInfo) if (parsed.protocol === 'http:' || parsed.protocol === 'https:') { // Redirect npmjs urls to ourself if (isNpmJsUrlThatCanBeRedirected(parsed)) { - return parsed.pathname + parsed.search + parsed.hash + return toLocalNpmxRedirect(parsed.pathname + parsed.search + parsed.hash) } return url } diff --git a/test/unit/server/utils/readme.spec.ts b/test/unit/server/utils/readme.spec.ts index 629b47f176..f2f567945d 100644 --- a/test/unit/server/utils/readme.spec.ts +++ b/test/unit/server/utils/readme.spec.ts @@ -284,12 +284,14 @@ describe('Markdown File URL Resolution', () => { ) }) - it('keeps local npmx paths unchanged', async () => { + it('resolves authored root-relative npmx-like paths to the repository root raw URL', async () => { const repoInfo = createRepoInfo() const markdown = `[Package](/package/test-pkg)` const result = await renderReadmeHtml(markdown, 'test-pkg', repoInfo) - expect(result.html).toContain('href="/package/test-pkg"') + expect(result.html).toContain( + 'href="https://raw.githubusercontent.com/test-owner/test-repo/HEAD/package/test-pkg"', + ) }) it('keeps npmjs redirects local when repository info is available', async () => { From 03ae3040b77a69a4a9d5faf9e617a1e4d0a4dae3 Mon Sep 17 00:00:00 2001 From: Your Name Date: Thu, 18 Jun 2026 03:07:36 +0530 Subject: [PATCH 5/5] fix: apply coderabbit suggestion --- server/utils/readme.ts | 2 +- test/unit/server/utils/readme.spec.ts | 8 ++++++++ 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/server/utils/readme.ts b/server/utils/readme.ts index 723418de3f..40822cb7b8 100644 --- a/server/utils/readme.ts +++ b/server/utils/readme.ts @@ -349,7 +349,7 @@ function resolveUrl(url: string, packageName: string, repoInfo?: RepositoryInfo) // Check if this is a markdown file link const isMarkdownFile = /\.md$/i.test(url.split('?')[0]?.split('#')[0] ?? '') - if (url.startsWith('/')) { + if (url.startsWith('/') && !url.startsWith('//')) { if (!repoInfo?.rawBaseUrl) { return url } diff --git a/test/unit/server/utils/readme.spec.ts b/test/unit/server/utils/readme.spec.ts index f2f567945d..5b8e01d4c4 100644 --- a/test/unit/server/utils/readme.spec.ts +++ b/test/unit/server/utils/readme.spec.ts @@ -348,6 +348,14 @@ describe('Markdown File URL Resolution', () => { expect(result.html).toContain('href="https://docs.example.com/"') }) + + it('leaves protocol-relative URLs unchanged with repository info', async () => { + const repoInfo = createRepoInfo() + const markdown = `[CDN](//cdn.example.com/file.css)` + const result = await renderReadmeHtml(markdown, 'test-pkg', repoInfo) + + expect(result.html).toContain('href="//cdn.example.com/file.css"') + }) }) describe('anchor links', () => {