From e6c5a4a4bb962c8276887c26e889fe97a8668f44 Mon Sep 17 00:00:00 2001 From: Julius Marminge Date: Fri, 27 Feb 2026 15:11:33 -0800 Subject: [PATCH] Handle favicon links when href precedes rel - Update favicon regex parsing to support rel/href in either order - Add route tests for HTML link tags and object-style metadata with href-first ordering --- apps/server/src/projectFaviconRoute.test.ts | 37 +++++++++++++++++++++ apps/server/src/projectFaviconRoute.ts | 8 +++-- 2 files changed, 42 insertions(+), 3 deletions(-) diff --git a/apps/server/src/projectFaviconRoute.test.ts b/apps/server/src/projectFaviconRoute.test.ts index b0f0273cfbaf9e8d681b9ae9e955bd85df095753..e8b826d7be74c5d81d3b2b73d29a5d85db51bb0f 100644 --- a/apps/server/src/projectFaviconRoute.test.ts +++ b/apps/server/src/projectFaviconRoute.test.ts @@ -115,4 +115,41 @@ describe("tryHandleProjectFaviconRequest", () => { expect(response.body).toBe("brand"); }); }); + + it("resolves icon link when href appears before rel in HTML", async () => { + const projectDir = makeTempDir("t3code-favicon-route-html-order-"); + const iconPath = path.join(projectDir, "public", "brand", "logo.svg"); + fs.mkdirSync(path.dirname(iconPath), { recursive: true }); + fs.writeFileSync(path.join(projectDir, "index.html"), ''); + fs.writeFileSync(iconPath, "brand-html-order", "utf8"); + + await withRouteServer(async (baseUrl) => { + const pathname = `/api/project-favicon?cwd=${encodeURIComponent(projectDir)}`; + const response = await request(baseUrl, pathname); + expect(response.statusCode).toBe(200); + expect(response.contentType).toContain("image/svg+xml"); + expect(response.body).toBe("brand-html-order"); + }); + }); + + it("resolves object-style icon metadata when href appears before rel", async () => { + const projectDir = makeTempDir("t3code-favicon-route-obj-order-"); + const iconPath = path.join(projectDir, "public", "brand", "obj.svg"); + fs.mkdirSync(path.dirname(iconPath), { recursive: true }); + fs.mkdirSync(path.join(projectDir, "src"), { recursive: true }); + fs.writeFileSync( + path.join(projectDir, "src", "root.tsx"), + 'const links = [{ href: "/brand/obj.svg", rel: "icon" }];', + "utf8", + ); + fs.writeFileSync(iconPath, "brand-obj-order", "utf8"); + + await withRouteServer(async (baseUrl) => { + const pathname = `/api/project-favicon?cwd=${encodeURIComponent(projectDir)}`; + const response = await request(baseUrl, pathname); + expect(response.statusCode).toBe(200); + expect(response.contentType).toContain("image/svg+xml"); + expect(response.body).toBe("brand-obj-order"); + }); + }); }); diff --git a/apps/server/src/projectFaviconRoute.ts b/apps/server/src/projectFaviconRoute.ts index 5b224ca56f46cb679aebc02489f7d16bbacbf8cc..4d66f0e86f3156ef6c7f194969acbab0b0ac2ea5 100644 --- a/apps/server/src/projectFaviconRoute.ts +++ b/apps/server/src/projectFaviconRoute.ts @@ -44,9 +44,11 @@ const ICON_SOURCE_FILES = [ "src/index.html", ]; -// Matches or { rel: "icon", href: "..." } patterns. -const LINK_ICON_HTML_RE = /]*rel=["'](?:icon|shortcut icon)["'][^>]*href=["']([^"'?]+)/i; -const LINK_ICON_OBJ_RE = /rel:\s*["'](?:icon|shortcut icon)["'][^}]*href:\s*["']([^"'?]+)/i; +// Matches tags or object-like icon metadata where rel/href can appear in any order. +const LINK_ICON_HTML_RE = + /]*\brel=["'](?:icon|shortcut icon)["'])(?=[^>]*\bhref=["']([^"'?]+))[^>]*>/i; +const LINK_ICON_OBJ_RE = + /(?=[^}]*\brel\s*:\s*["'](?:icon|shortcut icon)["'])(?=[^}]*\bhref\s*:\s*["']([^"'?]+))[^}]*/i; function extractIconHref(source: string): string | null { const htmlMatch = source.match(LINK_ICON_HTML_RE); -- 2.54.0