From 1ef1a80e3a7d2f25afe3f417d741201eabf6216f Mon Sep 17 00:00:00 2001 From: Kinfe123 Date: Wed, 20 May 2026 01:00:14 +0300 Subject: [PATCH 1/3] chore: release v0.1.110 --- packages/astro-theme/package.json | 2 +- packages/astro/package.json | 2 +- packages/docs/package.json | 2 +- packages/fumadocs/package.json | 2 +- packages/next/package.json | 2 +- packages/nuxt-theme/package.json | 2 +- packages/nuxt/package.json | 2 +- packages/svelte-theme/package.json | 2 +- packages/svelte/package.json | 2 +- packages/tanstack-start/package.json | 2 +- 10 files changed, 10 insertions(+), 10 deletions(-) diff --git a/packages/astro-theme/package.json b/packages/astro-theme/package.json index ec773a98..a0463a10 100644 --- a/packages/astro-theme/package.json +++ b/packages/astro-theme/package.json @@ -1,6 +1,6 @@ { "name": "@farming-labs/astro-theme", - "version": "0.1.109", + "version": "0.1.110", "description": "Astro UI components for @farming-labs/docs — layout, sidebar, TOC, search, and theme toggle", "keywords": [ "astro", diff --git a/packages/astro/package.json b/packages/astro/package.json index 6f5e72e0..148fe40b 100644 --- a/packages/astro/package.json +++ b/packages/astro/package.json @@ -1,6 +1,6 @@ { "name": "@farming-labs/astro", - "version": "0.1.109", + "version": "0.1.110", "description": "Astro adapter for @farming-labs/docs — content loading and navigation utilities", "keywords": [ "astro", diff --git a/packages/docs/package.json b/packages/docs/package.json index 99f97c7b..308cb775 100644 --- a/packages/docs/package.json +++ b/packages/docs/package.json @@ -1,6 +1,6 @@ { "name": "@farming-labs/docs", - "version": "0.1.109", + "version": "0.1.110", "description": "Modern, flexible MDX-based docs framework — core types, config, and CLI", "keywords": [ "docs", diff --git a/packages/fumadocs/package.json b/packages/fumadocs/package.json index fd734ad1..7d1b8a76 100644 --- a/packages/fumadocs/package.json +++ b/packages/fumadocs/package.json @@ -1,6 +1,6 @@ { "name": "@farming-labs/theme", - "version": "0.1.109", + "version": "0.1.110", "description": "Theme package for @farming-labs/docs — layout, provider, MDX components, and styles", "keywords": [ "docs", diff --git a/packages/next/package.json b/packages/next/package.json index 1ab097d4..503d8469 100644 --- a/packages/next/package.json +++ b/packages/next/package.json @@ -1,6 +1,6 @@ { "name": "@farming-labs/next", - "version": "0.1.109", + "version": "0.1.110", "description": "Next.js adapter for @farming-labs/docs — MDX config wrapper", "keywords": [ "docs", diff --git a/packages/nuxt-theme/package.json b/packages/nuxt-theme/package.json index 7032fa3d..1c43a00d 100644 --- a/packages/nuxt-theme/package.json +++ b/packages/nuxt-theme/package.json @@ -1,6 +1,6 @@ { "name": "@farming-labs/nuxt-theme", - "version": "0.1.109", + "version": "0.1.110", "description": "Nuxt/Vue UI components for @farming-labs/docs — layout, sidebar, TOC, search, and theme toggle", "keywords": [ "docs", diff --git a/packages/nuxt/package.json b/packages/nuxt/package.json index 13fb78c3..fc99356d 100644 --- a/packages/nuxt/package.json +++ b/packages/nuxt/package.json @@ -1,6 +1,6 @@ { "name": "@farming-labs/nuxt", - "version": "0.1.109", + "version": "0.1.110", "description": "Nuxt adapter for @farming-labs/docs — content loading and navigation utilities", "keywords": [ "docs", diff --git a/packages/svelte-theme/package.json b/packages/svelte-theme/package.json index 74dbf82a..eeaa52fd 100644 --- a/packages/svelte-theme/package.json +++ b/packages/svelte-theme/package.json @@ -1,6 +1,6 @@ { "name": "@farming-labs/svelte-theme", - "version": "0.1.109", + "version": "0.1.110", "description": "Svelte UI components for @farming-labs/docs — layout, sidebar, TOC, search, and theme toggle", "keywords": [ "docs", diff --git a/packages/svelte/package.json b/packages/svelte/package.json index 7c9f34d3..9fa4a580 100644 --- a/packages/svelte/package.json +++ b/packages/svelte/package.json @@ -1,6 +1,6 @@ { "name": "@farming-labs/svelte", - "version": "0.1.109", + "version": "0.1.110", "description": "SvelteKit adapter for @farming-labs/docs — content loading and navigation utilities", "keywords": [ "docs", diff --git a/packages/tanstack-start/package.json b/packages/tanstack-start/package.json index 579b6deb..aee67675 100644 --- a/packages/tanstack-start/package.json +++ b/packages/tanstack-start/package.json @@ -1,6 +1,6 @@ { "name": "@farming-labs/tanstack-start", - "version": "0.1.109", + "version": "0.1.110", "description": "TanStack Start adapter for @farming-labs/docs — content loading, search, and AI utilities", "keywords": [ "docs", From 18a05860c924ea22324dbb7c5b9cd0fdda72bc8d Mon Sep 17 00:00:00 2001 From: Kinfe123 Date: Wed, 20 May 2026 01:01:11 +0300 Subject: [PATCH 2/3] chore: release v0.1.111 --- packages/astro-theme/package.json | 2 +- packages/astro/package.json | 2 +- packages/docs/package.json | 2 +- packages/fumadocs/package.json | 2 +- packages/next/package.json | 2 +- packages/nuxt-theme/package.json | 2 +- packages/nuxt/package.json | 2 +- packages/svelte-theme/package.json | 2 +- packages/svelte/package.json | 2 +- packages/tanstack-start/package.json | 2 +- 10 files changed, 10 insertions(+), 10 deletions(-) diff --git a/packages/astro-theme/package.json b/packages/astro-theme/package.json index a0463a10..c5092ee0 100644 --- a/packages/astro-theme/package.json +++ b/packages/astro-theme/package.json @@ -1,6 +1,6 @@ { "name": "@farming-labs/astro-theme", - "version": "0.1.110", + "version": "0.1.111", "description": "Astro UI components for @farming-labs/docs — layout, sidebar, TOC, search, and theme toggle", "keywords": [ "astro", diff --git a/packages/astro/package.json b/packages/astro/package.json index 148fe40b..d9dfe1c0 100644 --- a/packages/astro/package.json +++ b/packages/astro/package.json @@ -1,6 +1,6 @@ { "name": "@farming-labs/astro", - "version": "0.1.110", + "version": "0.1.111", "description": "Astro adapter for @farming-labs/docs — content loading and navigation utilities", "keywords": [ "astro", diff --git a/packages/docs/package.json b/packages/docs/package.json index 308cb775..1eb37d4c 100644 --- a/packages/docs/package.json +++ b/packages/docs/package.json @@ -1,6 +1,6 @@ { "name": "@farming-labs/docs", - "version": "0.1.110", + "version": "0.1.111", "description": "Modern, flexible MDX-based docs framework — core types, config, and CLI", "keywords": [ "docs", diff --git a/packages/fumadocs/package.json b/packages/fumadocs/package.json index 7d1b8a76..5a9a6431 100644 --- a/packages/fumadocs/package.json +++ b/packages/fumadocs/package.json @@ -1,6 +1,6 @@ { "name": "@farming-labs/theme", - "version": "0.1.110", + "version": "0.1.111", "description": "Theme package for @farming-labs/docs — layout, provider, MDX components, and styles", "keywords": [ "docs", diff --git a/packages/next/package.json b/packages/next/package.json index 503d8469..0948bb06 100644 --- a/packages/next/package.json +++ b/packages/next/package.json @@ -1,6 +1,6 @@ { "name": "@farming-labs/next", - "version": "0.1.110", + "version": "0.1.111", "description": "Next.js adapter for @farming-labs/docs — MDX config wrapper", "keywords": [ "docs", diff --git a/packages/nuxt-theme/package.json b/packages/nuxt-theme/package.json index 1c43a00d..97abc20f 100644 --- a/packages/nuxt-theme/package.json +++ b/packages/nuxt-theme/package.json @@ -1,6 +1,6 @@ { "name": "@farming-labs/nuxt-theme", - "version": "0.1.110", + "version": "0.1.111", "description": "Nuxt/Vue UI components for @farming-labs/docs — layout, sidebar, TOC, search, and theme toggle", "keywords": [ "docs", diff --git a/packages/nuxt/package.json b/packages/nuxt/package.json index fc99356d..b1396179 100644 --- a/packages/nuxt/package.json +++ b/packages/nuxt/package.json @@ -1,6 +1,6 @@ { "name": "@farming-labs/nuxt", - "version": "0.1.110", + "version": "0.1.111", "description": "Nuxt adapter for @farming-labs/docs — content loading and navigation utilities", "keywords": [ "docs", diff --git a/packages/svelte-theme/package.json b/packages/svelte-theme/package.json index eeaa52fd..c7db031b 100644 --- a/packages/svelte-theme/package.json +++ b/packages/svelte-theme/package.json @@ -1,6 +1,6 @@ { "name": "@farming-labs/svelte-theme", - "version": "0.1.110", + "version": "0.1.111", "description": "Svelte UI components for @farming-labs/docs — layout, sidebar, TOC, search, and theme toggle", "keywords": [ "docs", diff --git a/packages/svelte/package.json b/packages/svelte/package.json index 9fa4a580..b17aacbb 100644 --- a/packages/svelte/package.json +++ b/packages/svelte/package.json @@ -1,6 +1,6 @@ { "name": "@farming-labs/svelte", - "version": "0.1.110", + "version": "0.1.111", "description": "SvelteKit adapter for @farming-labs/docs — content loading and navigation utilities", "keywords": [ "docs", diff --git a/packages/tanstack-start/package.json b/packages/tanstack-start/package.json index aee67675..1ffc9c79 100644 --- a/packages/tanstack-start/package.json +++ b/packages/tanstack-start/package.json @@ -1,6 +1,6 @@ { "name": "@farming-labs/tanstack-start", - "version": "0.1.110", + "version": "0.1.111", "description": "TanStack Start adapter for @farming-labs/docs — content loading, search, and AI utilities", "keywords": [ "docs", From d4af9b8701d79b86302735d59d3b56362ab67a7f Mon Sep 17 00:00:00 2001 From: Kinfe123 Date: Wed, 20 May 2026 01:40:17 +0300 Subject: [PATCH 3/3] feat: normalized docsPath route --- packages/fumadocs/src/docs-api.test.ts | 72 +++++++++++ packages/fumadocs/src/docs-api.ts | 9 +- packages/fumadocs/src/docs-layout.tsx | 9 +- packages/next/src/config.test.ts | 118 ++++++++++++++++++ packages/next/src/config.ts | 9 +- .../remark-markdown-alternate.test.ts | 33 +++++ .../mdx-plugins/remark-markdown-alternate.ts | 9 +- 7 files changed, 247 insertions(+), 12 deletions(-) diff --git a/packages/fumadocs/src/docs-api.test.ts b/packages/fumadocs/src/docs-api.test.ts index dcff3bc6..6e586481 100644 --- a/packages/fumadocs/src/docs-api.test.ts +++ b/packages/fumadocs/src/docs-api.test.ts @@ -2514,6 +2514,78 @@ The changelog now has its own dedicated route. ); }); + it.each(["", "/", "///"])( + "serves markdown from root-mounted docsPath value %s", + async (docsPath) => { + const rootDir = mkdtempSync(join(tmpdir(), "fumadocs-root-docspath-markdown-")); + tempDirs.push(rootDir); + + mkdirSync(join(rootDir, "app", "docs", "quickstart"), { recursive: true }); + writeFileSync( + join(rootDir, "app", "docs", "quickstart", "page.mdx"), + `--- +title: "Quickstart" +description: "Start here" +--- + +# Quickstart + +Welcome to the docs. +`, + ); + + process.chdir(rootDir); + + const { GET } = createDocsAPI({ + rootDir, + entry: "docs", + docsPath, + }); + + const response = await GET(new Request("http://localhost/quickstart.md")); + expect(response.status).toBe(200); + expect(response.headers.get("link")).toBe('; rel="canonical"'); + expect(await response.text()).toContain("# Quickstart\nURL: /quickstart"); + }, + ); + + it.each(["docs", "/docs", "docs/", "/docs/"])( + "serves markdown from default docsPath value %s", + async (docsPath) => { + const rootDir = mkdtempSync(join(tmpdir(), "fumadocs-default-docspath-markdown-")); + tempDirs.push(rootDir); + + mkdirSync(join(rootDir, "app", "docs", "quickstart"), { recursive: true }); + writeFileSync( + join(rootDir, "app", "docs", "quickstart", "page.mdx"), + `--- +title: "Quickstart" +description: "Start here" +--- + +# Quickstart + +Welcome to the docs. +`, + ); + + process.chdir(rootDir); + + const { GET } = createDocsAPI({ + rootDir, + entry: "docs", + docsPath, + }); + + const response = await GET(new Request("http://localhost/docs/quickstart.md")); + expect(response.status).toBe(200); + expect(response.headers.get("link")).toBe( + '; rel="canonical"', + ); + expect(await response.text()).toContain("# Quickstart\nURL: /docs/quickstart"); + }, + ); + it("skips changelog indexing when reading the changelog directory fails", async () => { const rootDir = mkdtempSync(join(tmpdir(), "fumadocs-changelog-search-read-failure-")); tempDirs.push(rootDir); diff --git a/packages/fumadocs/src/docs-api.ts b/packages/fumadocs/src/docs-api.ts index edcacfe1..bac41826 100644 --- a/packages/fumadocs/src/docs-api.ts +++ b/packages/fumadocs/src/docs-api.ts @@ -1644,10 +1644,13 @@ function resolveMarkdownRequest(entry: string, url: URL, request: Request): Mark function normalizeDocsPublicPath(value: string | undefined, entry: string): string { if (typeof value !== "string") return `/${normalizePathSegment(entry)}`; - const cleaned = value.trim(); - if (cleaned === "" || cleaned === "/") return ""; + const cleaned = value + .trim() + .replace(/^\/+|\/+$/g, "") + .replace(/\/+/g, "/"); + if (cleaned === "") return ""; - return `/${cleaned.replace(/^\/+|\/+$/g, "")}`; + return `/${cleaned}`; } function publicDocsRoute(publicPath: string, slugParts: string[] = []): string { diff --git a/packages/fumadocs/src/docs-layout.tsx b/packages/fumadocs/src/docs-layout.tsx index 7cc012ee..d3237a5a 100644 --- a/packages/fumadocs/src/docs-layout.tsx +++ b/packages/fumadocs/src/docs-layout.tsx @@ -189,10 +189,13 @@ function resolveDocsLocaleContext(config: DocsConfig, locale?: string): DocsLoca function normalizeDocsPublicPath(value: string | undefined, entry: string): string { if (typeof value !== "string") return `/${entry.replace(/^\/+|\/+$/g, "") || "docs"}`; - const cleaned = value.trim(); - if (cleaned === "" || cleaned === "/") return ""; + const cleaned = value + .trim() + .replace(/^\/+|\/+$/g, "") + .replace(/\/+/g, "/"); + if (cleaned === "") return ""; - return `/${cleaned.replace(/^\/+|\/+$/g, "")}`; + return `/${cleaned}`; } function publicDocsRoute(ctx: DocsLocaleContext, slugParts: string[] = []): string { diff --git a/packages/next/src/config.test.ts b/packages/next/src/config.test.ts index 70cf3eb7..a00ba115 100644 --- a/packages/next/src/config.test.ts +++ b/packages/next/src/config.test.ts @@ -57,6 +57,12 @@ const DOCS_CONFIG_WITH_ROOT_DOCS_PATH = `export default { }; `; +const DOCS_CONFIG_WITH_SLASH_DOCS_PATH = `export default { + entry: "docs", + docsPath: "/", +}; +`; + const MARKDOWN_ACCEPT_HEADER = { type: "header", key: "accept", @@ -684,6 +690,118 @@ describe("withDocs (app dir: src/app vs app)", () => { ); }); + it.each([DOCS_CONFIG_WITH_ROOT_DOCS_PATH, DOCS_CONFIG_WITH_SLASH_DOCS_PATH])( + "treats root docsPath values as the site root", + async (configSource) => { + writeFileSync(join(tmpDir, "docs.config.ts"), configSource, "utf-8"); + mkdirSync(join(tmpDir, "app"), { recursive: true }); + process.chdir(tmpDir); + + const nextConfig = withDocs({}); + const beforeFiles = getBeforeFilesRewrites(await readRewrites(nextConfig)); + + expect(beforeFiles).toEqual( + expect.arrayContaining([ + expect.objectContaining({ + source: "/", + destination: "/docs/", + }), + expect.objectContaining({ + source: expect.stringContaining(":slug((?!"), + destination: "/docs/:slug", + }), + expect.objectContaining({ + source: "/", + has: [MARKDOWN_ACCEPT_HEADER], + destination: "/api/docs?format=markdown", + }), + ]), + ); + }, + ); + + it.each(["docs", "/docs", "docs/", "/docs/"])( + "treats %s as the default docsPath", + async (docsPath) => { + writeFileSync( + join(tmpDir, "docs.config.ts"), + `export default { + entry: "docs", + docsPath: ${JSON.stringify(docsPath)}, +}; +`, + "utf-8", + ); + mkdirSync(join(tmpDir, "app"), { recursive: true }); + process.chdir(tmpDir); + + const nextConfig = withDocs({}); + const beforeFiles = getBeforeFilesRewrites(await readRewrites(nextConfig)); + + expect(beforeFiles).toEqual( + expect.arrayContaining([ + expect.objectContaining({ + source: "/docs", + has: [MARKDOWN_ACCEPT_HEADER], + destination: "/api/docs?format=markdown", + }), + expect.objectContaining({ + source: "/docs/:slug*", + has: [MARKDOWN_ACCEPT_HEADER], + destination: "/api/docs?format=markdown&path=:slug*", + }), + ]), + ); + expect(beforeFiles).not.toEqual( + expect.arrayContaining([ + expect.objectContaining({ + source: "/docs", + destination: "/docs", + }), + expect.objectContaining({ + source: "/docs/:slug*", + destination: "/docs/:slug*", + }), + ]), + ); + }, + ); + + it("normalizes duplicate slashes inside docsPath", async () => { + writeFileSync( + join(tmpDir, "docs.config.ts"), + `export default { + entry: "docs", + docsPath: "/guides//docs/", +}; +`, + "utf-8", + ); + mkdirSync(join(tmpDir, "app"), { recursive: true }); + process.chdir(tmpDir); + + const nextConfig = withDocs({}); + const beforeFiles = getBeforeFilesRewrites(await readRewrites(nextConfig)); + + expect(beforeFiles).toEqual( + expect.arrayContaining([ + expect.objectContaining({ + source: "/guides/docs", + destination: "/docs", + }), + expect.objectContaining({ + source: "/guides/docs/:slug*", + destination: "/docs/:slug*", + }), + expect.objectContaining({ + source: "/guides/docs", + has: [MARKDOWN_ACCEPT_HEADER], + destination: "/api/docs?format=markdown", + }), + ]), + ); + }); + it("adds agent feedback rewrites through the shared docs api handler by default", async () => { mkdirSync(join(tmpDir, "app"), { recursive: true }); process.chdir(tmpDir); diff --git a/packages/next/src/config.ts b/packages/next/src/config.ts index 3a1b0103..5ccd3bf3 100644 --- a/packages/next/src/config.ts +++ b/packages/next/src/config.ts @@ -1267,10 +1267,13 @@ function normalizeRouteSegment(value: string | undefined, fallback = "docs"): st function normalizeDocsPath(value: string | undefined, entry: string): string { if (typeof value !== "string") return `/${normalizeRouteSegment(entry)}`; - const cleaned = value.trim(); - if (cleaned === "" || cleaned === "/") return ""; + const cleaned = value + .trim() + .replace(/^\/+|\/+$/g, "") + .replace(/\/+/g, "/"); + if (cleaned === "") return ""; - return `/${cleaned.replace(/^\/+|\/+$/g, "")}`; + return `/${cleaned}`; } function docsRootSource(entry: string): string { diff --git a/packages/next/src/mdx-plugins/remark-markdown-alternate.test.ts b/packages/next/src/mdx-plugins/remark-markdown-alternate.test.ts index ff6bea12..69552ad3 100644 --- a/packages/next/src/mdx-plugins/remark-markdown-alternate.test.ts +++ b/packages/next/src/mdx-plugins/remark-markdown-alternate.test.ts @@ -55,6 +55,39 @@ describe("remarkMarkdownAlternate", () => { expect(tree.children[0]?.value).toContain('text/markdown: "/docs.md"'); }); + it.each(["", "/", "///"])("treats docsPath %s as root-mounted docs", (docsPath) => { + const tree = { + children: [{ type: "yaml", value: 'title: "Install"' }], + }; + const transform = remarkMarkdownAlternate({ + entry: "docs", + contentDir: "app/docs", + docsPath, + }); + + transform(tree, { path: "/repo/app/docs/install/page.mdx" }); + + expect(tree.children[0]?.value).toContain('text/markdown: "/install.md"'); + }); + + it.each(["docs", "/docs", "docs/", "/docs/"])( + "normalizes docsPath %s to the default docs route", + (docsPath) => { + const tree = { + children: [{ type: "yaml", value: 'title: "Install"' }], + }; + const transform = remarkMarkdownAlternate({ + entry: "docs", + contentDir: "app/docs", + docsPath, + }); + + transform(tree, { path: "/repo/app/docs/install/page.mdx" }); + + expect(tree.children[0]?.value).toContain('text/markdown: "/docs/install.md"'); + }, + ); + it("handles relative source paths", () => { const tree = { children: [{ type: "yaml", value: 'title: "Quickstart"' }], diff --git a/packages/next/src/mdx-plugins/remark-markdown-alternate.ts b/packages/next/src/mdx-plugins/remark-markdown-alternate.ts index b7ee944b..a80a24be 100644 --- a/packages/next/src/mdx-plugins/remark-markdown-alternate.ts +++ b/packages/next/src/mdx-plugins/remark-markdown-alternate.ts @@ -29,10 +29,13 @@ function normalizeSegment(value: string | undefined, fallback: string): string { function normalizeDocsPath(value: string | undefined, entry: string): string { if (typeof value !== "string") return `/${entry}`; - const cleaned = value.trim(); - if (cleaned === "" || cleaned === "/") return ""; + const cleaned = value + .trim() + .replace(/^\/+|\/+$/g, "") + .replace(/\/+/g, "/"); + if (cleaned === "") return ""; - return `/${cleaned.replace(/^\/+|\/+$/g, "")}`; + return `/${cleaned}`; } function joinDocsPath(docsPath: string, slug: string): string {