From d2522a7e6baa657c4ca9acbb6bbfcf01f21e5b23 Mon Sep 17 00:00:00 2001 From: Tushar <80577646+TusharThakur04@users.noreply.github.com> Date: Tue, 14 Apr 2026 04:51:15 +0530 Subject: [PATCH 1/2] fix(metadata): strip directory prefix when converting nested .md link to .html --- .../metadata/utils/__tests__/parse.test.mjs | 28 +++++++++++++++++++ src/generators/metadata/utils/visitors.mjs | 3 +- 2 files changed, 30 insertions(+), 1 deletion(-) diff --git a/src/generators/metadata/utils/__tests__/parse.test.mjs b/src/generators/metadata/utils/__tests__/parse.test.mjs index b7b9d2f6..51b0b313 100644 --- a/src/generators/metadata/utils/__tests__/parse.test.mjs +++ b/src/generators/metadata/utils/__tests__/parse.test.mjs @@ -220,6 +220,34 @@ describe('parseApiDoc', () => { assert.strictEqual(findLink(entry)?.url, 'events.html#some-section'); }); + + it('strips subdirectory prefix from nested .md links', () => { + const tree = u('root', [ + h('fs'), + u('paragraph', [ + u('link', { url: 'namespaces/comparators.md' }, [ + u('text', 'comparators'), + ]), + ]), + ]); + const [entry] = parseApiDoc({ path, tree }, typeMap); + + assert.strictEqual(findLink(entry)?.url, 'comparators.html'); + }); + + it('strips subdirectory prefix and preserves hash fragments', () => { + const tree = u('root', [ + h('fs'), + u('paragraph', [ + u('link', { url: 'namespaces/comparators.md#some-section' }, [ + u('text', 'comparators'), + ]), + ]), + ]); + const [entry] = parseApiDoc({ path, tree }, typeMap); + + assert.strictEqual(findLink(entry)?.url, 'comparators.html#some-section'); + }); }); describe('document without headings', () => { diff --git a/src/generators/metadata/utils/visitors.mjs b/src/generators/metadata/utils/visitors.mjs index f260079d..9240e37b 100644 --- a/src/generators/metadata/utils/visitors.mjs +++ b/src/generators/metadata/utils/visitors.mjs @@ -1,4 +1,5 @@ 'use strict'; +import { basename } from 'node:path/posix'; import { SKIP } from 'unist-util-visit'; @@ -18,7 +19,7 @@ import { transformNodesToString } from '../../../utils/unist.mjs'; export const visitMarkdownLink = node => { node.url = node.url.replace( QUERIES.markdownUrl, - (_, filename, hash = '') => `${filename}.html${hash}` + (_, filename, hash = '') => `${basename(filename)}.html${hash}` ); return [SKIP]; From f2f1fc1255845f0f6db63530dd0141db880f1158 Mon Sep 17 00:00:00 2001 From: Tushar <80577646+TusharThakur04@users.noreply.github.com> Date: Tue, 28 Apr 2026 03:55:12 +0530 Subject: [PATCH 2/2] fix: add guard for full .md URLs --- .../metadata/utils/__tests__/parse.test.mjs | 23 +++++++++++++++++++ src/generators/metadata/utils/visitors.mjs | 4 ++++ 2 files changed, 27 insertions(+) diff --git a/src/generators/metadata/utils/__tests__/parse.test.mjs b/src/generators/metadata/utils/__tests__/parse.test.mjs index 51b0b313..fe8cf22c 100644 --- a/src/generators/metadata/utils/__tests__/parse.test.mjs +++ b/src/generators/metadata/utils/__tests__/parse.test.mjs @@ -248,6 +248,29 @@ describe('parseApiDoc', () => { assert.strictEqual(findLink(entry)?.url, 'comparators.html#some-section'); }); + + it('ignores .md full URLs with any protocol', () => { + const protocolLinks = [ + 'https://github.com/example/config.md', + 'http://internal-server.com/docs.md', + 'file:///C:/Shared/docs/readme.md', + ]; + + for (const url of protocolLinks) { + const tree = u('root', [ + h('fs'), + u('paragraph', [u('link', { url }, [u('text', 'external link')])]), + ]); + const [entry] = parseApiDoc({ path, tree }, typeMap); + + // Assert that the URL comes out exactly as it went in + assert.strictEqual( + findLink(entry)?.url, + url, + `Failed to ignore protocol: ${url}` + ); + } + }); }); describe('document without headings', () => { diff --git a/src/generators/metadata/utils/visitors.mjs b/src/generators/metadata/utils/visitors.mjs index 9240e37b..a18e1373 100644 --- a/src/generators/metadata/utils/visitors.mjs +++ b/src/generators/metadata/utils/visitors.mjs @@ -17,6 +17,10 @@ import { transformNodesToString } from '../../../utils/unist.mjs'; * @param {import('@types/mdast').Link} node A Markdown link node */ export const visitMarkdownLink = node => { + // REJECT PROTOCOLS: Catches http:, https:, mailto:, ftp:, file:, vscode:, etc. + if (/^[a-z]+:/i.test(node.url)) { + return [SKIP]; + } node.url = node.url.replace( QUERIES.markdownUrl, (_, filename, hash = '') => `${basename(filename)}.html${hash}`