From 5f8f193982d2d4e061982aeb6865a81fe6d24427 Mon Sep 17 00:00:00 2001 From: Larkin Young Date: Mon, 16 Feb 2026 22:02:44 +0000 Subject: [PATCH] Fix regression: scope pre-normalization global linkMappings to bare paths only The v0.14.2 fix applied global linkMappings to raw paths for all links, but generic global mappings like .md-stripping would over-match relative links (./foo.md, ../bar.md), causing early return and bypassing sourceToTargetMap resolution. Now the pre-normalization pass only runs for bare paths (no ./, ../, /, or protocol prefix). Adds regression tests for ./ and ../ links with .md-stripping global mappings to prevent this class of bug. --- .../src/github.link-transform.spec.ts | 65 +++++++++++++++++++ .../src/github.link-transform.ts | 10 ++- 2 files changed, 74 insertions(+), 1 deletion(-) diff --git a/packages/astro-github-loader/src/github.link-transform.spec.ts b/packages/astro-github-loader/src/github.link-transform.spec.ts index f068a02..4d33b09 100644 --- a/packages/astro-github-loader/src/github.link-transform.spec.ts +++ b/packages/astro-github-loader/src/github.link-transform.spec.ts @@ -146,6 +146,71 @@ describe("globalLinkTransform", () => { ); }); + it("should not early-return relative ./ links when .md-stripping global mappings exist", () => { + const files: ImportedFile[] = [ + createImportedFile( + "docs/guide.md", + "src/content/docs/guide.md", + "[Subscriber](./subscriber.md)", + ), + createImportedFile( + "docs/subscriber.md", + "src/content/docs/subscriber.md", + "# Subscriber", + ), + ]; + + const result = globalLinkTransform(files, { + stripPrefixes: ["src/content/docs"], + linkMappings: [ + { + pattern: /\.md(#|$)/, + replacement: "$1", + global: true, + }, + { + pattern: /\/index(\.md)?$/, + replacement: "/", + global: true, + }, + ], + logger, + }); + + // Should resolve via sourceToTargetMap, not early-return from .md stripping + expect(result[0].content).toBe("[Subscriber](/subscriber/)"); + }); + + it("should not early-return relative ../ links when global mappings exist", () => { + const files: ImportedFile[] = [ + createImportedFile( + "docs/guides/intro.md", + "src/content/docs/guides/intro.md", + "[Overview](../overview.md)", + ), + createImportedFile( + "docs/overview.md", + "src/content/docs/overview.md", + "# Overview", + ), + ]; + + const result = globalLinkTransform(files, { + stripPrefixes: ["src/content/docs"], + linkMappings: [ + { + pattern: /\.md(#|$)/, + replacement: "$1", + global: true, + }, + ], + logger, + }); + + // Should resolve via normalization + sourceToTargetMap + expect(result[0].content).toBe("[Overview](/overview/)"); + }); + it("should preserve anchors in transformed links", () => { const files: ImportedFile[] = [ createImportedFile( diff --git a/packages/astro-github-loader/src/github.link-transform.ts b/packages/astro-github-loader/src/github.link-transform.ts index f39f0b6..a8afd31 100644 --- a/packages/astro-github-loader/src/github.link-transform.ts +++ b/packages/astro-github-loader/src/github.link-transform.ts @@ -318,7 +318,15 @@ function transformLink( // Bare-path links (e.g., "docs/markdown/autoapi/foo/") are repo-root-relative // but normalizePath() treats them as file-relative, mangling the path. // Applying global mappings first lets patterns match the link as written. - if (context.global.linkMappings) { + // Only for bare paths — relative (./, ../) and absolute (/) links must flow + // through normalizePath() first to avoid over-matching by generic global + // mappings like .md-stripping from generateStarlightLinkMappings(). + const isBareBarePath = + !linkPath.startsWith("./") && + !linkPath.startsWith("../") && + !linkPath.startsWith("/") && + !linkPath.includes("://"); + if (isBareBarePath && context.global.linkMappings) { const globalMappings = context.global.linkMappings.filter((m) => m.global); if (globalMappings.length > 0) { const rawMapped = applyLinkMappings(