From 6f86f21593ea4a4d28c34ce015d1021e19ea0c17 Mon Sep 17 00:00:00 2001 From: scanner <280983584+hivecommons-hive[bot]@users.noreply.github.com> Date: Sun, 18 Oct 2026 00:45:54 -0400 Subject: [PATCH 1/2] fix: resolve ../ and strip titles in rewriteRelativeImagePaths Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Signed-off-by: scanner <280983584+hivecommons-hive[bot]@users.noreply.github.com> --- changelog.d/fixed-362-rewrite-image-paths.md | 1 + src/__tests__/rewriteImagePaths.test.ts | 30 ++++++++++++++++++++ src/lib/rewriteImagePaths.ts | 25 ++++++++++++---- 3 files changed, 50 insertions(+), 6 deletions(-) create mode 100644 changelog.d/fixed-362-rewrite-image-paths.md diff --git a/changelog.d/fixed-362-rewrite-image-paths.md b/changelog.d/fixed-362-rewrite-image-paths.md new file mode 100644 index 0000000..145d3af --- /dev/null +++ b/changelog.d/fixed-362-rewrite-image-paths.md @@ -0,0 +1 @@ +- Fixed `rewriteRelativeImagePaths` so `../` image references resolve inside docs/content (instead of 403) and titled images (`![alt](img.png "Title")`) no longer 404. diff --git a/src/__tests__/rewriteImagePaths.test.ts b/src/__tests__/rewriteImagePaths.test.ts index 6add2b6..a2d4fc2 100644 --- a/src/__tests__/rewriteImagePaths.test.ts +++ b/src/__tests__/rewriteImagePaths.test.ts @@ -73,4 +73,34 @@ describe("rewriteRelativeImagePaths", () => { const twice = rewriteRelativeImagePaths(once, "console"); expect(twice).toBe(once); }); + + it("resolves ../ against baseDir", () => { + expect( + rewriteRelativeImagePaths("![logo](../images/logo.png)", "hive/guides") + ).toBe("![logo](/docs-images/hive/images/logo.png)"); + }); + + it("leaves ../ that escapes docs/content unchanged", () => { + const md = "![logo](../../../etc/logo.png)"; + expect(rewriteRelativeImagePaths(md, "hive/guides")).toBe(md); + expect(rewriteRelativeImagePaths("![x](../x.png)", "")).toBe("![x](../x.png)"); + }); + + it("preserves a double-quoted title", () => { + expect( + rewriteRelativeImagePaths('![logo](images/logo.png "Logo")', "hive") + ).toBe('![logo](/docs-images/hive/images/logo.png "Logo")'); + }); + + it("preserves a single-quoted title", () => { + expect( + rewriteRelativeImagePaths("![logo](./images/logo.png 'Logo')", "hive") + ).toBe("![logo](/docs-images/hive/images/logo.png 'Logo')"); + }); + + it("still rewrites a plain relative path without a title", () => { + expect(rewriteRelativeImagePaths("![a](images/a.png)", "")).toBe( + "![a](/docs-images/images/a.png)" + ); + }); }); diff --git a/src/lib/rewriteImagePaths.ts b/src/lib/rewriteImagePaths.ts index a264793..a42fea2 100644 --- a/src/lib/rewriteImagePaths.ts +++ b/src/lib/rewriteImagePaths.ts @@ -19,7 +19,11 @@ export function rewriteRelativeImagePaths( return content.replace( /!\[([^\]]*)\]\(([^)]+)\)/g, (match, alt: string, src: string) => { - const trimmed = src.trim(); + const raw = src.trim(); + // Split an optional CommonMark title (` "..."` or ` '...'`) off the destination. + const titleMatch = /^(\S+)(\s+(?:"[^"]*"|'[^']*'))$/.exec(raw); + const trimmed = titleMatch ? titleMatch[1] : raw; + const title = titleMatch ? titleMatch[2] : ""; // Leave absolute URLs, absolute paths, data URIs, and anchor-only refs unchanged. if ( trimmed.startsWith("/") || @@ -29,11 +33,20 @@ export function rewriteRelativeImagePaths( ) { return match; } - const normalised = trimmed.replace(/^\.\//, ""); - const apiPath = baseDir - ? `/docs-images/${baseDir}/${normalised}` - : `/docs-images/${normalised}`; - return `![${alt}](${apiPath})`; + // Pure string normalisation (this module may run in the browser). + const segments: string[] = []; + const parts = [...(baseDir ? baseDir.split("/") : []), ...trimmed.split("/")]; + for (const part of parts) { + if (part === "" || part === ".") continue; + if (part === "..") { + // Escapes docs/content: leave the original reference unchanged. + if (segments.length === 0) return match; + segments.pop(); + } else { + segments.push(part); + } + } + return `![${alt}](/docs-images/${segments.join("/")}${title})`; } ); } From af2dc475ea52f0cc6b90b09c2501f2cd3edc4e12 Mon Sep 17 00:00:00 2001 From: scanner <280983584+hivecommons-hive[bot]@users.noreply.github.com> Date: Sun, 18 Oct 2026 00:56:19 -0400 Subject: [PATCH 2/2] style: apply prettier formatting to rewriteImagePaths Signed-off-by: scanner <280983584+hivecommons-hive[bot]@users.noreply.github.com> --- src/__tests__/rewriteImagePaths.test.ts | 4 +++- src/lib/rewriteImagePaths.ts | 5 ++++- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/src/__tests__/rewriteImagePaths.test.ts b/src/__tests__/rewriteImagePaths.test.ts index a2d4fc2..7927b29 100644 --- a/src/__tests__/rewriteImagePaths.test.ts +++ b/src/__tests__/rewriteImagePaths.test.ts @@ -83,7 +83,9 @@ describe("rewriteRelativeImagePaths", () => { it("leaves ../ that escapes docs/content unchanged", () => { const md = "![logo](../../../etc/logo.png)"; expect(rewriteRelativeImagePaths(md, "hive/guides")).toBe(md); - expect(rewriteRelativeImagePaths("![x](../x.png)", "")).toBe("![x](../x.png)"); + expect(rewriteRelativeImagePaths("![x](../x.png)", "")).toBe( + "![x](../x.png)" + ); }); it("preserves a double-quoted title", () => { diff --git a/src/lib/rewriteImagePaths.ts b/src/lib/rewriteImagePaths.ts index a42fea2..17d211d 100644 --- a/src/lib/rewriteImagePaths.ts +++ b/src/lib/rewriteImagePaths.ts @@ -35,7 +35,10 @@ export function rewriteRelativeImagePaths( } // Pure string normalisation (this module may run in the browser). const segments: string[] = []; - const parts = [...(baseDir ? baseDir.split("/") : []), ...trimmed.split("/")]; + const parts = [ + ...(baseDir ? baseDir.split("/") : []), + ...trimmed.split("/"), + ]; for (const part of parts) { if (part === "" || part === ".") continue; if (part === "..") {