@fumadocs/cli 1.7.3 → 1.7.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (37) hide show
  1. package/dist/{ai-BAvzj17c.js → ai-BoUGzPvv.js} +2 -2
  2. package/dist/{ai-BAvzj17c.js.map → ai-BoUGzPvv.js.map} +1 -1
  3. package/dist/all-BmTB7dab.js +27 -0
  4. package/dist/{all-4XIIhn0t.js.map → all-BmTB7dab.js.map} +1 -1
  5. package/dist/{docs--WqEHTyl.js → docs-qRGwd9Yp.js} +5 -8
  6. package/dist/docs-qRGwd9Yp.js.map +1 -0
  7. package/dist/{epub-jc_2B886.js → epub-DbOh5Q2D.js} +2 -2
  8. package/dist/{epub-jc_2B886.js.map → epub-DbOh5Q2D.js.map} +1 -1
  9. package/dist/features/ai.js +1 -1
  10. package/dist/features/all.js +1 -1
  11. package/dist/features/docs/index.js +1 -1
  12. package/dist/features/epub.js +1 -1
  13. package/dist/features/feedback.js +1 -1
  14. package/dist/features/llms.js +1 -1
  15. package/dist/features/mcp.js +1 -1
  16. package/dist/features/og.js +1 -1
  17. package/dist/features/search.d.ts +6 -7
  18. package/dist/features/search.d.ts.map +1 -1
  19. package/dist/features/search.js +1 -1
  20. package/dist/features/webmcp.js +1 -1
  21. package/dist/{feedback-Brj092vC.js → feedback-DpXYMKtX.js} +2 -2
  22. package/dist/{feedback-Brj092vC.js.map → feedback-DpXYMKtX.js.map} +1 -1
  23. package/dist/index.js +2 -2
  24. package/dist/{llms-Br0RJXXg.js → llms-DQzAst6R.js} +2 -2
  25. package/dist/{llms-Br0RJXXg.js.map → llms-DQzAst6R.js.map} +1 -1
  26. package/dist/{mcp-Ce_W93we.js → mcp-BpFZ_fdV.js} +3 -3
  27. package/dist/{mcp-Ce_W93we.js.map → mcp-BpFZ_fdV.js.map} +1 -1
  28. package/dist/{og-LFlX1Bl4.js → og-m0CwRlRz.js} +3 -3
  29. package/dist/{og-LFlX1Bl4.js.map → og-m0CwRlRz.js.map} +1 -1
  30. package/dist/{search-BbzvfJal.js → search-BCHEW0-K.js} +39 -51
  31. package/dist/search-BCHEW0-K.js.map +1 -0
  32. package/dist/{webmcp-DyTLrsXL.js → webmcp-I9L7xzmw.js} +2 -2
  33. package/dist/{webmcp-DyTLrsXL.js.map → webmcp-I9L7xzmw.js.map} +1 -1
  34. package/package.json +1 -1
  35. package/dist/all-4XIIhn0t.js +0 -27
  36. package/dist/docs--WqEHTyl.js.map +0 -1
  37. package/dist/search-BbzvfJal.js.map +0 -1
@@ -1,4 +1,4 @@
1
- import { t as docs } from "./docs--WqEHTyl.js";
1
+ import { t as docs } from "./docs-qRGwd9Yp.js";
2
2
  import { r as findSource } from "./utils-DbMe7h6c.js";
3
3
  import path from "node:path";
4
4
  //#region src/features/ai.ts
@@ -90,4 +90,4 @@ async function readDynamicSource(ctx) {
90
90
  //#endregion
91
91
  export { ai as t };
92
92
 
93
- //# sourceMappingURL=ai-BAvzj17c.js.map
93
+ //# sourceMappingURL=ai-BoUGzPvv.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"ai-BAvzj17c.js","names":[],"sources":["../src/features/ai.ts"],"sourcesContent":["import path from 'node:path';\nimport type { ImportDeclaration } from 'oxc-parser';\nimport type { Feature, FeatureContext } from '@/features';\nimport { findSource } from './utils';\nimport { docs } from './docs';\n\nconst providers = {\n openrouter: { label: 'AI SDK', hint: 'default to OpenRouter', env: 'OPENROUTER_API_KEY' },\n llmgateway: {\n label: 'LLMGateway',\n hint: 'open-source LLM gateway, API key required',\n env: 'LLM_GATEWAY_API_KEY',\n },\n inkeep: { label: 'Inkeep AI', hint: 'API key required', env: 'INKEEP_API_KEY' },\n};\n\nexport type AIProvider = keyof typeof providers;\n\nexport const ai: Feature<{ provider: AIProvider }> = {\n id: 'ai',\n title: 'AI Chat',\n description: 'Ask AI dialog for your docs',\n requires: [docs],\n options: {\n provider: {\n message: 'Choose an AI provider',\n choices: Object.entries(providers).map(([value, { label, hint }]) => ({\n value: value as AIProvider,\n label,\n hint,\n })),\n },\n },\n supports: (project) =>\n project.static ? 'AI chat requires a server to run the chat route handler.' : true,\n async apply(ctx, { provider }) {\n await ctx.install(`ai/${provider}`);\n ctx.env(providers[provider].env, '');\n if (ctx.project.source.dynamic) await readDynamicSource(ctx);\n\n const { cwd, baseDir, info, config } = ctx.project;\n // e.g. `./components` -> `@/components/ai/layout`\n const clientLayoutImport = `@/${path.posix.join(config.aliases.componentsDir, 'ai/layout')}`;\n const layout = await findSource(cwd, path.join(baseDir, info.routesDir), '<DocsLayout');\n let layoutModule: string | undefined;\n let wired = false;\n\n if (layout !== undefined) {\n await ctx.source(layout, (file) => {\n wired = file.code.includes(`'${clientLayoutImport}'`);\n // `aiChat` takes client state, render the layout from a client component\n const declaration =\n !wired &&\n file.program.body.find(\n (node): node is ImportDeclaration =>\n node.type === 'ImportDeclaration' &&\n node.importKind !== 'type' &&\n /^fumadocs-ui\\/layouts\\/(docs|notebook|glass|spacious)$/.test(node.source.value) &&\n node.specifiers.length === 1 &&\n node.specifiers[0].local.name === 'DocsLayout',\n );\n if (!declaration) return;\n\n layoutModule = declaration.source.value;\n file.s.overwrite(\n declaration.source.start,\n declaration.source.end,\n `'${clientLayoutImport}'`,\n );\n });\n }\n\n if (layoutModule) {\n await ctx.write(\n path.join(baseDir, config.aliases.componentsDir, 'ai/layout.tsx'),\n clientLayout(layoutModule),\n );\n } else if (!wired) {\n ctx.note(\n 'Pass the AI chat to your docs layout, see https://fumadocs.dev/docs/integrations/llms#ask-ai.',\n );\n }\n ctx.note(`Set ${providers[provider].env} in \\`.env.local\\`.`);\n },\n};\n\n/** a client component rendering the docs layout with the installed chat */\nfunction clientLayout(layoutModule: string) {\n // Glass and Spacious layouts have their own trigger\n const trigger = /\\/(docs|notebook)$/.test(layoutModule);\n\n return `'use client';\nimport { DocsLayout as Layout, type DocsLayoutProps } from '${layoutModule}';\nimport { AIChat, AIChatPanel,${trigger ? ' AIChatTrigger,' : ''} useAIChat } from './search';\n\nexport function DocsLayout(props: DocsLayoutProps) {\n return (\n <AIChat>\n <ChatLayout {...props} />${trigger ? '\\n <AIChatTrigger />' : ''}\n </AIChat>\n );\n}\n\nfunction ChatLayout(props: DocsLayoutProps) {\n const { open, setOpen } = useAIChat();\n\n return <Layout {...props} aiChat={{ open, onOpenChange: setOpen, panel: <AIChatPanel /> }} />;\n}\n`;\n}\n\n/** the installed chat route indexes pages of a static `source`, runtime sources resolve on demand */\nasync function readDynamicSource(ctx: FeatureContext) {\n const { cwd, baseDir } = ctx.project;\n const route = await findSource(cwd, baseDir, 'createSearchServer');\n const edited =\n route !== undefined &&\n (await ctx.source(route, (file) => {\n file.s.replaceAll(\n \"import { source } from '@/lib/source';\",\n \"import { getSource } from '@/lib/source';\",\n );\n file.s.replaceAll('source.getPages()', '(await getSource()).getPages()');\n // every page of a runtime source carries its Markdown\n file.s.replaceAll(\" if (!('getText' in page.data)) return null;\\n\\n\", '');\n file.s.replaceAll(\"await page.data.getText('processed')\", 'page.data.content');\n }));\n\n if (!edited)\n ctx.note(\n 'Read pages from `getSource()` in the chat route, the search index is built from `page.data.content`.',\n );\n}\n"],"mappings":";;;;AAMA,MAAM,YAAY;CAChB,YAAY;EAAE,OAAO;EAAU,MAAM;EAAyB,KAAK;CAAqB;CACxF,YAAY;EACV,OAAO;EACP,MAAM;EACN,KAAK;CACP;CACA,QAAQ;EAAE,OAAO;EAAa,MAAM;EAAoB,KAAK;CAAiB;AAChF;AAIA,MAAa,KAAwC;CACnD,IAAI;CACJ,OAAO;CACP,aAAa;CACb,UAAU,CAAC,IAAI;CACf,SAAS,EACP,UAAU;EACR,SAAS;EACT,SAAS,OAAO,QAAQ,SAAS,CAAC,CAAC,KAAK,CAAC,OAAO,EAAE,OAAO,aAAa;GAC7D;GACP;GACA;EACF,EAAE;CACJ,EACF;CACA,WAAW,YACT,QAAQ,SAAS,6DAA6D;CAChF,MAAM,MAAM,KAAK,EAAE,YAAY;EAC7B,MAAM,IAAI,QAAQ,MAAM,UAAU;EAClC,IAAI,IAAI,UAAU,SAAS,CAAC,KAAK,EAAE;EACnC,IAAI,IAAI,QAAQ,OAAO,SAAS,MAAM,kBAAkB,GAAG;EAE3D,MAAM,EAAE,KAAK,SAAS,MAAM,WAAW,IAAI;EAE3C,MAAM,qBAAqB,KAAK,KAAK,MAAM,KAAK,OAAO,QAAQ,eAAe,WAAW;EACzF,MAAM,SAAS,MAAM,WAAW,KAAK,KAAK,KAAK,SAAS,KAAK,SAAS,GAAG,aAAa;EACtF,IAAI;EACJ,IAAI,QAAQ;EAEZ,IAAI,WAAW,KAAA,GACb,MAAM,IAAI,OAAO,SAAS,SAAS;GACjC,QAAQ,KAAK,KAAK,SAAS,IAAI,mBAAmB,EAAE;GAEpD,MAAM,cACJ,CAAC,SACD,KAAK,QAAQ,KAAK,MACf,SACC,KAAK,SAAS,uBACd,KAAK,eAAe,UACpB,yDAAyD,KAAK,KAAK,OAAO,KAAK,KAC/E,KAAK,WAAW,WAAW,KAC3B,KAAK,WAAW,EAAE,CAAC,MAAM,SAAS,YACtC;GACF,IAAI,CAAC,aAAa;GAElB,eAAe,YAAY,OAAO;GAClC,KAAK,EAAE,UACL,YAAY,OAAO,OACnB,YAAY,OAAO,KACnB,IAAI,mBAAmB,EACzB;EACF,CAAC;EAGH,IAAI,cACF,MAAM,IAAI,MACR,KAAK,KAAK,SAAS,OAAO,QAAQ,eAAe,eAAe,GAChE,aAAa,YAAY,CAC3B;OACK,IAAI,CAAC,OACV,IAAI,KACF,+FACF;EAEF,IAAI,KAAK,OAAO,UAAU,SAAS,CAAC,IAAI,oBAAoB;CAC9D;AACF;;AAGA,SAAS,aAAa,cAAsB;CAE1C,MAAM,UAAU,qBAAqB,KAAK,YAAY;CAEtD,OAAO;8DACqD,aAAa;+BAC5C,UAAU,oBAAoB,GAAG;;;;;iCAK/B,UAAU,8BAA8B,GAAG;;;;;;;;;;;AAW5E;;AAGA,eAAe,kBAAkB,KAAqB;CACpD,MAAM,EAAE,KAAK,YAAY,IAAI;CAC7B,MAAM,QAAQ,MAAM,WAAW,KAAK,SAAS,oBAAoB;CAcjE,IAAI,EAZF,UAAU,KAAA,KACT,MAAM,IAAI,OAAO,QAAQ,SAAS;EACjC,KAAK,EAAE,WACL,0CACA,2CACF;EACA,KAAK,EAAE,WAAW,qBAAqB,gCAAgC;EAEvE,KAAK,EAAE,WAAW,yDAAyD,EAAE;EAC7E,KAAK,EAAE,WAAW,wCAAwC,mBAAmB;CAC/E,CAAC,IAGD,IAAI,KACF,sGACF;AACJ"}
1
+ {"version":3,"file":"ai-BoUGzPvv.js","names":[],"sources":["../src/features/ai.ts"],"sourcesContent":["import path from 'node:path';\nimport type { ImportDeclaration } from 'oxc-parser';\nimport type { Feature, FeatureContext } from '@/features';\nimport { findSource } from './utils';\nimport { docs } from './docs';\n\nconst providers = {\n openrouter: { label: 'AI SDK', hint: 'default to OpenRouter', env: 'OPENROUTER_API_KEY' },\n llmgateway: {\n label: 'LLMGateway',\n hint: 'open-source LLM gateway, API key required',\n env: 'LLM_GATEWAY_API_KEY',\n },\n inkeep: { label: 'Inkeep AI', hint: 'API key required', env: 'INKEEP_API_KEY' },\n};\n\nexport type AIProvider = keyof typeof providers;\n\nexport const ai: Feature<{ provider: AIProvider }> = {\n id: 'ai',\n title: 'AI Chat',\n description: 'Ask AI dialog for your docs',\n requires: [docs],\n options: {\n provider: {\n message: 'Choose an AI provider',\n choices: Object.entries(providers).map(([value, { label, hint }]) => ({\n value: value as AIProvider,\n label,\n hint,\n })),\n },\n },\n supports: (project) =>\n project.static ? 'AI chat requires a server to run the chat route handler.' : true,\n async apply(ctx, { provider }) {\n await ctx.install(`ai/${provider}`);\n ctx.env(providers[provider].env, '');\n if (ctx.project.source.dynamic) await readDynamicSource(ctx);\n\n const { cwd, baseDir, info, config } = ctx.project;\n // e.g. `./components` -> `@/components/ai/layout`\n const clientLayoutImport = `@/${path.posix.join(config.aliases.componentsDir, 'ai/layout')}`;\n const layout = await findSource(cwd, path.join(baseDir, info.routesDir), '<DocsLayout');\n let layoutModule: string | undefined;\n let wired = false;\n\n if (layout !== undefined) {\n await ctx.source(layout, (file) => {\n wired = file.code.includes(`'${clientLayoutImport}'`);\n // `aiChat` takes client state, render the layout from a client component\n const declaration =\n !wired &&\n file.program.body.find(\n (node): node is ImportDeclaration =>\n node.type === 'ImportDeclaration' &&\n node.importKind !== 'type' &&\n /^fumadocs-ui\\/layouts\\/(docs|notebook|glass|spacious)$/.test(node.source.value) &&\n node.specifiers.length === 1 &&\n node.specifiers[0].local.name === 'DocsLayout',\n );\n if (!declaration) return;\n\n layoutModule = declaration.source.value;\n file.s.overwrite(\n declaration.source.start,\n declaration.source.end,\n `'${clientLayoutImport}'`,\n );\n });\n }\n\n if (layoutModule) {\n await ctx.write(\n path.join(baseDir, config.aliases.componentsDir, 'ai/layout.tsx'),\n clientLayout(layoutModule),\n );\n } else if (!wired) {\n ctx.note(\n 'Pass the AI chat to your docs layout, see https://fumadocs.dev/docs/integrations/llms#ask-ai.',\n );\n }\n ctx.note(`Set ${providers[provider].env} in \\`.env.local\\`.`);\n },\n};\n\n/** a client component rendering the docs layout with the installed chat */\nfunction clientLayout(layoutModule: string) {\n // Glass and Spacious layouts have their own trigger\n const trigger = /\\/(docs|notebook)$/.test(layoutModule);\n\n return `'use client';\nimport { DocsLayout as Layout, type DocsLayoutProps } from '${layoutModule}';\nimport { AIChat, AIChatPanel,${trigger ? ' AIChatTrigger,' : ''} useAIChat } from './search';\n\nexport function DocsLayout(props: DocsLayoutProps) {\n return (\n <AIChat>\n <ChatLayout {...props} />${trigger ? '\\n <AIChatTrigger />' : ''}\n </AIChat>\n );\n}\n\nfunction ChatLayout(props: DocsLayoutProps) {\n const { open, setOpen } = useAIChat();\n\n return <Layout {...props} aiChat={{ open, onOpenChange: setOpen, panel: <AIChatPanel /> }} />;\n}\n`;\n}\n\n/** the installed chat route indexes pages of a static `source`, runtime sources resolve on demand */\nasync function readDynamicSource(ctx: FeatureContext) {\n const { cwd, baseDir } = ctx.project;\n const route = await findSource(cwd, baseDir, 'createSearchServer');\n const edited =\n route !== undefined &&\n (await ctx.source(route, (file) => {\n file.s.replaceAll(\n \"import { source } from '@/lib/source';\",\n \"import { getSource } from '@/lib/source';\",\n );\n file.s.replaceAll('source.getPages()', '(await getSource()).getPages()');\n // every page of a runtime source carries its Markdown\n file.s.replaceAll(\" if (!('getText' in page.data)) return null;\\n\\n\", '');\n file.s.replaceAll(\"await page.data.getText('processed')\", 'page.data.content');\n }));\n\n if (!edited)\n ctx.note(\n 'Read pages from `getSource()` in the chat route, the search index is built from `page.data.content`.',\n );\n}\n"],"mappings":";;;;AAMA,MAAM,YAAY;CAChB,YAAY;EAAE,OAAO;EAAU,MAAM;EAAyB,KAAK;CAAqB;CACxF,YAAY;EACV,OAAO;EACP,MAAM;EACN,KAAK;CACP;CACA,QAAQ;EAAE,OAAO;EAAa,MAAM;EAAoB,KAAK;CAAiB;AAChF;AAIA,MAAa,KAAwC;CACnD,IAAI;CACJ,OAAO;CACP,aAAa;CACb,UAAU,CAAC,IAAI;CACf,SAAS,EACP,UAAU;EACR,SAAS;EACT,SAAS,OAAO,QAAQ,SAAS,CAAC,CAAC,KAAK,CAAC,OAAO,EAAE,OAAO,aAAa;GAC7D;GACP;GACA;EACF,EAAE;CACJ,EACF;CACA,WAAW,YACT,QAAQ,SAAS,6DAA6D;CAChF,MAAM,MAAM,KAAK,EAAE,YAAY;EAC7B,MAAM,IAAI,QAAQ,MAAM,UAAU;EAClC,IAAI,IAAI,UAAU,SAAS,CAAC,KAAK,EAAE;EACnC,IAAI,IAAI,QAAQ,OAAO,SAAS,MAAM,kBAAkB,GAAG;EAE3D,MAAM,EAAE,KAAK,SAAS,MAAM,WAAW,IAAI;EAE3C,MAAM,qBAAqB,KAAK,KAAK,MAAM,KAAK,OAAO,QAAQ,eAAe,WAAW;EACzF,MAAM,SAAS,MAAM,WAAW,KAAK,KAAK,KAAK,SAAS,KAAK,SAAS,GAAG,aAAa;EACtF,IAAI;EACJ,IAAI,QAAQ;EAEZ,IAAI,WAAW,KAAA,GACb,MAAM,IAAI,OAAO,SAAS,SAAS;GACjC,QAAQ,KAAK,KAAK,SAAS,IAAI,mBAAmB,EAAE;GAEpD,MAAM,cACJ,CAAC,SACD,KAAK,QAAQ,KAAK,MACf,SACC,KAAK,SAAS,uBACd,KAAK,eAAe,UACpB,yDAAyD,KAAK,KAAK,OAAO,KAAK,KAC/E,KAAK,WAAW,WAAW,KAC3B,KAAK,WAAW,EAAE,CAAC,MAAM,SAAS,YACtC;GACF,IAAI,CAAC,aAAa;GAElB,eAAe,YAAY,OAAO;GAClC,KAAK,EAAE,UACL,YAAY,OAAO,OACnB,YAAY,OAAO,KACnB,IAAI,mBAAmB,EACzB;EACF,CAAC;EAGH,IAAI,cACF,MAAM,IAAI,MACR,KAAK,KAAK,SAAS,OAAO,QAAQ,eAAe,eAAe,GAChE,aAAa,YAAY,CAC3B;OACK,IAAI,CAAC,OACV,IAAI,KACF,+FACF;EAEF,IAAI,KAAK,OAAO,UAAU,SAAS,CAAC,IAAI,oBAAoB;CAC9D;AACF;;AAGA,SAAS,aAAa,cAAsB;CAE1C,MAAM,UAAU,qBAAqB,KAAK,YAAY;CAEtD,OAAO;8DACqD,aAAa;+BAC5C,UAAU,oBAAoB,GAAG;;;;;iCAK/B,UAAU,8BAA8B,GAAG;;;;;;;;;;;AAW5E;;AAGA,eAAe,kBAAkB,KAAqB;CACpD,MAAM,EAAE,KAAK,YAAY,IAAI;CAC7B,MAAM,QAAQ,MAAM,WAAW,KAAK,SAAS,oBAAoB;CAcjE,IAAI,EAZF,UAAU,KAAA,KACT,MAAM,IAAI,OAAO,QAAQ,SAAS;EACjC,KAAK,EAAE,WACL,0CACA,2CACF;EACA,KAAK,EAAE,WAAW,qBAAqB,gCAAgC;EAEvE,KAAK,EAAE,WAAW,yDAAyD,EAAE;EAC7E,KAAK,EAAE,WAAW,wCAAwC,mBAAmB;CAC/E,CAAC,IAGD,IAAI,KACF,sGACF;AACJ"}
@@ -0,0 +1,27 @@
1
+ import { t as docs } from "./docs-qRGwd9Yp.js";
2
+ import { t as ai } from "./ai-BoUGzPvv.js";
3
+ import { r as llms } from "./llms-DQzAst6R.js";
4
+ import { t as og } from "./og-m0CwRlRz.js";
5
+ import { t as search } from "./search-BCHEW0-K.js";
6
+ import { t as mcp } from "./mcp-BpFZ_fdV.js";
7
+ import { n as webmcp } from "./webmcp-I9L7xzmw.js";
8
+ import { t as feedback } from "./feedback-DpXYMKtX.js";
9
+ import { t as epub } from "./epub-DbOh5Q2D.js";
10
+ import { t as lint } from "./lint-zkXUULgO.js";
11
+ //#region src/features/all.ts
12
+ const features = [
13
+ docs,
14
+ ai,
15
+ llms,
16
+ mcp,
17
+ webmcp,
18
+ og,
19
+ search,
20
+ feedback,
21
+ epub,
22
+ lint
23
+ ];
24
+ //#endregion
25
+ export { features as t };
26
+
27
+ //# sourceMappingURL=all-BmTB7dab.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"all-4XIIhn0t.js","names":[],"sources":["../src/features/all.ts"],"sourcesContent":["import type { AnyFeature } from '.';\nimport { docs } from './docs';\nimport { ai } from './ai';\nimport { llms } from './llms';\nimport { og } from './og';\nimport { search } from './search';\nimport { mcp } from './mcp';\nimport { webmcp } from './webmcp';\nimport { feedback } from './feedback';\nimport { epub } from './epub';\nimport { lint } from './lint';\n\nexport const features: AnyFeature[] = [\n docs,\n ai,\n llms,\n mcp,\n webmcp,\n og,\n search,\n feedback,\n epub,\n lint,\n];\n"],"mappings":";;;;;;;;;;;AAYA,MAAa,WAAyB;CACpC;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;AACF"}
1
+ {"version":3,"file":"all-BmTB7dab.js","names":[],"sources":["../src/features/all.ts"],"sourcesContent":["import type { AnyFeature } from '.';\nimport { docs } from './docs';\nimport { ai } from './ai';\nimport { llms } from './llms';\nimport { og } from './og';\nimport { search } from './search';\nimport { mcp } from './mcp';\nimport { webmcp } from './webmcp';\nimport { feedback } from './feedback';\nimport { epub } from './epub';\nimport { lint } from './lint';\n\nexport const features: AnyFeature[] = [\n docs,\n ai,\n llms,\n mcp,\n webmcp,\n og,\n search,\n feedback,\n epub,\n lint,\n];\n"],"mappings":";;;;;;;;;;;AAYA,MAAa,WAAyB;CACpC;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;AACF"}
@@ -181,16 +181,13 @@ import {
181
181
  SearchDialogOverlay,
182
182
  type SharedProps,
183
183
  } from 'fumadocs-ui/components/dialog/search';
184
- import { useDocsSearch } from 'fumadocs-core/search/client';
185
- import { staticClient } from 'fumadocs-core/search/client/orama-static';
184
+ import { useStaticSearch } from 'fumadocs-core/search/client';
186
185
  ${i18n ? "import { useI18n } from 'fumadocs-ui/contexts/i18n';\n" : ""}
187
186
  export default function DefaultSearchDialog(props: SharedProps) {
188
- ${i18n ? " const { locale } = useI18n();\n" : ""} const { search, setSearch, query } = useDocsSearch({
189
- client: staticClient(${i18n ? "{ locale }" : ""}),
190
- });
187
+ ${i18n ? " const { locale } = useI18n();\n" : ""} const search = useStaticSearch(${i18n ? "{ locale }" : ""});
191
188
 
192
189
  return (
193
- <SearchDialog search={search} onSearchChange={setSearch} isLoading={query.isLoading} {...props}>
190
+ <SearchDialog {...search} {...props}>
194
191
  <SearchDialogOverlay />
195
192
  <SearchDialogContent>
196
193
  <SearchDialogHeader>
@@ -198,7 +195,7 @@ ${i18n ? " const { locale } = useI18n();\n" : ""} const { search, setSearch, q
198
195
  <SearchDialogInput />
199
196
  <SearchDialogClose />
200
197
  </SearchDialogHeader>
201
- <SearchDialogList items={query.data !== 'empty' ? query.data : null} />
198
+ <SearchDialogList />
202
199
  </SearchDialogContent>
203
200
  </SearchDialog>
204
201
  );
@@ -735,4 +732,4 @@ async function configureRoutes(ctx, i18n) {
735
732
  //#endregion
736
733
  export { routeModule as a, reactRouterTypes as i, findNextProxy as n, formatRoute as r, docs as t };
737
734
 
738
- //# sourceMappingURL=docs--WqEHTyl.js.map
735
+ //# sourceMappingURL=docs-qRGwd9Yp.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"docs-qRGwd9Yp.js","names":[],"sources":["../src/project/route.ts","../src/features/docs/templates.ts","../src/features/docs/index.ts"],"sourcesContent":["import type { I18nInfo, ReactFramework } from '@/project';\n\nexport type RouteSegment =\n /** static path, can contain multiple segments like `/llms.mdx/docs` */\n | string\n | {\n param: string;\n catchAll?: boolean;\n /** the catch-all also matches zero segments */\n optional?: boolean;\n /** static file name after the catch-all like `content.md`, part of the parameter when the framework can't express it */\n suffix?: string;\n };\n\nexport interface RouteDescriptor {\n segments: RouteSegment[];\n /** under the locale parameter when the project has i18n */\n locale?: boolean;\n}\n\nexport interface FormattedRoute {\n /** route file, relative to the base dir */\n file: string;\n /** route string of the framework, e.g. for `RouteContext<'...'>`, `createFileRoute('...')`, `route('...')` or `ApiContext<'...'>` */\n path: string;\n /** path-to-regexp pattern, for the Next.js proxy matcher and rewrites */\n pattern: string;\n}\n\ninterface Convention {\n locale: (optional: boolean) => string;\n param: (name: string, catchAll: boolean, optional: boolean, suffix?: string) => string;\n file: (segments: string[], ext: string) => string;\n path: (segments: string[]) => string;\n}\n\nconst conventions: Record<ReactFramework, Convention> = {\n next: {\n locale: () => '[lang]',\n param: (name, catchAll, optional) =>\n catchAll ? (optional ? `[[...${name}]]` : `[...${name}]`) : `[${name}]`,\n file: (segments, ext) => `app/${segments.join('/')}/route.${ext}`,\n path: (segments) => `/${segments.join('/')}`,\n },\n 'react-router': {\n locale: (optional) => (optional ? ':lang?' : ':lang'),\n param: (name, catchAll) => (catchAll ? '*' : `:${name}`),\n // `routes.ts` maps paths to files, name files after the static segments like flat routes\n file: (segments, ext) =>\n `routes/${segments.filter((segment) => !/^[:*]/.test(segment)).join('.')}.${ext}`,\n path: (segments) => segments.join('/'),\n },\n 'tanstack-start': {\n locale: (optional) => (optional ? '{-$lang}' : '$lang'),\n param: (name, catchAll) => (catchAll ? '$' : `$${name}`),\n file: (segments, ext) => `routes/${segments.join('/').replaceAll('.', '[.]')}.${ext}`,\n path: (segments) => `/${segments.join('/')}`,\n },\n waku: {\n locale: () => '[lang]',\n param: (name, catchAll, _optional, suffix) =>\n catchAll ? `[...${name}]${suffix ? `/${suffix}` : ''}` : `[${name}]`,\n file: (segments, ext) => `pages/_api/${segments.join('/')}.${ext}`,\n path: (segments) => `/${segments.join('/')}`,\n },\n};\n\n/** the locale segment of route files, with a trailing slash */\nexport function localeSegment(framework: ReactFramework, i18n: I18nInfo | null) {\n return i18n ? `${conventions[framework].locale(i18n.optionalLocale)}/` : '';\n}\n\nexport function formatRoute(\n route: RouteDescriptor,\n framework: ReactFramework,\n i18n: I18nInfo | null,\n ext = 'ts',\n): FormattedRoute {\n const convention = conventions[framework];\n const segments: string[] = [];\n const patterns: string[] = [];\n if (route.locale && i18n) {\n segments.push(convention.locale(i18n.optionalLocale));\n patterns.push(':lang');\n }\n\n for (const segment of route.segments) {\n if (typeof segment === 'string') {\n for (const part of segment.split('/')) {\n if (part.length === 0) continue;\n segments.push(part);\n patterns.push(part);\n }\n } else {\n const { param, catchAll = false, optional = false, suffix } = segment;\n segments.push(convention.param(param, catchAll, optional, suffix));\n patterns.push(catchAll ? `:${param}*` : `:${param}`);\n }\n }\n\n return {\n file: convention.file(segments, ext),\n path: convention.path(segments),\n pattern: `/${patterns.join('/')}`,\n };\n}\n\nexport interface RouteModule {\n /** import statements of the handler */\n imports: string;\n /** expression returning a `Response` */\n body: string;\n /** the handler reads `request` */\n request?: boolean;\n /** @defaultValue `['GET']` */\n methods?: string[];\n /** Next.js: never revalidate the route */\n revalidate?: boolean;\n /** Waku: prerender the route */\n static?: boolean;\n}\n\n/** the route module of a handler that only reads the request, per framework */\nexport function routeModule(\n framework: ReactFramework,\n route: FormattedRoute,\n mod: RouteModule,\n): string {\n const { imports, body, request, methods = ['GET'], revalidate, static: isStatic } = mod;\n const arg = request ? 'request: Request' : '';\n const head = `${imports.trim()}\\n\\n`;\n\n if (framework === 'react-router') {\n const out: string[] = [];\n // GET is the loader, every other method is the action\n out.push(handler('loader', 'Route.LoaderArgs'));\n if (methods.length > 1) out.push(handler('action', 'Route.ActionArgs'));\n\n return `${request ? `${reactRouterTypes(route)}\\n` : ''}${head}${out.join('\\n')}`;\n }\n\n if (framework === 'tanstack-start') {\n const out: string[] = [];\n for (const method of methods)\n out.push(` ${method}: async (${request ? '{ request }' : ''}) => ${body},`);\n\n return `import { createFileRoute } from '@tanstack/react-router';\n${head}export const Route = createFileRoute('${route.path}')({\n server: {\n handlers: {\n${out.join('\\n')}\n },\n },\n});\n`;\n }\n\n const out: string[] = [];\n if (framework === 'next' && revalidate) out.push('export const revalidate = false;\\n');\n for (const method of methods)\n out.push(`export async function ${method}(${arg}) {\n return ${body};\n}\n`);\n if (framework === 'waku' && isStatic)\n out.push(`export async function getConfig() {\n return {\n render: 'static' as const,\n } as const;\n}\n`);\n\n return `${head}${out.join('\\n')}`;\n\n function handler(name: string, type: string) {\n return `export async function ${name}(${request ? `{ request }: ${type}` : ''}) {\n return ${body};\n}\n`;\n }\n}\n\n/** import of the generated types of a React Router route module */\nexport const reactRouterTypes = (route: FormattedRoute) =>\n `import type { Route } from './+types/${route.file\n .split('/')\n .pop()!\n .replace(/\\.tsx?$/, '')}';`;\n","import type { I18nInfo, ReactFramework } from '@/project';\nimport { localeSegment } from '@/project/route';\n\nexport interface TemplateInput {\n static: boolean;\n i18n: I18nInfo | null;\n /** import specifier of `RootProvider` */\n provider: string;\n /** extra props of `RootProvider` */\n providerProps: string;\n}\n\n/** files relative to the base dir */\nexport type Template = (input: TemplateInput) => Record<string, string>;\n\nconst wrapper = (children: string) =>\n `<div className=\"flex flex-col min-h-screen\">${children}</div>`;\n\nconst i18nConfig = (optionalLocale: boolean) => `import { defineI18n } from 'fumadocs-core/i18n';\n\nexport const i18n = defineI18n({\n defaultLanguage: 'en',\n languages: ['en', 'cn'],${optionalLocale ? \"\\n hideLocale: 'default-locale',\" : ''}\n});\n`;\n\nconst sharedConstants = `export const docsRoute = '/docs';\n`;\n\nconst source = (async: boolean, i18n: boolean) => `import { loader } from 'fumadocs-core/source';\nimport { defineDocs } from 'fumadocs-mdx/macro';\nimport { docsRoute } from './shared';\n${i18n ? \"import { i18n } from '@/lib/i18n';\\n\" : ''}\nexport const docs = defineDocs({\n dir: 'content/docs',${async ? '\\n docs: {\\n async: true,\\n },' : ''}\n});\n\nexport const source = loader({\n baseUrl: docsRoute,\n source: docs.toFumadocsSource(),${i18n ? '\\n i18n,' : ''}\n});\n`;\n\nconst layoutShared = (i18n: boolean) =>\n i18n\n ? `import type { BaseLayoutProps } from 'fumadocs-ui/layouts/shared';\nimport { uiTranslations } from 'fumadocs-ui/i18n';\nimport { i18n } from '@/lib/i18n';\n\nexport const translations = i18n.translations().extend(uiTranslations());\n\nexport function baseOptions(locale: string = i18n.defaultLanguage): BaseLayoutProps {\n return {\n nav: {\n title: 'My App',\n url: \\`/\\${locale}\\`,\n },\n };\n}\n`\n : `import type { BaseLayoutProps } from 'fumadocs-ui/layouts/shared';\n\nexport function baseOptions(): BaseLayoutProps {\n return {\n nav: {\n title: 'My App',\n },\n };\n}\n`;\n\nconst mdx = `import defaultMdxComponents from 'fumadocs-ui/mdx';\nimport type { MDXComponents } from 'mdx/types';\n\nexport function getMDXComponents(components?: MDXComponents) {\n return {\n ...defaultMdxComponents,\n ...components,\n } satisfies MDXComponents;\n}\n\nexport const useMDXComponents = getMDXComponents;\n\ndeclare global {\n type MDXProvidedComponents = ReturnType<typeof getMDXComponents>;\n}\n`;\n\nconst search = (i18n: boolean) => `'use client';\nimport {\n SearchDialog,\n SearchDialogClose,\n SearchDialogContent,\n SearchDialogHeader,\n SearchDialogIcon,\n SearchDialogInput,\n SearchDialogList,\n SearchDialogOverlay,\n type SharedProps,\n} from 'fumadocs-ui/components/dialog/search';\nimport { useStaticSearch } from 'fumadocs-core/search/client';\n${i18n ? \"import { useI18n } from 'fumadocs-ui/contexts/i18n';\\n\" : ''}\nexport default function DefaultSearchDialog(props: SharedProps) {\n${i18n ? ' const { locale } = useI18n();\\n' : ''} const search = useStaticSearch(${i18n ? '{ locale }' : ''});\n\n return (\n <SearchDialog {...search} {...props}>\n <SearchDialogOverlay />\n <SearchDialogContent>\n <SearchDialogHeader>\n <SearchDialogIcon />\n <SearchDialogInput />\n <SearchDialogClose />\n </SearchDialogHeader>\n <SearchDialogList />\n </SearchDialogContent>\n </SearchDialog>\n );\n}\n`;\n\nfunction shared(input: TemplateInput, async: boolean): Record<string, string> {\n const i18n = input.i18n !== null;\n return {\n 'lib/shared.ts': sharedConstants,\n 'lib/source.ts': source(async, i18n),\n 'lib/layout.shared.tsx': layoutShared(i18n),\n 'components/mdx.tsx': mdx,\n ...(input.static ? { 'components/search.tsx': search(i18n) } : {}),\n ...(input.i18n ? { 'lib/i18n.ts': i18nConfig(input.i18n.optionalLocale) } : {}),\n };\n}\n\nconst searchImport = (input: TemplateInput) =>\n input.static ? `import SearchDialog from '@/components/search';\\n` : '';\n\n/** imports of the root provider, with the i18n provider */\nconst providerImports = (input: TemplateInput) =>\n `import { RootProvider } from '${input.provider}';\n${searchImport(input)}${\n input.i18n\n ? `import { i18nProvider } from 'fumadocs-ui/i18n';\nimport { translations } from '@/lib/layout.shared';\nimport { i18n } from '@/lib/i18n';\n`\n : ''\n }`;\n\nconst providerProps = (input: TemplateInput) =>\n `${input.i18n ? ' i18n={i18nProvider(translations, lang)}' : ''}${input.providerProps}`;\n\n/** the docs page for React Router & TanStack Start, where MDX content is loaded lazily on client */\nconst clientContent = `function Content({ path }: { path: string }) {\n const page = docs.getPage(path);\n if (!page) throw new Error(\\`unknown page: \\${path}\\`);\n\n const { toc } = use(page.load());\n const MDX = page.body;\n\n return (\n <DocsPage toc={toc}>\n <DocsTitle>{page.title}</DocsTitle>\n <DocsDescription>{page.description}</DocsDescription>\n <DocsBody>\n <MDX components={useMDXComponents()} />\n </DocsBody>\n </DocsPage>\n );\n}`;\n\nconst searchRoute = {\n next: (input: TemplateInput) => `import { source } from '@/lib/source';\nimport { createFromSource } from 'fumadocs-core/search/server';\n${input.static ? '\\nexport const revalidate = false;\\n' : ''}\nexport const { ${input.static ? 'staticGET: GET' : 'GET'} } = createFromSource(source);\n`,\n waku: `import { createFromSource } from 'fumadocs-core/search/server';\nimport { source } from '@/lib/source';\n\nexport const { GET } = createFromSource(source);\n`,\n};\n\nexport const templates: Record<ReactFramework, Template> = {\n next: (input) => {\n const i18n = input.i18n !== null;\n // route group with `[lang]` segment for i18n\n const dir = i18n ? 'app/(docs)/[lang]' : 'app/(docs)';\n const route = i18n ? '/[lang]' : '';\n const lang = i18n ? ', params.lang' : '';\n\n return {\n ...shared(input, false),\n [`${dir}/layout.tsx`]: input.static\n ? `'use client';\n${providerImports(input)}${i18n ? \"import { use } from 'react';\\n\" : ''}\nexport default function Layout({ ${i18n ? 'params, ' : ''}children }: LayoutProps<'${route || '/'}'>) {\n${i18n ? ' const { lang } = use(params);\\n\\n' : ''} return (\n <RootProvider${providerProps(input)}>\n ${wrapper('{children}')}\n </RootProvider>\n );\n}\n`\n : `${providerImports(input)}\nexport default ${i18n ? 'async ' : ''}function Layout({ ${i18n ? 'params, ' : ''}children }: LayoutProps<'${route || '/'}'>) {\n${i18n ? ' const { lang } = await params;\\n\\n' : ''} return (\n <RootProvider${providerProps(input)}>\n ${wrapper('{children}')}\n </RootProvider>\n );\n}\n`,\n [`${dir}/docs/layout.tsx`]: `import { source } from '@/lib/source';\nimport { DocsLayout } from 'fumadocs-ui/layouts/docs';\nimport { baseOptions } from '@/lib/layout.shared';\n\nexport default ${i18n ? 'async ' : ''}function Layout({ ${i18n ? 'params, ' : ''}children }: LayoutProps<'${route}/docs'>) {\n${i18n ? ' const { lang } = await params;\\n\\n' : ''} return (\n <DocsLayout tree={source.getPageTree(${i18n ? 'lang' : ''})} {...baseOptions(${i18n ? 'lang' : ''})}>\n {children}\n </DocsLayout>\n );\n}\n`,\n [`${dir}/docs/[[...slug]]/page.tsx`]: `import { source } from '@/lib/source';\nimport { DocsBody, DocsDescription, DocsPage, DocsTitle } from 'fumadocs-ui/layouts/docs/page';\nimport { notFound } from 'next/navigation';\nimport { getMDXComponents } from '@/components/mdx';\nimport type { Metadata } from 'next';\nimport { createRelativeLink } from 'fumadocs-ui/mdx';\n\nexport default async function Page(props: PageProps<'${route}/docs/[[...slug]]'>) {\n const params = await props.params;\n const page = source.getPage(params.slug${lang});\n if (!page) notFound();\n\n const MDX = page.data.body;\n\n return (\n <DocsPage toc={page.data.toc} full={page.data.full}>\n <DocsTitle>{page.data.title}</DocsTitle>\n <DocsDescription>{page.data.description}</DocsDescription>\n <DocsBody>\n <MDX\n components={getMDXComponents({\n // this allows you to link to other pages with relative file paths\n a: createRelativeLink(source, page),\n })}\n />\n </DocsBody>\n </DocsPage>\n );\n}\n\nexport async function generateStaticParams() {\n return source.generateParams();\n}\n\nexport async function generateMetadata(props: PageProps<'${route}/docs/[[...slug]]'>): Promise<Metadata> {\n const params = await props.params;\n const page = source.getPage(params.slug${lang});\n if (!page) notFound();\n\n return {\n title: page.data.title,\n description: page.data.description,\n };\n}\n`,\n 'app/api/search/route.ts': searchRoute.next(input),\n };\n },\n\n 'react-router': (input) => {\n const i18n = input.i18n !== null;\n return {\n ...shared(input, true),\n 'routes/docs/layout.tsx': `import { Outlet${i18n ? ', useParams' : ''} } from 'react-router';\n${providerImports(input)}\nexport default function Layout() {\n${i18n ? ' const { lang = i18n.defaultLanguage } = useParams();\\n\\n' : ''} return (\n <RootProvider${providerProps(input)}>\n ${wrapper('\\n <Outlet />\\n ')}\n </RootProvider>\n );\n}\n`,\n 'routes/docs/page.tsx': `import type { Route } from './+types/page';\nimport { DocsLayout } from 'fumadocs-ui/layouts/docs';\nimport { DocsBody, DocsDescription, DocsPage, DocsTitle } from 'fumadocs-ui/layouts/docs/page';\nimport { docs, source } from '@/lib/source';\nimport { baseOptions } from '@/lib/layout.shared';\nimport { useFumadocsLoader } from 'fumadocs-core/source/client';\nimport { useMDXComponents } from '@/components/mdx';\nimport { use } from 'react';\n\nexport async function loader({ params }: Route.LoaderArgs) {\n const slugs = params['*'].split('/').filter((v) => v.length > 0);\n const page = source.getPage(slugs${i18n ? ', params.lang' : ''});\n if (!page) throw new Response('Not found', { status: 404 });\n\n return {\n path: page.path,\n url: page.url,\n pageTree: await source.serializePageTree(source.getPageTree(${i18n ? 'params.lang' : ''})),\n };\n}\n\n${clientContent.replace('<DocsPage toc={toc}>', '<DocsPage toc={toc}>\\n <title>{page.title}</title>\\n <meta name=\"description\" content={page.description} />')}\n\nexport default function Page({ loaderData${i18n ? ', params' : ''} }: Route.ComponentProps) {\n const { path, pageTree } = useFumadocsLoader(loaderData);\n\n return (\n <DocsLayout {...baseOptions(${i18n ? 'params.lang' : ''})} tree={pageTree}>\n <Content path={path} />\n </DocsLayout>\n );\n}\n`,\n 'routes/docs/search.ts': `import type { Route } from './+types/search';\nimport { createFromSource } from 'fumadocs-core/search/server';\nimport { source } from '@/lib/source';\n\nconst server = createFromSource(source);\n\nexport async function loader(${input.static ? '' : '{ request }: Route.LoaderArgs'}) {\n return server.${input.static ? 'staticGET()' : 'GET(request)'};\n}\n`,\n };\n },\n\n 'tanstack-start': (input) => {\n const seg = localeSegment('tanstack-start', input.i18n);\n const i18n = input.i18n !== null;\n return {\n ...shared(input, true),\n 'routes/_docs.tsx': `import { createFileRoute, Outlet${i18n ? ', useParams' : ''} } from '@tanstack/react-router';\n${providerImports(input)}\nexport const Route = createFileRoute('/_docs')({\n component: Layout,\n});\n\nfunction Layout() {\n${i18n ? ' const { lang = i18n.defaultLanguage } = useParams({ strict: false });\\n\\n' : ''} return (\n <RootProvider${providerProps(input)}>\n ${wrapper('\\n <Outlet />\\n ')}\n </RootProvider>\n );\n}\n`,\n [`routes/_docs/${seg}docs/$.tsx`]: `import { createFileRoute, notFound } from '@tanstack/react-router';\nimport { DocsLayout } from 'fumadocs-ui/layouts/docs';\nimport { createServerFn } from '@tanstack/react-start';\nimport { docs, source } from '@/lib/source';\nimport { DocsBody, DocsDescription, DocsPage, DocsTitle } from 'fumadocs-ui/layouts/docs/page';\nimport { baseOptions } from '@/lib/layout.shared';\nimport { useFumadocsLoader } from 'fumadocs-core/source/client';\nimport { Suspense, use } from 'react';\nimport { useMDXComponents } from '@/components/mdx';\n\nexport const Route = createFileRoute('/_docs/${seg}docs/$')({\n component: Page,\n loader: async ({ params }) => {\n const data = await serverLoader({\n data: {\n slugs: params._splat?.split('/') ?? [],${i18n ? '\\n lang: params.lang,' : ''}\n },\n });\n await docs.getPage(data.path)?.preload();\n return data;\n },\n});\n\nconst serverLoader = createServerFn({\n method: 'GET',\n})\n .validator((params: { slugs: string[]${i18n ? '; lang?: string' : ''} }) => params)\n .handler(async ({ data: { slugs${i18n ? ', lang' : ''} } }) => {\n const page = source.getPage(slugs${i18n ? ', lang' : ''});\n if (!page) throw notFound();\n\n return {\n path: page.path,\n pageTree: await source.serializePageTree(source.getPageTree(${i18n ? 'lang' : ''})),\n };\n });\n\n${clientContent}\n\nfunction Page() {\n${i18n ? ' const { lang } = Route.useParams();\\n' : ''} const data = useFumadocsLoader(Route.useLoaderData());\n\n return (\n <DocsLayout {...baseOptions(${i18n ? 'lang' : ''})} tree={data.pageTree}>\n <Suspense>\n <Content path={data.path} />\n </Suspense>\n </DocsLayout>\n );\n}\n`,\n 'routes/api/search.ts': `import { createFileRoute } from '@tanstack/react-router';\nimport { source } from '@/lib/source';\nimport { createFromSource } from 'fumadocs-core/search/server';\n\nconst server = createFromSource(source);\n\nexport const Route = createFileRoute('/api/search')({\n server: {\n handlers: {\n GET: ${input.static ? '() => server.staticGET()' : 'async ({ request }) => server.GET(request)'},\n },\n },\n});\n`,\n };\n },\n\n waku: (input) => {\n const i18n = input.i18n !== null;\n const page = `import { source } from '@/lib/source';\nimport type { PageProps } from 'waku/router';\nimport { unstable_notFound } from 'waku/router/server';\nimport { createRelativeLink } from 'fumadocs-ui/mdx';\nimport { DocsBody, DocsDescription, DocsPage, DocsTitle } from 'fumadocs-ui/layouts/docs/page';\nimport { getMDXComponents } from '@/components/mdx';\n${i18n ? \"import { DocsLayout } from 'fumadocs-ui/layouts/docs';\\nimport { baseOptions } from '@/lib/layout.shared';\\n\" : ''}\nexport default function Page({ slug${i18n ? ', lang' : ''} }: PageProps<'${i18n ? '/[lang]' : ''}/docs/[...slug]'>) {\n const page = source.getPage(slug${i18n ? ', lang' : ''});\n if (!page) unstable_notFound();\n\n const MDX = page.data.body;\n const content = (\n <DocsPage toc={page.data.toc}>\n <DocsTitle>{page.data.title}</DocsTitle>\n <DocsDescription>{page.data.description}</DocsDescription>\n <DocsBody>\n <MDX\n components={getMDXComponents({\n // this allows you to link to other pages with relative file paths\n a: createRelativeLink(source, page),\n })}\n />\n </DocsBody>\n </DocsPage>\n );\n${\n i18n\n ? `\n // layouts don't receive the locale, render the docs layout here\n return (\n <DocsLayout {...baseOptions(lang)} tree={source.getPageTree(lang)}>\n {content}\n </DocsLayout>\n );`\n : `\n return content;`\n}\n}\n\nexport async function getConfig() {\n return {\n render: 'static',\n staticPaths: source.generateParams().map((item) => ${i18n ? '[item.lang, ...item.slug]' : 'item.slug'}),\n } as const;\n}\n`;\n\n return {\n ...shared(input, false),\n 'pages/(docs)/_layout.tsx': i18n\n ? `import type { ReactNode } from 'react';\nimport { Provider } from '@/components/provider';\n\nexport default function Layout({ children }: { children: ReactNode }) {\n return (\n <Provider>\n ${wrapper('{children}')}\n </Provider>\n );\n}\n`\n : `import type { ReactNode } from 'react';\n${providerImports(input)}\nexport default function Layout({ children }: { children: ReactNode }) {\n return (\n <RootProvider${providerProps(input)}>\n ${wrapper('{children}')}\n </RootProvider>\n );\n}\n`,\n ...(i18n\n ? {\n 'components/provider.tsx': `'use client';\nimport type { ReactNode } from 'react';\nimport { useRouter } from 'waku/router/client';\n${providerImports(input)}\nexport function Provider({ children }: { children: ReactNode }) {\n const router = useRouter();\n const lang = router.path.split('/')[1] || i18n.defaultLanguage;\n\n return <RootProvider${providerProps(input)}>{children}</RootProvider>;\n}\n`,\n 'pages/(docs)/[lang]/docs/[...slug].tsx': page,\n }\n : {\n 'pages/(docs)/docs/_layout.tsx': `import type { ReactNode } from 'react';\nimport { DocsLayout } from 'fumadocs-ui/layouts/docs';\nimport { source } from '@/lib/source';\nimport { baseOptions } from '@/lib/layout.shared';\n\nexport default function Layout({ children }: { children: ReactNode }) {\n return (\n <DocsLayout {...baseOptions()} tree={source.getPageTree()}>\n {children}\n </DocsLayout>\n );\n}\n`,\n 'pages/(docs)/docs/[...slug].tsx': page,\n }),\n 'pages/_api/api/search.ts': searchRoute.waku,\n };\n },\n};\n\nexport const nextProxy = `import { createI18nMiddleware } from 'fumadocs-core/i18n/middleware';\nimport { i18n } from '@/lib/i18n';\n\nexport default createI18nMiddleware(i18n);\n\nexport const config = {\n // only the docs routes, add the ones of other features here\n matcher: ['/docs/:path*', '/:lang/docs/:path*'],\n};\n`;\n\nexport const sampleContent = `---\ntitle: Hello World\ndescription: Your first document\n---\n\nWelcome to the docs! You can edit this page in \\`content/docs/index.mdx\\`.\n\n## Heading\n\n<Cards>\n <Card title=\"Learn more about Fumadocs\" href=\"https://fumadocs.dev\" />\n</Cards>\n`;\n\nexport const sampleContentCn = `---\ntitle: 你好,世界\ndescription: 你的第一份文檔\n---\n\n歡迎來到文檔!你可以在 \\`content/docs/index.cn.mdx\\` 編輯這個頁面。\n`;\n","import fs from 'node:fs/promises';\nimport path from 'node:path';\nimport type { Feature, FeatureContext } from '@/features';\nimport type { I18nInfo, Project } from '@/project';\nimport {\n addImport,\n addJsxAttribute,\n addVitePlugin,\n findJsxElement,\n wrapNextConfig,\n} from '@/codemod';\nimport { exists } from '@/utils/fs';\nimport { localeSegment } from '@/project/route';\nimport { nextProxy, sampleContent, sampleContentCn, templates } from './templates';\nimport { reactFramework, reactOnly, registerReactRouterRoutes } from '../utils';\n\nconst cssImports = (preset: string) => [\n `@import 'fumadocs-ui/css/${preset}.css';`,\n `@import 'fumadocs-ui/css/preset.css';`,\n];\n\nexport const docs: Feature<{ i18n: boolean }> = {\n id: 'docs',\n title: 'Docs',\n description: 'set up Fumadocs on your app, in a dedicated route group',\n supports: reactOnly,\n options: {\n i18n: { message: 'Enable internationalization?', initialValue: false },\n },\n async detect(project) {\n return exists(path.join(project.cwd, project.baseDir, 'lib/source.ts'));\n },\n async apply(ctx, options) {\n const { project } = ctx;\n const { baseDir, packageJson, config } = project;\n const framework = reactFramework(project);\n // Waku has no optional route segments, the locale is always in the URL\n const i18n = project.i18n ?? (options.i18n ? { optionalLocale: framework !== 'waku' } : null);\n const providerProps: string[] = [];\n if (project.static) providerProps.push('search={{ SearchDialog }}');\n if ('next-themes' in (packageJson.dependencies ?? {}))\n providerProps.push('theme={{ enabled: false }}');\n\n ctx.addDependencies({\n 'fumadocs-core': null,\n 'fumadocs-mdx': null,\n 'fumadocs-ui': config.uiLibrary === 'base-ui' ? 'npm:@fumadocs/base-ui' : null,\n });\n ctx.addDependencies({ '@types/mdx': null }, true);\n\n const files = templates[framework]({\n static: project.static,\n i18n,\n provider: project.info.provider,\n providerProps: providerProps.map((prop) => ` ${prop}`).join(''),\n });\n for (const [file, content] of Object.entries(files)) {\n await ctx.write(path.posix.join(baseDir, file), content);\n }\n await ctx.write('content/docs/index.mdx', sampleContent);\n if (i18n) await ctx.write('content/docs/index.cn.mdx', sampleContentCn);\n await ctx.append('.gitignore', ['.source']);\n if (i18n && framework === 'next') await configureProxy(ctx);\n\n await configureBundler(ctx);\n await configureCss(ctx);\n await suppressHydrationWarning(ctx);\n if (framework === 'react-router') await configureRoutes(ctx, i18n);\n\n const tsconfig = await fs\n .readFile(path.join(project.cwd, 'tsconfig.json'), 'utf-8')\n .catch(() => '');\n if (!tsconfig.includes('\"@/*\"')) {\n ctx.note(\n `Generated files import from the \\`@/*\\` alias, add it to \\`tsconfig.json\\`:\\n \"paths\": { \"@/*\": [\"./${baseDir ? `${baseDir}/` : ''}*\"] }`,\n );\n }\n if (project.static && framework === 'tanstack-start') {\n ctx.note(\n 'In SPA mode, prerender the docs pages and search index, see https://fumadocs.dev/docs/manual-installation/tanstack-start.',\n );\n }\n if (i18n && framework === 'react-router') {\n ctx.note(\n 'When prerendering, list the docs pages with `source.getPages().map((page) => page.url)` instead of globbing content files, see https://fumadocs.dev/docs/internationalization/react-router.',\n );\n }\n if (i18n) {\n ctx.note(\n `Languages are configured in \\`lib/i18n.ts\\`, translated content uses a locale suffix like \\`index.cn.mdx\\`.\\nUI translations: https://fumadocs.dev/docs/internationalization#translations`,\n );\n }\n ctx.note(\n `Start the dev server and open ${i18n && !i18n.optionalLocale ? '/en/docs' : '/docs'} to see your docs.`,\n );\n },\n};\n\n/** the Next.js proxy (middleware) file of the project, relative to cwd */\nexport async function findNextProxy({ cwd, baseDir }: Project) {\n for (const file of ['proxy.ts', 'middleware.ts']) {\n for (const dir of new Set([baseDir, ''])) {\n const target = path.join(dir, file);\n if (await exists(path.join(cwd, target))) return target;\n }\n }\n}\n\n/** the i18n middleware redirects & rewrites docs URLs without locale */\nasync function configureProxy(ctx: FeatureContext) {\n const existing = await findNextProxy(ctx.project);\n if (!existing) {\n await ctx.write(path.join(ctx.project.baseDir, 'proxy.ts'), nextProxy);\n return;\n }\n\n ctx.note(\n `A proxy already exists at ${existing}, handle the docs locale there. Fumadocs provides \\`createI18nMiddleware\\` from 'fumadocs-core/i18n/middleware', see https://fumadocs.dev/docs/internationalization/next.`,\n );\n}\n\nasync function configureBundler(ctx: FeatureContext) {\n const { framework, configFile } = ctx.project;\n const edited =\n configFile !== undefined &&\n (await ctx\n .source(configFile, (file) => {\n const ok =\n framework === 'next'\n ? wrapNextConfig(file)\n : addVitePlugin(\n file,\n { name: 'fumadocsMdx', from: 'fumadocs-mdx/vite' },\n framework === 'waku' ? ['vite', 'plugins'] : ['plugins'],\n );\n if (!ok) throw new Error(`cannot update ${configFile}`);\n })\n .catch(() => false));\n\n if (!edited) {\n ctx.note(\n framework === 'next'\n ? \"Wrap your Next.js config with `createMDX()` from 'fumadocs-mdx/next', see https://fumadocs.dev/docs/mdx/next.\"\n : \"Add `fumadocsMdx()` from 'fumadocs-mdx/vite' to the Vite plugins of your config file.\",\n );\n }\n}\n\n/** resolve the global CSS file imported by the root file */\nasync function findCssEntry({ cwd, baseDir, info }: Project): Promise<string | undefined> {\n const rootFile = path.join(baseDir, info.rootFile);\n const content = await fs.readFile(path.join(cwd, rootFile), 'utf-8').catch(() => '');\n const match = /import\\s+(?:\\w+\\s+from\\s+)?['\"]([^'\"]+\\.css)(?:\\?url)?['\"]/.exec(content);\n if (!match) return;\n const specifier = match[1];\n\n if (specifier.startsWith('@/')) return path.join(baseDir, specifier.slice(2));\n if (specifier.startsWith('.')) return path.join(path.dirname(rootFile), specifier);\n}\n\nasync function configureCss(ctx: FeatureContext) {\n const { cwd } = ctx.project;\n const preset = (await exists(path.join(cwd, 'components.json'))) ? 'shadcn' : 'neutral';\n const entry = await findCssEntry(ctx.project);\n if (entry && (await exists(path.join(cwd, entry)))) {\n await ctx.append(entry, cssImports(preset));\n return;\n }\n\n ctx.note(\n `Add the styles to your global CSS file (after \\`@import 'tailwindcss'\\`):\\n ${cssImports(preset).join('\\n ')}`,\n );\n}\n\n/** `next-themes` sets the theme class on `<html>`, React warns about the mismatch without this attribute */\nasync function suppressHydrationWarning(ctx: FeatureContext) {\n const { baseDir, info } = ctx.project;\n const edited = await ctx.source(path.join(baseDir, info.rootFile), (file) => {\n const html = findJsxElement(file, 'html')?.openingElement;\n if (!html || file.code.slice(html.start, html.end).includes('suppressHydrationWarning')) return;\n addJsxAttribute(file, html, 'suppressHydrationWarning');\n });\n\n if (!edited)\n ctx.note('Add `suppressHydrationWarning` to the `<html>` element of your root layout.');\n}\n\nasync function configureRoutes(ctx: FeatureContext, i18n: I18nInfo | null) {\n const added = await registerReactRouterRoutes(ctx, [\n `layout('routes/docs/layout.tsx', [route('${localeSegment('react-router', i18n)}docs/*', 'routes/docs/page.tsx')])`,\n { path: 'api/search', file: 'routes/docs/search.ts', pattern: '/api/search' },\n ]);\n if (!added) return;\n await ctx.source(path.join(ctx.project.baseDir, 'routes.ts'), (file) => {\n addImport(file, { from: '@react-router/dev/routes', named: ['layout', 'route'] });\n });\n}\n"],"mappings":"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"}
@@ -1,5 +1,5 @@
1
1
  import { i as addReactRouterPrerenderArray, n as enableProcessedMarkdown } from "./codemod-auol-p4R.js";
2
- import { a as routeModule, r as formatRoute, t as docs } from "./docs--WqEHTyl.js";
2
+ import { a as routeModule, r as formatRoute, t as docs } from "./docs-qRGwd9Yp.js";
3
3
  import { a as reactFramework, c as requiresMarkdown, d as sourceRef, s as registerReactRouterRoutes } from "./utils-DbMe7h6c.js";
4
4
  import path from "node:path";
5
5
  //#region src/features/epub.ts
@@ -49,4 +49,4 @@ const epub = {
49
49
  //#endregion
50
50
  export { epub as t };
51
51
 
52
- //# sourceMappingURL=epub-jc_2B886.js.map
52
+ //# sourceMappingURL=epub-DbOh5Q2D.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"epub-jc_2B886.js","names":[],"sources":["../src/features/epub.ts"],"sourcesContent":["import path from 'node:path';\nimport type { Feature } from '@/features';\nimport type { ReactFramework } from '@/project';\nimport { type FormattedRoute, formatRoute, routeModule } from '@/project/route';\nimport { addReactRouterPrerenderArray, enableProcessedMarkdown } from '@/codemod';\nimport { docs } from './docs';\nimport {\n reactFramework,\n registerReactRouterRoutes,\n requiresMarkdown,\n type SourceRef,\n sourceRef,\n} from './utils';\n\nconst api = ({ dynamic, ref }: SourceRef) => `import { ${ref} } from '@/lib/source';\nimport { createEpubExportAPI } from 'fumadocs-epub';\n\nconst api = createEpubExportAPI({\n ${ref === 'source' ? 'source' : `source: ${ref}`},${dynamic ? '\\n getMarkdown: (page) => page.data.content,' : ''}\n title: 'Documentation',\n author: 'Your Team',\n description: 'Exported documentation',\n // guards the route, requests must send \\`Authorization: Bearer <secret>\\`\n secret: process.env.EXPORT_SECRET,\n});\n`;\n\nconst templates = (framework: ReactFramework, route: FormattedRoute, src: SourceRef) =>\n routeModule(framework, route, {\n imports: api(src),\n body: 'api.GET(request)',\n request: true,\n revalidate: true,\n });\n\nexport const epub: Feature = {\n id: 'epub',\n title: 'EPUB Export',\n description: 'a route to export docs as an EPUB file, protected by EXPORT_SECRET',\n requires: [docs],\n supports: (project) =>\n project.static ? 'the export route needs a server at runtime' : requiresMarkdown(project),\n async apply(ctx) {\n const { baseDir, source } = ctx.project;\n const framework = reactFramework(ctx.project);\n ctx.addDependencies({ 'fumadocs-epub': null });\n\n if (source.collections) {\n await ctx.source(source.collections, (file) => {\n if (!enableProcessedMarkdown(file))\n throw new Error(`cannot find \\`defineDocs()\\` in ${source.collections}`);\n });\n }\n\n const route = formatRoute({ segments: ['export/epub'] }, framework, null);\n await ctx.write(\n path.join(baseDir, route.file),\n templates(framework, route, sourceRef(ctx.project.source.dynamic)),\n );\n if (framework === 'react-router') {\n await registerReactRouterRoutes(ctx, [route]);\n // the route needs a secret at runtime\n await ctx.source('react-router.config.ts', (file) => {\n addReactRouterPrerenderArray(file, 'excluded', [`/${route.path}`]);\n });\n }\n\n ctx.env('EXPORT_SECRET', '');\n ctx.note(\n 'Set EXPORT_SECRET in `.env.local`, then export with `fumadocs export epub` after a production build.',\n );\n },\n};\n"],"mappings":";;;;;AAcA,MAAM,OAAO,EAAE,SAAS,UAAqB,YAAY,IAAI;;;;IAIzD,QAAQ,WAAW,WAAW,WAAW,MAAM,GAAG,UAAU,kDAAkD,GAAG;;;;;;;;AASrH,MAAM,aAAa,WAA2B,OAAuB,QACnE,YAAY,WAAW,OAAO;CAC5B,SAAS,IAAI,GAAG;CAChB,MAAM;CACN,SAAS;CACT,YAAY;AACd,CAAC;AAEH,MAAa,OAAgB;CAC3B,IAAI;CACJ,OAAO;CACP,aAAa;CACb,UAAU,CAAC,IAAI;CACf,WAAW,YACT,QAAQ,SAAS,+CAA+C,iBAAiB,OAAO;CAC1F,MAAM,MAAM,KAAK;EACf,MAAM,EAAE,SAAS,WAAW,IAAI;EAChC,MAAM,YAAY,eAAe,IAAI,OAAO;EAC5C,IAAI,gBAAgB,EAAE,iBAAiB,KAAK,CAAC;EAE7C,IAAI,OAAO,aACT,MAAM,IAAI,OAAO,OAAO,cAAc,SAAS;GAC7C,IAAI,CAAC,wBAAwB,IAAI,GAC/B,MAAM,IAAI,MAAM,mCAAmC,OAAO,aAAa;EAC3E,CAAC;EAGH,MAAM,QAAQ,YAAY,EAAE,UAAU,CAAC,aAAa,EAAE,GAAG,WAAW,IAAI;EACxE,MAAM,IAAI,MACR,KAAK,KAAK,SAAS,MAAM,IAAI,GAC7B,UAAU,WAAW,OAAO,UAAU,IAAI,QAAQ,OAAO,OAAO,CAAC,CACnE;EACA,IAAI,cAAc,gBAAgB;GAChC,MAAM,0BAA0B,KAAK,CAAC,KAAK,CAAC;GAE5C,MAAM,IAAI,OAAO,2BAA2B,SAAS;IACnD,6BAA6B,MAAM,YAAY,CAAC,IAAI,MAAM,MAAM,CAAC;GACnE,CAAC;EACH;EAEA,IAAI,IAAI,iBAAiB,EAAE;EAC3B,IAAI,KACF,sGACF;CACF;AACF"}
1
+ {"version":3,"file":"epub-DbOh5Q2D.js","names":[],"sources":["../src/features/epub.ts"],"sourcesContent":["import path from 'node:path';\nimport type { Feature } from '@/features';\nimport type { ReactFramework } from '@/project';\nimport { type FormattedRoute, formatRoute, routeModule } from '@/project/route';\nimport { addReactRouterPrerenderArray, enableProcessedMarkdown } from '@/codemod';\nimport { docs } from './docs';\nimport {\n reactFramework,\n registerReactRouterRoutes,\n requiresMarkdown,\n type SourceRef,\n sourceRef,\n} from './utils';\n\nconst api = ({ dynamic, ref }: SourceRef) => `import { ${ref} } from '@/lib/source';\nimport { createEpubExportAPI } from 'fumadocs-epub';\n\nconst api = createEpubExportAPI({\n ${ref === 'source' ? 'source' : `source: ${ref}`},${dynamic ? '\\n getMarkdown: (page) => page.data.content,' : ''}\n title: 'Documentation',\n author: 'Your Team',\n description: 'Exported documentation',\n // guards the route, requests must send \\`Authorization: Bearer <secret>\\`\n secret: process.env.EXPORT_SECRET,\n});\n`;\n\nconst templates = (framework: ReactFramework, route: FormattedRoute, src: SourceRef) =>\n routeModule(framework, route, {\n imports: api(src),\n body: 'api.GET(request)',\n request: true,\n revalidate: true,\n });\n\nexport const epub: Feature = {\n id: 'epub',\n title: 'EPUB Export',\n description: 'a route to export docs as an EPUB file, protected by EXPORT_SECRET',\n requires: [docs],\n supports: (project) =>\n project.static ? 'the export route needs a server at runtime' : requiresMarkdown(project),\n async apply(ctx) {\n const { baseDir, source } = ctx.project;\n const framework = reactFramework(ctx.project);\n ctx.addDependencies({ 'fumadocs-epub': null });\n\n if (source.collections) {\n await ctx.source(source.collections, (file) => {\n if (!enableProcessedMarkdown(file))\n throw new Error(`cannot find \\`defineDocs()\\` in ${source.collections}`);\n });\n }\n\n const route = formatRoute({ segments: ['export/epub'] }, framework, null);\n await ctx.write(\n path.join(baseDir, route.file),\n templates(framework, route, sourceRef(ctx.project.source.dynamic)),\n );\n if (framework === 'react-router') {\n await registerReactRouterRoutes(ctx, [route]);\n // the route needs a secret at runtime\n await ctx.source('react-router.config.ts', (file) => {\n addReactRouterPrerenderArray(file, 'excluded', [`/${route.path}`]);\n });\n }\n\n ctx.env('EXPORT_SECRET', '');\n ctx.note(\n 'Set EXPORT_SECRET in `.env.local`, then export with `fumadocs export epub` after a production build.',\n );\n },\n};\n"],"mappings":";;;;;AAcA,MAAM,OAAO,EAAE,SAAS,UAAqB,YAAY,IAAI;;;;IAIzD,QAAQ,WAAW,WAAW,WAAW,MAAM,GAAG,UAAU,kDAAkD,GAAG;;;;;;;;AASrH,MAAM,aAAa,WAA2B,OAAuB,QACnE,YAAY,WAAW,OAAO;CAC5B,SAAS,IAAI,GAAG;CAChB,MAAM;CACN,SAAS;CACT,YAAY;AACd,CAAC;AAEH,MAAa,OAAgB;CAC3B,IAAI;CACJ,OAAO;CACP,aAAa;CACb,UAAU,CAAC,IAAI;CACf,WAAW,YACT,QAAQ,SAAS,+CAA+C,iBAAiB,OAAO;CAC1F,MAAM,MAAM,KAAK;EACf,MAAM,EAAE,SAAS,WAAW,IAAI;EAChC,MAAM,YAAY,eAAe,IAAI,OAAO;EAC5C,IAAI,gBAAgB,EAAE,iBAAiB,KAAK,CAAC;EAE7C,IAAI,OAAO,aACT,MAAM,IAAI,OAAO,OAAO,cAAc,SAAS;GAC7C,IAAI,CAAC,wBAAwB,IAAI,GAC/B,MAAM,IAAI,MAAM,mCAAmC,OAAO,aAAa;EAC3E,CAAC;EAGH,MAAM,QAAQ,YAAY,EAAE,UAAU,CAAC,aAAa,EAAE,GAAG,WAAW,IAAI;EACxE,MAAM,IAAI,MACR,KAAK,KAAK,SAAS,MAAM,IAAI,GAC7B,UAAU,WAAW,OAAO,UAAU,IAAI,QAAQ,OAAO,OAAO,CAAC,CACnE;EACA,IAAI,cAAc,gBAAgB;GAChC,MAAM,0BAA0B,KAAK,CAAC,KAAK,CAAC;GAE5C,MAAM,IAAI,OAAO,2BAA2B,SAAS;IACnD,6BAA6B,MAAM,YAAY,CAAC,IAAI,MAAM,MAAM,CAAC;GACnE,CAAC;EACH;EAEA,IAAI,IAAI,iBAAiB,EAAE;EAC3B,IAAI,KACF,sGACF;CACF;AACF"}
@@ -1,2 +1,2 @@
1
- import { t as ai } from "../ai-BAvzj17c.js";
1
+ import { t as ai } from "../ai-BoUGzPvv.js";
2
2
  export { ai };
@@ -1,2 +1,2 @@
1
- import { t as features } from "../all-4XIIhn0t.js";
1
+ import { t as features } from "../all-BmTB7dab.js";
2
2
  export { features };
@@ -1,2 +1,2 @@
1
- import { n as findNextProxy, t as docs } from "../../docs--WqEHTyl.js";
1
+ import { n as findNextProxy, t as docs } from "../../docs-qRGwd9Yp.js";
2
2
  export { docs, findNextProxy };
@@ -1,2 +1,2 @@
1
- import { t as epub } from "../epub-jc_2B886.js";
1
+ import { t as epub } from "../epub-DbOh5Q2D.js";
2
2
  export { epub };
@@ -1,2 +1,2 @@
1
- import { t as feedback } from "../feedback-Brj092vC.js";
1
+ import { t as feedback } from "../feedback-DpXYMKtX.js";
2
2
  export { feedback };
@@ -1,2 +1,2 @@
1
- import { a as markdownRewrite, c as templates, i as llmsRoutes, n as extendNextProxy, o as markdownUrl, r as llms, s as tanstackMarkdownUrl, t as docsLlms } from "../llms-Br0RJXXg.js";
1
+ import { a as markdownRewrite, c as templates, i as llmsRoutes, n as extendNextProxy, o as markdownUrl, r as llms, s as tanstackMarkdownUrl, t as docsLlms } from "../llms-DQzAst6R.js";
2
2
  export { docsLlms, extendNextProxy, llms, llmsRoutes, markdownRewrite, markdownUrl, tanstackMarkdownUrl, templates };
@@ -1,2 +1,2 @@
1
- import { n as mcpRoute, r as templates, t as mcp } from "../mcp-Ce_W93we.js";
1
+ import { n as mcpRoute, r as templates, t as mcp } from "../mcp-BpFZ_fdV.js";
2
2
  export { mcp, mcpRoute, templates };
@@ -1,2 +1,2 @@
1
- import { t as og } from "../og-LFlX1Bl4.js";
1
+ import { t as og } from "../og-m0CwRlRz.js";
2
2
  export { og };
@@ -25,7 +25,6 @@ declare const providers: {
25
25
  documents: string;
26
26
  };
27
27
  sync: (env: Env, { output }: {
28
- dir: string;
29
28
  output: string;
30
29
  }) => string;
31
30
  };
@@ -44,7 +43,6 @@ declare const providers: {
44
43
  documents: string;
45
44
  };
46
45
  sync: (env: Env, { output }: {
47
- dir: string;
48
46
  output: string;
49
47
  }) => string;
50
48
  };
@@ -63,7 +61,6 @@ declare const providers: {
63
61
  documents: string;
64
62
  };
65
63
  sync: (env: Env, { output }: {
66
- dir: string;
67
64
  output: string;
68
65
  }) => string;
69
66
  };
@@ -83,7 +80,6 @@ declare const providers: {
83
80
  documents: string;
84
81
  };
85
82
  sync: (env: Env, { output }: {
86
- dir: string;
87
83
  output: string;
88
84
  }) => string;
89
85
  };
@@ -97,8 +93,11 @@ declare const providers: {
97
93
  privateEnv: string[];
98
94
  static: false;
99
95
  dialog: () => string;
100
- sync: (_env: Env, { dir }: {
101
- dir: string;
96
+ exportIndexes: ({ ref }: SourceRef) => {
97
+ head: string;
98
+ documents: string;
99
+ };
100
+ sync: (_env: Env, { output }: {
102
101
  output: string;
103
102
  }) => string;
104
103
  searchRoute: {
@@ -120,7 +119,7 @@ export declare const syncCommand = "node --env-file-if-exists=.env.local scripts
120
119
  /** the files written by a provider, relative to the project, `output` is where `static.json` is pre-rendered */
121
120
  export declare function templates(name: SearchProvider, framework: ReactFramework, { baseDir, source }: {
122
121
  baseDir: string;
123
- source: Pick<SourceInfo, 'dynamic' | 'async' | 'dir'>;
122
+ source: Pick<SourceInfo, 'dynamic' | 'async'>;
124
123
  }, output: string): [file: string, content: string][];
125
124
  export declare const search: Feature<{
126
125
  provider: SearchProvider;
@@ -1 +1 @@
1
- {"version":3,"file":"search.d.ts","names":["_$__project_route0.FormattedRoute"],"sources":["../../src/features/search.ts"],"mappings":";;;;;UAyBU;EACR;EACA;;;qBAsEW,aAAU,gBACP,iBACC;cAqCX;;;;;MAIc;;;;;;;;;;;MATc;MAAgB;;;;;;;MAyD9B;;;;;;;;;;;MAzDc;MAAgB;;;;;;;MAyG9B;;;;;;;;;;;MAzGc;MAAgB;;;;;;;MA4J9B;MAAiB;;;;;;;;;;;MA5JH;MAAgB;;;;;;;MA4M9B;;;;;;;MA5Mc;MAAgB;;;;;;;;;;YAiRtC,8BAA8B;;qBAG7B,aAAU,WAAe,mBAAAA;qBAGzB,cAAW,WAAe,gBAAc,cAAc;;wBASnD,aAAa,WAAW,gBAAgB;;qBAgB3C;;wBAGG,UACd,MAAM,gBACN,WAAW,kBACT,SAAS;EAAY;EAAiB,QAAQ,KAAK;GACrD,kBACE,cAAc;qBAmBL,QAAQ;EAAU,UAAU"}
1
+ {"version":3,"file":"search.d.ts","names":["_$__project_route0.FormattedRoute"],"sources":["../../src/features/search.ts"],"mappings":";;;;;UAyBU;EACR;EACA;;;qBAuEW,aAAU,gBACP,iBACC;cAqCX;;;;;MAIc;;;;;;;;;;;MATiB;;;;;;;MAwDjB;;;;;;;;;;;MAxDiB;;;;;;;MAqGjB;;;;;;;;;;;MArGiB;;;;;;;MAqJjB;MAAiB;;;;;;;;;;;MArJA;;;;;;;MAqMjB;;;;;;;;;;;MArMiB;;;;;;;;;;YA4QzB,8BAA8B;;qBAG7B,aAAU,WAAe,mBAAAA;qBAGzB,cAAW,WAAe,gBAAc,cAAc;;wBASnD,aAAa,WAAW,gBAAgB;;qBAgB3C;;wBAGG,UACd,MAAM,gBACN,WAAW,kBACT,SAAS;EAAY;EAAiB,QAAQ,KAAK;GACrD,kBACE,cAAc;qBAkBL,QAAQ;EAAU,UAAU"}
@@ -1,2 +1,2 @@
1
- import { a as syncCommand, i as staticRoute, n as staticJson, o as syncScript, r as staticOutput, s as templates, t as search } from "../search-BbzvfJal.js";
1
+ import { a as syncCommand, i as staticRoute, n as staticJson, o as syncScript, r as staticOutput, s as templates, t as search } from "../search-BCHEW0-K.js";
2
2
  export { search, staticJson, staticOutput, staticRoute, syncCommand, syncScript, templates };
@@ -1,2 +1,2 @@
1
- import { n as webmcp, t as component } from "../webmcp-DyTLrsXL.js";
1
+ import { n as webmcp, t as component } from "../webmcp-I9L7xzmw.js";
2
2
  export { component, webmcp };
@@ -1,5 +1,5 @@
1
1
  import { b as findJsxElement, h as appendJsxChildren, p as addImport } from "./codemod-auol-p4R.js";
2
- import { t as docs } from "./docs--WqEHTyl.js";
2
+ import { t as docs } from "./docs-qRGwd9Yp.js";
3
3
  import { r as findSource } from "./utils-DbMe7h6c.js";
4
4
  import path from "node:path";
5
5
  //#region src/features/feedback.ts
@@ -34,4 +34,4 @@ const feedback = {
34
34
  //#endregion
35
35
  export { feedback as t };
36
36
 
37
- //# sourceMappingURL=feedback-Brj092vC.js.map
37
+ //# sourceMappingURL=feedback-DpXYMKtX.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"feedback-Brj092vC.js","names":[],"sources":["../src/features/feedback.ts"],"sourcesContent":["import path from 'node:path';\nimport type { Feature } from '@/features';\nimport { addImport, appendJsxChildren, findJsxElement } from '@/codemod';\nimport { docs } from './docs';\nimport { findSource } from './utils';\n\nexport const feedback: Feature = {\n id: 'feedback',\n title: 'Feedback',\n description: 'let readers rate docs pages, with a component to send feedback',\n requires: [docs],\n async apply(ctx) {\n const { cwd, baseDir, info, framework, static: isStatic } = ctx.project;\n await ctx.install('feedback');\n\n // server actions are only available on RSC frameworks, static export has none\n const serverAction = framework === 'next' || framework === 'waku';\n const page = await findSource(cwd, path.join(baseDir, info.routesDir), '<DocsPage');\n const edited =\n page !== undefined &&\n !(serverAction && isStatic) &&\n (await ctx.source(page, (file) => {\n if (file.code.includes('<Feedback')) return;\n const element = findJsxElement(file, 'DocsPage');\n if (!element) return;\n\n appendJsxChildren(\n file,\n element,\n `<Feedback\n onSendAction={async (feedback) => {${serverAction ? \"\\n 'use server';\\n\" : ''}\n console.log(feedback);\n return {};\n }}\n/>`,\n );\n addImport(file, { from: '@/components/feedback/client', named: ['Feedback'] });\n }));\n\n if (!edited) {\n ctx.note(\n serverAction && isStatic\n ? 'Static export has no server actions, render `<Feedback />` from `@/components/feedback/client` in a client component and send the feedback to your API in `onSendAction`.'\n : 'Add `<Feedback />` from `@/components/feedback/client` to the bottom of your docs page.',\n );\n }\n ctx.note(\n 'Handle the feedback in `onSendAction`, e.g. report it to GitHub Discussions: https://fumadocs.dev/docs/integrations/feedback.',\n );\n },\n};\n"],"mappings":";;;;;AAMA,MAAa,WAAoB;CAC/B,IAAI;CACJ,OAAO;CACP,aAAa;CACb,UAAU,CAAC,IAAI;CACf,MAAM,MAAM,KAAK;EACf,MAAM,EAAE,KAAK,SAAS,MAAM,WAAW,QAAQ,aAAa,IAAI;EAChE,MAAM,IAAI,QAAQ,UAAU;EAG5B,MAAM,eAAe,cAAc,UAAU,cAAc;EAC3D,MAAM,OAAO,MAAM,WAAW,KAAK,KAAK,KAAK,SAAS,KAAK,SAAS,GAAG,WAAW;EAsBlF,IAAI,EApBF,SAAS,KAAA,KACT,EAAE,gBAAgB,aACjB,MAAM,IAAI,OAAO,OAAO,SAAS;GAChC,IAAI,KAAK,KAAK,SAAS,WAAW,GAAG;GACrC,MAAM,UAAU,eAAe,MAAM,UAAU;GAC/C,IAAI,CAAC,SAAS;GAEd,kBACE,MACA,SACA;uCAC6B,eAAe,0BAA0B,GAAG;;;;GAK3E;GACA,UAAU,MAAM;IAAE,MAAM;IAAgC,OAAO,CAAC,UAAU;GAAE,CAAC;EAC/E,CAAC,IAGD,IAAI,KACF,gBAAgB,WACZ,8KACA,yFACN;EAEF,IAAI,KACF,+HACF;CACF;AACF"}
1
+ {"version":3,"file":"feedback-DpXYMKtX.js","names":[],"sources":["../src/features/feedback.ts"],"sourcesContent":["import path from 'node:path';\nimport type { Feature } from '@/features';\nimport { addImport, appendJsxChildren, findJsxElement } from '@/codemod';\nimport { docs } from './docs';\nimport { findSource } from './utils';\n\nexport const feedback: Feature = {\n id: 'feedback',\n title: 'Feedback',\n description: 'let readers rate docs pages, with a component to send feedback',\n requires: [docs],\n async apply(ctx) {\n const { cwd, baseDir, info, framework, static: isStatic } = ctx.project;\n await ctx.install('feedback');\n\n // server actions are only available on RSC frameworks, static export has none\n const serverAction = framework === 'next' || framework === 'waku';\n const page = await findSource(cwd, path.join(baseDir, info.routesDir), '<DocsPage');\n const edited =\n page !== undefined &&\n !(serverAction && isStatic) &&\n (await ctx.source(page, (file) => {\n if (file.code.includes('<Feedback')) return;\n const element = findJsxElement(file, 'DocsPage');\n if (!element) return;\n\n appendJsxChildren(\n file,\n element,\n `<Feedback\n onSendAction={async (feedback) => {${serverAction ? \"\\n 'use server';\\n\" : ''}\n console.log(feedback);\n return {};\n }}\n/>`,\n );\n addImport(file, { from: '@/components/feedback/client', named: ['Feedback'] });\n }));\n\n if (!edited) {\n ctx.note(\n serverAction && isStatic\n ? 'Static export has no server actions, render `<Feedback />` from `@/components/feedback/client` in a client component and send the feedback to your API in `onSendAction`.'\n : 'Add `<Feedback />` from `@/components/feedback/client` to the bottom of your docs page.',\n );\n }\n ctx.note(\n 'Handle the feedback in `onSendAction`, e.g. report it to GitHub Discussions: https://fumadocs.dev/docs/integrations/feedback.',\n );\n },\n};\n"],"mappings":";;;;;AAMA,MAAa,WAAoB;CAC/B,IAAI;CACJ,OAAO;CACP,aAAa;CACb,UAAU,CAAC,IAAI;CACf,MAAM,MAAM,KAAK;EACf,MAAM,EAAE,KAAK,SAAS,MAAM,WAAW,QAAQ,aAAa,IAAI;EAChE,MAAM,IAAI,QAAQ,UAAU;EAG5B,MAAM,eAAe,cAAc,UAAU,cAAc;EAC3D,MAAM,OAAO,MAAM,WAAW,KAAK,KAAK,KAAK,SAAS,KAAK,SAAS,GAAG,WAAW;EAsBlF,IAAI,EApBF,SAAS,KAAA,KACT,EAAE,gBAAgB,aACjB,MAAM,IAAI,OAAO,OAAO,SAAS;GAChC,IAAI,KAAK,KAAK,SAAS,WAAW,GAAG;GACrC,MAAM,UAAU,eAAe,MAAM,UAAU;GAC/C,IAAI,CAAC,SAAS;GAEd,kBACE,MACA,SACA;uCAC6B,eAAe,0BAA0B,GAAG;;;;GAK3E;GACA,UAAU,MAAM;IAAE,MAAM;IAAgC,OAAO,CAAC,UAAU;GAAE,CAAC;EAC/E,CAAC,IAGD,IAAI,KACF,gBAAgB,WACZ,8KACA,yFACN;EAEF,IAAI,KACF,+HACF;CACF;AACF"}
package/dist/index.js CHANGED
@@ -5,7 +5,7 @@ import { t as exists } from "./fs-D6J9dOEx.js";
5
5
  import { i as loadProject, n as frameworks } from "./project-DwRVTm4p.js";
6
6
  import { n as UIRegistries, t as FumadocsComponentInstaller } from "./installer-C4662Woc.js";
7
7
  import { t as runFeature } from "./features-Do6x9Tsz.js";
8
- import { t as features } from "./all-4XIIhn0t.js";
8
+ import { t as features } from "./all-BmTB7dab.js";
9
9
  import fs from "node:fs/promises";
10
10
  import path from "node:path";
11
11
  import { cac } from "cac";
@@ -66,7 +66,7 @@ async function runTree(args) {
66
66
  }
67
67
  //#endregion
68
68
  //#region package.json
69
- var version = "1.7.3";
69
+ var version = "1.7.4";
70
70
  //#endregion
71
71
  //#region src/commands/customise.ts
72
72
  async function customise(config, connector) {
@@ -1,5 +1,5 @@
1
1
  import { c as addNextRewrites, n as enableProcessedMarkdown, p as addImport, r as addTanstackPrerender, t as addProxyMatcher } from "./codemod-auol-p4R.js";
2
- import { a as routeModule, i as reactRouterTypes, n as findNextProxy, r as formatRoute, t as docs } from "./docs--WqEHTyl.js";
2
+ import { a as routeModule, i as reactRouterTypes, n as findNextProxy, r as formatRoute, t as docs } from "./docs-qRGwd9Yp.js";
3
3
  import { a as reactFramework, c as requiresMarkdown, d as sourceRef, n as addPageUrl, s as registerReactRouterRoutes, t as addExport, u as sharedRoute } from "./utils-DbMe7h6c.js";
4
4
  import path from "node:path";
5
5
  //#region src/features/llms.ts
@@ -225,4 +225,4 @@ async function extendNextProxy(ctx, route) {
225
225
  //#endregion
226
226
  export { markdownRewrite as a, templates as c, llmsRoutes as i, extendNextProxy as n, markdownUrl as o, llms as r, tanstackMarkdownUrl as s, docsLlms as t };
227
227
 
228
- //# sourceMappingURL=llms-Br0RJXXg.js.map
228
+ //# sourceMappingURL=llms-DQzAst6R.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"llms-Br0RJXXg.js","names":[],"sources":["../src/features/llms.ts"],"sourcesContent":["import path from 'node:path';\nimport type { Feature, FeatureContext } from '@/features';\nimport type { I18nInfo, ReactFramework } from '@/project';\nimport {\n type FormattedRoute,\n formatRoute,\n reactRouterTypes,\n routeModule,\n type RouteDescriptor,\n} from '@/project/route';\nimport {\n addImport,\n addNextRewrites,\n addProxyMatcher,\n addTanstackPrerender,\n enableProcessedMarkdown,\n} from '@/codemod';\nimport { docs, findNextProxy } from './docs';\nimport {\n addExport,\n addPageUrl,\n reactFramework,\n type SourceRef,\n sourceRef,\n registerReactRouterRoutes,\n requiresMarkdown,\n sharedRoute,\n} from './utils';\n\n/** runtime content sources expose raw Markdown as `page.data.content`, Fumadocs MDX processes it */\nexport const docsLlms = ({ dynamic, ref }: SourceRef) => `\nexport const docsLlms = llms(${ref}, {\n renderPage: ${dynamic ? '(page)' : 'async (page)'} => \\`# \\${page.data.title} (\\${page.url})\n\n\\${${dynamic ? 'page.data.content' : \"await page.data.getText('processed')\"}}\\`,\n});`;\n\n/** the Markdown URL of a page, `/llms.mdx/docs/<slugs>/content.md` */\nexport const markdownUrl = (i18n: boolean) => `\nconst getContentUrl = createGetUrl(docsContentRoute${i18n ? ', i18n' : ''});\n\nexport function getPageMarkdownUrl(page: { slugs: string[]; locale?: string }) {\n const segments = [...page.slugs, 'content.md'];\n\n return { segments, url: getContentUrl(segments, page.locale) };\n}`;\n\n/** TanStack Start serves Markdown with a `.md` suffix under the docs route */\nexport const tanstackMarkdownUrl = (i18n: boolean) => `\nconst getDocsUrl = createGetUrl(docsRoute${i18n ? ', i18n' : ''});\n\nexport function getPageMarkdownUrl(page: { slugs: string[]; locale?: string }) {\n const segments = [...page.slugs];\n if (segments.length === 0) {\n segments.push('index.md');\n } else {\n segments[segments.length - 1] += '.md';\n }\n\n return { segments, url: getDocsUrl(segments, page.locale) };\n}\n\n/** @returns page slugs */\nexport function decodeMarkdownUrl(segments: string[]) {\n if (segments.length === 0) return [];\n\n const out = [...segments];\n out[out.length - 1] = out[out.length - 1].replace(/\\\\.md$/, '');\n if (out.length === 1 && out[0] === 'index') out.pop();\n return out;\n}`;\n\nconst imports = `import { docsLlms } from '@/lib/source';`;\nconst pageImports = ({ ref }: SourceRef) => `import { docsLlms, ${ref} } from '@/lib/source';`;\nconst markdown = `new Response(await docsLlms.page(page), {\n headers: {\n 'Content-Type': 'text/markdown',\n },\n })`;\n\ninterface Routes {\n index: FormattedRoute;\n full: FormattedRoute;\n markdown: FormattedRoute;\n}\n\ntype Template = (route: FormattedRoute, i18n: boolean, src: SourceRef) => string;\n\n/** the per-page Markdown route, the only one reading route params */\nconst markdownTemplates: Record<ReactFramework, Template> = {\n next: (route, i18n, src) => `${pageImports(src)}\nimport { notFound } from 'next/navigation';\n\nexport const revalidate = false;\n\nexport async function GET(_req: Request, { params }: RouteContext<'${route.path}'>) {\n const { slug${i18n ? ', lang' : ''} } = await params;\n // remove the appended \"content.md\", \\`/docs/index.md\\` is rewritten to the root page\n const slugs = slug?.slice(0, -1) ?? [];\n if (slugs.at(-1) === 'index') slugs.pop();\n const page = ${src.resolved}.getPage(slugs${i18n ? ', lang' : ''});\n if (!page) notFound();\n\n return ${markdown};\n}\n\nexport ${src.dynamic ? 'async ' : ''}function generateStaticParams() {\n return ${src.resolved}.generateParams().map((item) => ({\n ...item,\n slug: [...item.slug, 'content.md'],\n }));\n}\n`,\n 'react-router': (route, i18n, src) => `${reactRouterTypes(route)}\n${pageImports(src)}\n\nexport async function loader({ params }: Route.LoaderArgs) {\n const slugs = params['*'].split('/').filter((v) => v.length > 0);\n // remove the appended \"content.md\"\n slugs.pop();\n const page = ${src.resolved}.getPage(slugs${i18n ? ', params.lang' : ''});\n if (!page) return new Response('not found', { status: 404 });\n\n return ${markdown};\n}\n`,\n 'tanstack-start': (\n route,\n i18n,\n src,\n ) => `import { createFileRoute, notFound } from '@tanstack/react-router';\nimport { decodeMarkdownUrl } from '@/lib/shared';\n${pageImports(src)}\n\nexport const Route = createFileRoute('${route.path}')({\n server: {\n handlers: {\n GET: async ({ params }) => {\n const slugs = decodeMarkdownUrl(params._splat?.split('/') ?? []);\n const page = ${src.resolved}.getPage(slugs${i18n ? ', params.lang' : ''});\n if (!page) throw notFound();\n\n return ${markdown.replaceAll('\\n ', '\\n ')};\n },\n },\n },\n});\n`,\n waku: (route, i18n, src) => `${pageImports(src)}\nimport type { ApiContext } from 'waku/router';\nimport { unstable_notFound } from 'waku/router/server';\n\nexport async function GET(_: Request, { params }: ApiContext<'${route.path}'>) {\n const page = ${src.resolved}.getPage(params.slug${i18n ? ', params.lang' : ''});\n if (!page) unstable_notFound();\n\n return ${markdown};\n}\n\nexport async function getConfig() {\n return {\n render: 'static' as const,\n staticPaths: ${src.resolved}.generateParams().map((item) => ${i18n ? '[item.lang, ...item.slug]' : 'item.slug'}),\n } as const;\n}\n`,\n};\n\nexport function templates(\n framework: ReactFramework,\n routes: Routes,\n i18n: boolean,\n src: SourceRef,\n): [route: FormattedRoute, content: string][] {\n const index = (body: string) => ({ imports, body, revalidate: true, static: true });\n\n return [\n [\n routes.index,\n routeModule(framework, routes.index, index('new Response(await docsLlms.index())')),\n ],\n [\n routes.full,\n routeModule(framework, routes.full, index('new Response(await docsLlms.full())')),\n ],\n [routes.markdown, markdownTemplates[framework](routes.markdown, i18n, src)],\n ];\n}\n\nexport function llmsRoutes(\n framework: ReactFramework,\n i18n: I18nInfo | null,\n docsRoute: string,\n contentRoute: string,\n): Routes {\n const format = (route: RouteDescriptor) => formatRoute(route, framework, i18n);\n return {\n index: format({ segments: ['llms.txt'] }),\n full: format({ segments: ['llms-full.txt'] }),\n markdown: format(\n framework === 'tanstack-start'\n ? { segments: [docsRoute, '{$}.md'], locale: true }\n : {\n segments: [\n contentRoute,\n { param: 'slug', catchAll: true, optional: true, suffix: 'content.md' },\n ],\n locale: true,\n },\n ),\n };\n}\n\nexport const llms: Feature = {\n id: 'llms',\n title: 'LLM Routes',\n description: 'serve docs as Markdown for LLMs: llms.txt, llms-full.txt and per-page Markdown',\n requires: [docs],\n supports: requiresMarkdown,\n async apply(ctx) {\n const { baseDir, i18n, source, configFile } = ctx.project;\n const framework = reactFramework(ctx.project);\n const sourceFile = path.join(baseDir, 'lib/source.ts');\n const src = sourceRef(ctx.project.source.dynamic);\n // runtime content sources already give raw Markdown\n if (source.collections) {\n await ctx.source(source.collections, (file) => {\n if (!enableProcessedMarkdown(file))\n throw new Error(`cannot find \\`defineDocs()\\` in ${source.collections}`);\n });\n }\n if (await addExport(ctx, sourceFile, 'docsLlms', docsLlms(src))) {\n await ctx.source(sourceFile, (file) =>\n addImport(file, { from: 'fumadocs-core/source', named: ['llms'] }),\n );\n }\n\n const docsRoute = source.baseUrl.replace(/\\/$/, '');\n let contentRoute = `/llms.mdx${docsRoute}`;\n if (framework === 'tanstack-start') {\n await sharedRoute(ctx, 'docsRoute', source.baseUrl);\n await addPageUrl(ctx, 'getPageMarkdownUrl', tanstackMarkdownUrl(i18n !== null));\n } else {\n contentRoute = await sharedRoute(ctx, 'docsContentRoute', contentRoute);\n await addPageUrl(ctx, 'getPageMarkdownUrl', markdownUrl(i18n !== null));\n }\n const routes = llmsRoutes(framework, i18n, docsRoute, contentRoute);\n for (const [route, content] of templates(framework, routes, i18n !== null, src))\n await ctx.write(path.join(baseDir, route.file), content);\n\n if (framework === 'react-router') {\n await registerReactRouterRoutes(ctx, [routes.index, routes.full, routes.markdown]);\n } else if (framework === 'tanstack-start' && ctx.project.static && configFile) {\n // SPA mode only serves prerendered routes\n await ctx.source(configFile, (file) =>\n addTanstackPrerender(file, [routes.index.path, routes.full.path]),\n );\n } else if (framework === 'next') {\n if (i18n) await extendNextProxy(ctx, routes.markdown);\n await configureMarkdownUrl(ctx, docsRoute, routes.markdown);\n }\n if (ctx.project.static && framework !== 'next') {\n ctx.note(\n 'Per-page Markdown routes are dynamic, add the pages to your prerender config to serve them statically.',\n );\n }\n if (framework === 'react-router' || framework === 'waku') {\n ctx.note(\n 'To serve pages with a `.md` suffix, rewrite `/docs/*.md` to the `llms.mdx` route in a middleware, see https://fumadocs.dev/docs/integrations/llms#md-extension.',\n );\n }\n ctx.note(\n \"Add page actions to your docs page with `MarkdownCopyButton` and `ViewOptionsPopover` from 'fumadocs-ui/layouts/docs/page', the Markdown URL of a page is `getPageMarkdownUrl(page).url` from '@/lib/shared'.\",\n );\n },\n};\n\n/** `/docs/*.md` serves the Markdown of a page, like TanStack Start's `.md` route */\nexport const markdownRewrite = (docsRoute: string, markdown: FormattedRoute, i18n: boolean) => ({\n source: `${i18n ? '/:lang' : ''}${docsRoute}/:slug*.md`,\n destination: `${markdown.pattern}/content.md`,\n});\n\nasync function configureMarkdownUrl(\n ctx: FeatureContext,\n docsRoute: string,\n markdown: FormattedRoute,\n) {\n const { configFile, i18n } = ctx.project;\n const rewrite = markdownRewrite(docsRoute, markdown, i18n !== null);\n const edited =\n configFile !== undefined &&\n (await ctx\n .source(configFile, (file) => {\n if (file.code.includes('llms.mdx')) return;\n if (!addNextRewrites(file, [rewrite])) throw new Error('cannot add rewrites');\n })\n .catch(() => false));\n\n if (!edited) {\n ctx.note(\n `Rewrite \\`${rewrite.source}\\` to \\`${rewrite.destination}\\` in your Next.js config to serve pages with a \\`.md\\` suffix.`,\n );\n }\n}\n\n/** routes under `[lang]` need the i18n middleware to resolve the locale */\nexport async function extendNextProxy(ctx: FeatureContext, route: FormattedRoute) {\n const patterns = [route.pattern.replace('/:lang', ''), route.pattern];\n const proxy = await findNextProxy(ctx.project);\n const edited =\n proxy !== undefined &&\n (await ctx\n .source(proxy, (file) => {\n if (!addProxyMatcher(file, patterns)) throw new Error('cannot find the matcher');\n })\n .catch(() => false));\n if (!edited)\n ctx.note(\n `Add ${patterns.map((p) => `\\`${p}\\``).join(', ')} to the matcher of your i18n middleware.`,\n );\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"llms-DQzAst6R.js","names":[],"sources":["../src/features/llms.ts"],"sourcesContent":["import path from 'node:path';\nimport type { Feature, FeatureContext } from '@/features';\nimport type { I18nInfo, ReactFramework } from '@/project';\nimport {\n type FormattedRoute,\n formatRoute,\n reactRouterTypes,\n routeModule,\n type RouteDescriptor,\n} from '@/project/route';\nimport {\n addImport,\n addNextRewrites,\n addProxyMatcher,\n addTanstackPrerender,\n enableProcessedMarkdown,\n} from '@/codemod';\nimport { docs, findNextProxy } from './docs';\nimport {\n addExport,\n addPageUrl,\n reactFramework,\n type SourceRef,\n sourceRef,\n registerReactRouterRoutes,\n requiresMarkdown,\n sharedRoute,\n} from './utils';\n\n/** runtime content sources expose raw Markdown as `page.data.content`, Fumadocs MDX processes it */\nexport const docsLlms = ({ dynamic, ref }: SourceRef) => `\nexport const docsLlms = llms(${ref}, {\n renderPage: ${dynamic ? '(page)' : 'async (page)'} => \\`# \\${page.data.title} (\\${page.url})\n\n\\${${dynamic ? 'page.data.content' : \"await page.data.getText('processed')\"}}\\`,\n});`;\n\n/** the Markdown URL of a page, `/llms.mdx/docs/<slugs>/content.md` */\nexport const markdownUrl = (i18n: boolean) => `\nconst getContentUrl = createGetUrl(docsContentRoute${i18n ? ', i18n' : ''});\n\nexport function getPageMarkdownUrl(page: { slugs: string[]; locale?: string }) {\n const segments = [...page.slugs, 'content.md'];\n\n return { segments, url: getContentUrl(segments, page.locale) };\n}`;\n\n/** TanStack Start serves Markdown with a `.md` suffix under the docs route */\nexport const tanstackMarkdownUrl = (i18n: boolean) => `\nconst getDocsUrl = createGetUrl(docsRoute${i18n ? ', i18n' : ''});\n\nexport function getPageMarkdownUrl(page: { slugs: string[]; locale?: string }) {\n const segments = [...page.slugs];\n if (segments.length === 0) {\n segments.push('index.md');\n } else {\n segments[segments.length - 1] += '.md';\n }\n\n return { segments, url: getDocsUrl(segments, page.locale) };\n}\n\n/** @returns page slugs */\nexport function decodeMarkdownUrl(segments: string[]) {\n if (segments.length === 0) return [];\n\n const out = [...segments];\n out[out.length - 1] = out[out.length - 1].replace(/\\\\.md$/, '');\n if (out.length === 1 && out[0] === 'index') out.pop();\n return out;\n}`;\n\nconst imports = `import { docsLlms } from '@/lib/source';`;\nconst pageImports = ({ ref }: SourceRef) => `import { docsLlms, ${ref} } from '@/lib/source';`;\nconst markdown = `new Response(await docsLlms.page(page), {\n headers: {\n 'Content-Type': 'text/markdown',\n },\n })`;\n\ninterface Routes {\n index: FormattedRoute;\n full: FormattedRoute;\n markdown: FormattedRoute;\n}\n\ntype Template = (route: FormattedRoute, i18n: boolean, src: SourceRef) => string;\n\n/** the per-page Markdown route, the only one reading route params */\nconst markdownTemplates: Record<ReactFramework, Template> = {\n next: (route, i18n, src) => `${pageImports(src)}\nimport { notFound } from 'next/navigation';\n\nexport const revalidate = false;\n\nexport async function GET(_req: Request, { params }: RouteContext<'${route.path}'>) {\n const { slug${i18n ? ', lang' : ''} } = await params;\n // remove the appended \"content.md\", \\`/docs/index.md\\` is rewritten to the root page\n const slugs = slug?.slice(0, -1) ?? [];\n if (slugs.at(-1) === 'index') slugs.pop();\n const page = ${src.resolved}.getPage(slugs${i18n ? ', lang' : ''});\n if (!page) notFound();\n\n return ${markdown};\n}\n\nexport ${src.dynamic ? 'async ' : ''}function generateStaticParams() {\n return ${src.resolved}.generateParams().map((item) => ({\n ...item,\n slug: [...item.slug, 'content.md'],\n }));\n}\n`,\n 'react-router': (route, i18n, src) => `${reactRouterTypes(route)}\n${pageImports(src)}\n\nexport async function loader({ params }: Route.LoaderArgs) {\n const slugs = params['*'].split('/').filter((v) => v.length > 0);\n // remove the appended \"content.md\"\n slugs.pop();\n const page = ${src.resolved}.getPage(slugs${i18n ? ', params.lang' : ''});\n if (!page) return new Response('not found', { status: 404 });\n\n return ${markdown};\n}\n`,\n 'tanstack-start': (\n route,\n i18n,\n src,\n ) => `import { createFileRoute, notFound } from '@tanstack/react-router';\nimport { decodeMarkdownUrl } from '@/lib/shared';\n${pageImports(src)}\n\nexport const Route = createFileRoute('${route.path}')({\n server: {\n handlers: {\n GET: async ({ params }) => {\n const slugs = decodeMarkdownUrl(params._splat?.split('/') ?? []);\n const page = ${src.resolved}.getPage(slugs${i18n ? ', params.lang' : ''});\n if (!page) throw notFound();\n\n return ${markdown.replaceAll('\\n ', '\\n ')};\n },\n },\n },\n});\n`,\n waku: (route, i18n, src) => `${pageImports(src)}\nimport type { ApiContext } from 'waku/router';\nimport { unstable_notFound } from 'waku/router/server';\n\nexport async function GET(_: Request, { params }: ApiContext<'${route.path}'>) {\n const page = ${src.resolved}.getPage(params.slug${i18n ? ', params.lang' : ''});\n if (!page) unstable_notFound();\n\n return ${markdown};\n}\n\nexport async function getConfig() {\n return {\n render: 'static' as const,\n staticPaths: ${src.resolved}.generateParams().map((item) => ${i18n ? '[item.lang, ...item.slug]' : 'item.slug'}),\n } as const;\n}\n`,\n};\n\nexport function templates(\n framework: ReactFramework,\n routes: Routes,\n i18n: boolean,\n src: SourceRef,\n): [route: FormattedRoute, content: string][] {\n const index = (body: string) => ({ imports, body, revalidate: true, static: true });\n\n return [\n [\n routes.index,\n routeModule(framework, routes.index, index('new Response(await docsLlms.index())')),\n ],\n [\n routes.full,\n routeModule(framework, routes.full, index('new Response(await docsLlms.full())')),\n ],\n [routes.markdown, markdownTemplates[framework](routes.markdown, i18n, src)],\n ];\n}\n\nexport function llmsRoutes(\n framework: ReactFramework,\n i18n: I18nInfo | null,\n docsRoute: string,\n contentRoute: string,\n): Routes {\n const format = (route: RouteDescriptor) => formatRoute(route, framework, i18n);\n return {\n index: format({ segments: ['llms.txt'] }),\n full: format({ segments: ['llms-full.txt'] }),\n markdown: format(\n framework === 'tanstack-start'\n ? { segments: [docsRoute, '{$}.md'], locale: true }\n : {\n segments: [\n contentRoute,\n { param: 'slug', catchAll: true, optional: true, suffix: 'content.md' },\n ],\n locale: true,\n },\n ),\n };\n}\n\nexport const llms: Feature = {\n id: 'llms',\n title: 'LLM Routes',\n description: 'serve docs as Markdown for LLMs: llms.txt, llms-full.txt and per-page Markdown',\n requires: [docs],\n supports: requiresMarkdown,\n async apply(ctx) {\n const { baseDir, i18n, source, configFile } = ctx.project;\n const framework = reactFramework(ctx.project);\n const sourceFile = path.join(baseDir, 'lib/source.ts');\n const src = sourceRef(ctx.project.source.dynamic);\n // runtime content sources already give raw Markdown\n if (source.collections) {\n await ctx.source(source.collections, (file) => {\n if (!enableProcessedMarkdown(file))\n throw new Error(`cannot find \\`defineDocs()\\` in ${source.collections}`);\n });\n }\n if (await addExport(ctx, sourceFile, 'docsLlms', docsLlms(src))) {\n await ctx.source(sourceFile, (file) =>\n addImport(file, { from: 'fumadocs-core/source', named: ['llms'] }),\n );\n }\n\n const docsRoute = source.baseUrl.replace(/\\/$/, '');\n let contentRoute = `/llms.mdx${docsRoute}`;\n if (framework === 'tanstack-start') {\n await sharedRoute(ctx, 'docsRoute', source.baseUrl);\n await addPageUrl(ctx, 'getPageMarkdownUrl', tanstackMarkdownUrl(i18n !== null));\n } else {\n contentRoute = await sharedRoute(ctx, 'docsContentRoute', contentRoute);\n await addPageUrl(ctx, 'getPageMarkdownUrl', markdownUrl(i18n !== null));\n }\n const routes = llmsRoutes(framework, i18n, docsRoute, contentRoute);\n for (const [route, content] of templates(framework, routes, i18n !== null, src))\n await ctx.write(path.join(baseDir, route.file), content);\n\n if (framework === 'react-router') {\n await registerReactRouterRoutes(ctx, [routes.index, routes.full, routes.markdown]);\n } else if (framework === 'tanstack-start' && ctx.project.static && configFile) {\n // SPA mode only serves prerendered routes\n await ctx.source(configFile, (file) =>\n addTanstackPrerender(file, [routes.index.path, routes.full.path]),\n );\n } else if (framework === 'next') {\n if (i18n) await extendNextProxy(ctx, routes.markdown);\n await configureMarkdownUrl(ctx, docsRoute, routes.markdown);\n }\n if (ctx.project.static && framework !== 'next') {\n ctx.note(\n 'Per-page Markdown routes are dynamic, add the pages to your prerender config to serve them statically.',\n );\n }\n if (framework === 'react-router' || framework === 'waku') {\n ctx.note(\n 'To serve pages with a `.md` suffix, rewrite `/docs/*.md` to the `llms.mdx` route in a middleware, see https://fumadocs.dev/docs/integrations/llms#md-extension.',\n );\n }\n ctx.note(\n \"Add page actions to your docs page with `MarkdownCopyButton` and `ViewOptionsPopover` from 'fumadocs-ui/layouts/docs/page', the Markdown URL of a page is `getPageMarkdownUrl(page).url` from '@/lib/shared'.\",\n );\n },\n};\n\n/** `/docs/*.md` serves the Markdown of a page, like TanStack Start's `.md` route */\nexport const markdownRewrite = (docsRoute: string, markdown: FormattedRoute, i18n: boolean) => ({\n source: `${i18n ? '/:lang' : ''}${docsRoute}/:slug*.md`,\n destination: `${markdown.pattern}/content.md`,\n});\n\nasync function configureMarkdownUrl(\n ctx: FeatureContext,\n docsRoute: string,\n markdown: FormattedRoute,\n) {\n const { configFile, i18n } = ctx.project;\n const rewrite = markdownRewrite(docsRoute, markdown, i18n !== null);\n const edited =\n configFile !== undefined &&\n (await ctx\n .source(configFile, (file) => {\n if (file.code.includes('llms.mdx')) return;\n if (!addNextRewrites(file, [rewrite])) throw new Error('cannot add rewrites');\n })\n .catch(() => false));\n\n if (!edited) {\n ctx.note(\n `Rewrite \\`${rewrite.source}\\` to \\`${rewrite.destination}\\` in your Next.js config to serve pages with a \\`.md\\` suffix.`,\n );\n }\n}\n\n/** routes under `[lang]` need the i18n middleware to resolve the locale */\nexport async function extendNextProxy(ctx: FeatureContext, route: FormattedRoute) {\n const patterns = [route.pattern.replace('/:lang', ''), route.pattern];\n const proxy = await findNextProxy(ctx.project);\n const edited =\n proxy !== undefined &&\n (await ctx\n .source(proxy, (file) => {\n if (!addProxyMatcher(file, patterns)) throw new Error('cannot find the matcher');\n })\n .catch(() => false));\n if (!edited)\n ctx.note(\n `Add ${patterns.map((p) => `\\`${p}\\``).join(', ')} to the matcher of your i18n middleware.`,\n );\n}\n"],"mappings":"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"}
@@ -1,7 +1,7 @@
1
1
  import { i as addReactRouterPrerenderArray } from "./codemod-auol-p4R.js";
2
- import { a as routeModule, r as formatRoute } from "./docs--WqEHTyl.js";
2
+ import { a as routeModule, r as formatRoute } from "./docs-qRGwd9Yp.js";
3
3
  import { a as reactFramework, c as requiresMarkdown, d as sourceRef, s as registerReactRouterRoutes } from "./utils-DbMe7h6c.js";
4
- import { r as llms } from "./llms-Br0RJXXg.js";
4
+ import { r as llms } from "./llms-DQzAst6R.js";
5
5
  import path from "node:path";
6
6
  //#region src/features/mcp.ts
7
7
  const server = ({ ref }) => `import { createMcpHandler, McpServer } from '@modelcontextprotocol/server';
@@ -64,4 +64,4 @@ const mcp = {
64
64
  //#endregion
65
65
  export { mcpRoute as n, templates as r, mcp as t };
66
66
 
67
- //# sourceMappingURL=mcp-Ce_W93we.js.map
67
+ //# sourceMappingURL=mcp-BpFZ_fdV.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"mcp-Ce_W93we.js","names":[],"sources":["../src/features/mcp.ts"],"sourcesContent":["import path from 'node:path';\nimport type { Feature } from '@/features';\nimport type { ReactFramework } from '@/project';\nimport { type FormattedRoute, formatRoute, routeModule } from '@/project/route';\nimport { addReactRouterPrerenderArray } from '@/codemod';\nimport { llms } from './llms';\nimport {\n reactFramework,\n registerReactRouterRoutes,\n requiresMarkdown,\n type SourceRef,\n sourceRef,\n} from './utils';\n\nconst server = ({\n ref,\n}: SourceRef) => `import { createMcpHandler, McpServer } from '@modelcontextprotocol/server';\nimport { registerSearchTool, registerSourceTools } from 'fumadocs-core/mcp';\nimport { createFromSource } from 'fumadocs-core/search/server';\nimport { docsLlms, ${ref} } from '@/lib/source';\n\nconst handler = createMcpHandler(() => {\n const mcp = new McpServer({\n name: 'docs',\n version: '1.0.0',\n });\n\n registerSourceTools(mcp, ${ref}, docsLlms);\n registerSearchTool(mcp, createFromSource(${ref}));\n\n return mcp;\n});\n`;\n\nexport const templates = (framework: ReactFramework, route: FormattedRoute, src: SourceRef) =>\n routeModule(framework, route, {\n imports: server(src),\n body: 'handler.fetch(request)',\n request: true,\n methods: ['GET', 'POST', 'DELETE'],\n });\n\nexport const mcpRoute = (framework: ReactFramework) =>\n formatRoute({ segments: ['api/mcp'] }, framework, null);\n\nexport const mcp: Feature = {\n id: 'mcp',\n title: 'MCP Server',\n description: 'a MCP server for AI agents to search and read your docs, at /api/mcp',\n requires: [llms],\n supports: (project) =>\n project.static ? 'MCP requires a server at runtime' : requiresMarkdown(project),\n async apply(ctx) {\n const { baseDir } = ctx.project;\n const framework = reactFramework(ctx.project);\n ctx.addDependencies({ '@modelcontextprotocol/server': null, zod: null });\n\n const route = mcpRoute(framework);\n await ctx.write(\n path.join(baseDir, route.file),\n templates(framework, route, sourceRef(ctx.project.source.dynamic)),\n );\n\n if (framework === 'react-router') {\n await registerReactRouterRoutes(ctx, [route]);\n await ctx.source('react-router.config.ts', (file) => {\n addReactRouterPrerenderArray(file, 'excluded', [`/${route.path}`]);\n });\n }\n\n ctx.note(`Connect AI agents to your docs with the MCP server:\n{\n \"mcpServers\": {\n \"docs\": { \"url\": \"https://your-site.com/api/mcp\" }\n }\n}`);\n },\n};\n"],"mappings":";;;;;;AAcA,MAAM,UAAU,EACd,UACe;;;qBAGI,IAAI;;;;;;;;6BAQI,IAAI;6CACY,IAAI;;;;;AAMjD,MAAa,aAAa,WAA2B,OAAuB,QAC1E,YAAY,WAAW,OAAO;CAC5B,SAAS,OAAO,GAAG;CACnB,MAAM;CACN,SAAS;CACT,SAAS;EAAC;EAAO;EAAQ;CAAQ;AACnC,CAAC;AAEH,MAAa,YAAY,cACvB,YAAY,EAAE,UAAU,CAAC,SAAS,EAAE,GAAG,WAAW,IAAI;AAExD,MAAa,MAAe;CAC1B,IAAI;CACJ,OAAO;CACP,aAAa;CACb,UAAU,CAAC,IAAI;CACf,WAAW,YACT,QAAQ,SAAS,qCAAqC,iBAAiB,OAAO;CAChF,MAAM,MAAM,KAAK;EACf,MAAM,EAAE,YAAY,IAAI;EACxB,MAAM,YAAY,eAAe,IAAI,OAAO;EAC5C,IAAI,gBAAgB;GAAE,gCAAgC;GAAM,KAAK;EAAK,CAAC;EAEvE,MAAM,QAAQ,SAAS,SAAS;EAChC,MAAM,IAAI,MACR,KAAK,KAAK,SAAS,MAAM,IAAI,GAC7B,UAAU,WAAW,OAAO,UAAU,IAAI,QAAQ,OAAO,OAAO,CAAC,CACnE;EAEA,IAAI,cAAc,gBAAgB;GAChC,MAAM,0BAA0B,KAAK,CAAC,KAAK,CAAC;GAC5C,MAAM,IAAI,OAAO,2BAA2B,SAAS;IACnD,6BAA6B,MAAM,YAAY,CAAC,IAAI,MAAM,MAAM,CAAC;GACnE,CAAC;EACH;EAEA,IAAI,KAAK;;;;;EAKX;CACA;AACF"}
1
+ {"version":3,"file":"mcp-BpFZ_fdV.js","names":[],"sources":["../src/features/mcp.ts"],"sourcesContent":["import path from 'node:path';\nimport type { Feature } from '@/features';\nimport type { ReactFramework } from '@/project';\nimport { type FormattedRoute, formatRoute, routeModule } from '@/project/route';\nimport { addReactRouterPrerenderArray } from '@/codemod';\nimport { llms } from './llms';\nimport {\n reactFramework,\n registerReactRouterRoutes,\n requiresMarkdown,\n type SourceRef,\n sourceRef,\n} from './utils';\n\nconst server = ({\n ref,\n}: SourceRef) => `import { createMcpHandler, McpServer } from '@modelcontextprotocol/server';\nimport { registerSearchTool, registerSourceTools } from 'fumadocs-core/mcp';\nimport { createFromSource } from 'fumadocs-core/search/server';\nimport { docsLlms, ${ref} } from '@/lib/source';\n\nconst handler = createMcpHandler(() => {\n const mcp = new McpServer({\n name: 'docs',\n version: '1.0.0',\n });\n\n registerSourceTools(mcp, ${ref}, docsLlms);\n registerSearchTool(mcp, createFromSource(${ref}));\n\n return mcp;\n});\n`;\n\nexport const templates = (framework: ReactFramework, route: FormattedRoute, src: SourceRef) =>\n routeModule(framework, route, {\n imports: server(src),\n body: 'handler.fetch(request)',\n request: true,\n methods: ['GET', 'POST', 'DELETE'],\n });\n\nexport const mcpRoute = (framework: ReactFramework) =>\n formatRoute({ segments: ['api/mcp'] }, framework, null);\n\nexport const mcp: Feature = {\n id: 'mcp',\n title: 'MCP Server',\n description: 'a MCP server for AI agents to search and read your docs, at /api/mcp',\n requires: [llms],\n supports: (project) =>\n project.static ? 'MCP requires a server at runtime' : requiresMarkdown(project),\n async apply(ctx) {\n const { baseDir } = ctx.project;\n const framework = reactFramework(ctx.project);\n ctx.addDependencies({ '@modelcontextprotocol/server': null, zod: null });\n\n const route = mcpRoute(framework);\n await ctx.write(\n path.join(baseDir, route.file),\n templates(framework, route, sourceRef(ctx.project.source.dynamic)),\n );\n\n if (framework === 'react-router') {\n await registerReactRouterRoutes(ctx, [route]);\n await ctx.source('react-router.config.ts', (file) => {\n addReactRouterPrerenderArray(file, 'excluded', [`/${route.path}`]);\n });\n }\n\n ctx.note(`Connect AI agents to your docs with the MCP server:\n{\n \"mcpServers\": {\n \"docs\": { \"url\": \"https://your-site.com/api/mcp\" }\n }\n}`);\n },\n};\n"],"mappings":";;;;;;AAcA,MAAM,UAAU,EACd,UACe;;;qBAGI,IAAI;;;;;;;;6BAQI,IAAI;6CACY,IAAI;;;;;AAMjD,MAAa,aAAa,WAA2B,OAAuB,QAC1E,YAAY,WAAW,OAAO;CAC5B,SAAS,OAAO,GAAG;CACnB,MAAM;CACN,SAAS;CACT,SAAS;EAAC;EAAO;EAAQ;CAAQ;AACnC,CAAC;AAEH,MAAa,YAAY,cACvB,YAAY,EAAE,UAAU,CAAC,SAAS,EAAE,GAAG,WAAW,IAAI;AAExD,MAAa,MAAe;CAC1B,IAAI;CACJ,OAAO;CACP,aAAa;CACb,UAAU,CAAC,IAAI;CACf,WAAW,YACT,QAAQ,SAAS,qCAAqC,iBAAiB,OAAO;CAChF,MAAM,MAAM,KAAK;EACf,MAAM,EAAE,YAAY,IAAI;EACxB,MAAM,YAAY,eAAe,IAAI,OAAO;EAC5C,IAAI,gBAAgB;GAAE,gCAAgC;GAAM,KAAK;EAAK,CAAC;EAEvE,MAAM,QAAQ,SAAS,SAAS;EAChC,MAAM,IAAI,MACR,KAAK,KAAK,SAAS,MAAM,IAAI,GAC7B,UAAU,WAAW,OAAO,UAAU,IAAI,QAAQ,OAAO,OAAO,CAAC,CACnE;EAEA,IAAI,cAAc,gBAAgB;GAChC,MAAM,0BAA0B,KAAK,CAAC,KAAK,CAAC;GAC5C,MAAM,IAAI,OAAO,2BAA2B,SAAS;IACnD,6BAA6B,MAAM,YAAY,CAAC,IAAI,MAAM,MAAM,CAAC;GACnE,CAAC;EACH;EAEA,IAAI,KAAK;;;;;EAKX;CACA;AACF"}
@@ -1,7 +1,7 @@
1
1
  import { S as getProperty, f as addElements, u as getConfigObject } from "./codemod-auol-p4R.js";
2
- import { i as reactRouterTypes, r as formatRoute, t as docs } from "./docs--WqEHTyl.js";
2
+ import { i as reactRouterTypes, r as formatRoute, t as docs } from "./docs-qRGwd9Yp.js";
3
3
  import { a as reactFramework, d as sourceRef, n as addPageUrl, s as registerReactRouterRoutes, u as sharedRoute } from "./utils-DbMe7h6c.js";
4
- import { n as extendNextProxy } from "./llms-Br0RJXXg.js";
4
+ import { n as extendNextProxy } from "./llms-DQzAst6R.js";
5
5
  import path from "node:path";
6
6
  //#region src/features/og.ts
7
7
  /** the image URL of a page, `/og/docs/<slugs>/image.png` */
@@ -139,4 +139,4 @@ const og = {
139
139
  //#endregion
140
140
  export { og as t };
141
141
 
142
- //# sourceMappingURL=og-LFlX1Bl4.js.map
142
+ //# sourceMappingURL=og-m0CwRlRz.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"og-LFlX1Bl4.js","names":[],"sources":["../src/features/og.ts"],"sourcesContent":["import path from 'node:path';\nimport type { Feature } from '@/features';\nimport type { ReactFramework } from '@/project';\nimport { type FormattedRoute, formatRoute, reactRouterTypes } from '@/project/route';\nimport { addElements, getConfigObject, getProperty } from '@/codemod';\nimport { docs } from './docs';\nimport { extendNextProxy } from './llms';\nimport {\n addPageUrl,\n reactFramework,\n registerReactRouterRoutes,\n sharedRoute,\n type SourceRef,\n sourceRef,\n} from './utils';\n\ntype Engine = 'takumi' | 'next-og';\n\n/** the image URL of a page, `/og/docs/<slugs>/image.png` */\nconst imageUrl = (ext: string, i18n: boolean) => `\nconst getImageUrl = createGetUrl(docsImageRoute${i18n ? ', i18n' : ''});\n\nexport function getPageImageUrl(page: { slugs: string[]; locale?: string }) {\n const segments = [...page.slugs, 'image.${ext}'];\n\n return { segments, url: getImageUrl(segments, page.locale) };\n}`;\n\nconst imports = (engine: Engine) =>\n `import { generateOGImage } from 'fumadocs-ui/og${engine === 'takumi' ? '/takumi' : ''}';`;\n\nconst image = (engine: Engine) => `generateOGImage({\n title: page.data.title,\n description: page.data.description,\n site: 'My App',${engine === 'takumi' ? \"\\n format: 'webp',\" : ''}\n })`;\n\nconst ext = (engine: Engine) => (engine === 'takumi' ? 'webp' : 'png');\n\ntype Template = (route: FormattedRoute, engine: Engine, i18n: boolean, src: SourceRef) => string;\n\nconst templates: Record<ReactFramework, Template> = {\n next: (route, engine, i18n, src) => `import { ${src.ref} } from '@/lib/source';\nimport { notFound } from 'next/navigation';\n${imports(engine)}\n\nexport const revalidate = false;\n\nexport async function GET(_req: Request, { params }: RouteContext<'${route.path}'>) {\n const { slug${i18n ? ', lang' : ''} } = await params;\n const page = ${src.resolved}.getPage(slug.slice(0, -1)${i18n ? ', lang' : ''});\n if (!page) notFound();\n\n return ${image(engine)};\n}\n\nexport ${src.dynamic ? 'async ' : ''}function generateStaticParams() {\n return ${src.resolved}.generateParams().map((item) => ({\n ...item,\n slug: [...item.slug, 'image.${ext(engine)}'],\n }));\n}\n`,\n 'react-router': (route, engine, i18n, src) => `${reactRouterTypes(route)}\nimport { ${src.ref} } from '@/lib/source';\n${imports(engine)}\n\nexport async function loader({ params }: Route.LoaderArgs) {\n const slugs = params['*'].split('/').filter((v) => v.length > 0);\n const page = ${src.resolved}.getPage(slugs.slice(0, -1)${i18n ? ', params.lang' : ''});\n if (!page) throw new Response(undefined, { status: 404 });\n\n return ${image(engine)};\n}\n`,\n 'tanstack-start': (\n route,\n engine,\n i18n,\n src,\n ) => `import { createFileRoute } from '@tanstack/react-router';\nimport { ${src.ref} } from '@/lib/source';\n${imports(engine)}\n\nexport const Route = createFileRoute('${route.path}')({\n server: {\n handlers: {\n GET: async ({ params }) => {\n const slugs = params._splat?.split('/') ?? [];\n const page = ${src.resolved}.getPage(slugs.slice(0, -1)${i18n ? ', params.lang' : ''});\n if (!page) return new Response(undefined, { status: 404 });\n\n return ${image(engine).replaceAll('\\n ', '\\n ')};\n },\n },\n },\n});\n`,\n waku: (route, engine, i18n, src) => `import { ${src.ref} } from '@/lib/source';\nimport type { ApiContext } from 'waku/router';\n${imports(engine)}\n\nexport async function GET(_: Request, { params }: ApiContext<'${route.path}'>) {\n const page = ${src.resolved}.getPage(params.slug${i18n ? ', params.lang' : ''});\n if (!page) return new Response(undefined, { status: 404 });\n\n return ${image(engine)};\n}\n\nexport async function getConfig() {\n return {\n render: 'static' as const,\n staticPaths: ${src.resolved}.generateParams().map((item) => ${i18n ? '[item.lang, ...item.slug]' : 'item.slug'}),\n } as const;\n}\n`,\n};\n\nexport const og: Feature<{ engine: Engine }> = {\n id: 'og',\n title: 'OG Image',\n description: 'generate Open Graph images for docs pages',\n requires: [docs],\n options: {\n engine: {\n message: 'Choose an image engine',\n choices: [\n { value: 'takumi', label: 'Takumi', hint: 'outputs WebP, framework-agnostic' },\n { value: 'next-og', label: 'next/og', hint: 'Next.js built-in solution' },\n ],\n },\n },\n async apply(ctx, { engine }) {\n const { baseDir, configFile, i18n, source } = ctx.project;\n const framework = reactFramework(ctx.project);\n if (engine === 'next-og' && framework !== 'next')\n throw new Error('next/og is only available on Next.js');\n if (engine === 'takumi') ctx.addDependencies({ 'takumi-js': null });\n\n const shared = path.join(baseDir, 'lib/shared.ts');\n const imageRoute = await sharedRoute(\n ctx,\n 'docsImageRoute',\n `/og${source.baseUrl.replace(/\\/$/, '')}`,\n );\n const added = await addPageUrl(ctx, 'getPageImageUrl', imageUrl(ext(engine), i18n !== null));\n if (!added && engine === 'takumi') {\n await ctx.source(shared, (file) => {\n file.s.replaceAll('image.png', 'image.webp');\n });\n }\n\n const route = formatRoute(\n {\n segments: [imageRoute, { param: 'slug', catchAll: true, suffix: `image.${ext(engine)}` }],\n locale: true,\n },\n framework,\n i18n,\n 'tsx',\n );\n await ctx.write(\n path.join(baseDir, route.file),\n templates[framework](route, engine, i18n !== null, sourceRef(ctx.project.source.dynamic)),\n );\n\n if (framework === 'react-router') await registerReactRouterRoutes(ctx, [route]);\n if (framework === 'next' && engine === 'takumi' && configFile) {\n await ctx.source(configFile, (file) => {\n const config = getConfigObject(file);\n if (config && !getProperty(config, 'serverExternalPackages'))\n addElements(file, config, [\"serverExternalPackages: ['@takumi-rs/core']\"]);\n });\n }\n if (framework === 'next' && i18n) await extendNextProxy(ctx, route);\n\n ctx.note(\n \"The image URL of a page is `getPageImageUrl(page).url` from '@/lib/shared', reference it in the page metadata (e.g. `og:image`).\",\n );\n },\n};\n"],"mappings":"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"}
1
+ {"version":3,"file":"og-m0CwRlRz.js","names":[],"sources":["../src/features/og.ts"],"sourcesContent":["import path from 'node:path';\nimport type { Feature } from '@/features';\nimport type { ReactFramework } from '@/project';\nimport { type FormattedRoute, formatRoute, reactRouterTypes } from '@/project/route';\nimport { addElements, getConfigObject, getProperty } from '@/codemod';\nimport { docs } from './docs';\nimport { extendNextProxy } from './llms';\nimport {\n addPageUrl,\n reactFramework,\n registerReactRouterRoutes,\n sharedRoute,\n type SourceRef,\n sourceRef,\n} from './utils';\n\ntype Engine = 'takumi' | 'next-og';\n\n/** the image URL of a page, `/og/docs/<slugs>/image.png` */\nconst imageUrl = (ext: string, i18n: boolean) => `\nconst getImageUrl = createGetUrl(docsImageRoute${i18n ? ', i18n' : ''});\n\nexport function getPageImageUrl(page: { slugs: string[]; locale?: string }) {\n const segments = [...page.slugs, 'image.${ext}'];\n\n return { segments, url: getImageUrl(segments, page.locale) };\n}`;\n\nconst imports = (engine: Engine) =>\n `import { generateOGImage } from 'fumadocs-ui/og${engine === 'takumi' ? '/takumi' : ''}';`;\n\nconst image = (engine: Engine) => `generateOGImage({\n title: page.data.title,\n description: page.data.description,\n site: 'My App',${engine === 'takumi' ? \"\\n format: 'webp',\" : ''}\n })`;\n\nconst ext = (engine: Engine) => (engine === 'takumi' ? 'webp' : 'png');\n\ntype Template = (route: FormattedRoute, engine: Engine, i18n: boolean, src: SourceRef) => string;\n\nconst templates: Record<ReactFramework, Template> = {\n next: (route, engine, i18n, src) => `import { ${src.ref} } from '@/lib/source';\nimport { notFound } from 'next/navigation';\n${imports(engine)}\n\nexport const revalidate = false;\n\nexport async function GET(_req: Request, { params }: RouteContext<'${route.path}'>) {\n const { slug${i18n ? ', lang' : ''} } = await params;\n const page = ${src.resolved}.getPage(slug.slice(0, -1)${i18n ? ', lang' : ''});\n if (!page) notFound();\n\n return ${image(engine)};\n}\n\nexport ${src.dynamic ? 'async ' : ''}function generateStaticParams() {\n return ${src.resolved}.generateParams().map((item) => ({\n ...item,\n slug: [...item.slug, 'image.${ext(engine)}'],\n }));\n}\n`,\n 'react-router': (route, engine, i18n, src) => `${reactRouterTypes(route)}\nimport { ${src.ref} } from '@/lib/source';\n${imports(engine)}\n\nexport async function loader({ params }: Route.LoaderArgs) {\n const slugs = params['*'].split('/').filter((v) => v.length > 0);\n const page = ${src.resolved}.getPage(slugs.slice(0, -1)${i18n ? ', params.lang' : ''});\n if (!page) throw new Response(undefined, { status: 404 });\n\n return ${image(engine)};\n}\n`,\n 'tanstack-start': (\n route,\n engine,\n i18n,\n src,\n ) => `import { createFileRoute } from '@tanstack/react-router';\nimport { ${src.ref} } from '@/lib/source';\n${imports(engine)}\n\nexport const Route = createFileRoute('${route.path}')({\n server: {\n handlers: {\n GET: async ({ params }) => {\n const slugs = params._splat?.split('/') ?? [];\n const page = ${src.resolved}.getPage(slugs.slice(0, -1)${i18n ? ', params.lang' : ''});\n if (!page) return new Response(undefined, { status: 404 });\n\n return ${image(engine).replaceAll('\\n ', '\\n ')};\n },\n },\n },\n});\n`,\n waku: (route, engine, i18n, src) => `import { ${src.ref} } from '@/lib/source';\nimport type { ApiContext } from 'waku/router';\n${imports(engine)}\n\nexport async function GET(_: Request, { params }: ApiContext<'${route.path}'>) {\n const page = ${src.resolved}.getPage(params.slug${i18n ? ', params.lang' : ''});\n if (!page) return new Response(undefined, { status: 404 });\n\n return ${image(engine)};\n}\n\nexport async function getConfig() {\n return {\n render: 'static' as const,\n staticPaths: ${src.resolved}.generateParams().map((item) => ${i18n ? '[item.lang, ...item.slug]' : 'item.slug'}),\n } as const;\n}\n`,\n};\n\nexport const og: Feature<{ engine: Engine }> = {\n id: 'og',\n title: 'OG Image',\n description: 'generate Open Graph images for docs pages',\n requires: [docs],\n options: {\n engine: {\n message: 'Choose an image engine',\n choices: [\n { value: 'takumi', label: 'Takumi', hint: 'outputs WebP, framework-agnostic' },\n { value: 'next-og', label: 'next/og', hint: 'Next.js built-in solution' },\n ],\n },\n },\n async apply(ctx, { engine }) {\n const { baseDir, configFile, i18n, source } = ctx.project;\n const framework = reactFramework(ctx.project);\n if (engine === 'next-og' && framework !== 'next')\n throw new Error('next/og is only available on Next.js');\n if (engine === 'takumi') ctx.addDependencies({ 'takumi-js': null });\n\n const shared = path.join(baseDir, 'lib/shared.ts');\n const imageRoute = await sharedRoute(\n ctx,\n 'docsImageRoute',\n `/og${source.baseUrl.replace(/\\/$/, '')}`,\n );\n const added = await addPageUrl(ctx, 'getPageImageUrl', imageUrl(ext(engine), i18n !== null));\n if (!added && engine === 'takumi') {\n await ctx.source(shared, (file) => {\n file.s.replaceAll('image.png', 'image.webp');\n });\n }\n\n const route = formatRoute(\n {\n segments: [imageRoute, { param: 'slug', catchAll: true, suffix: `image.${ext(engine)}` }],\n locale: true,\n },\n framework,\n i18n,\n 'tsx',\n );\n await ctx.write(\n path.join(baseDir, route.file),\n templates[framework](route, engine, i18n !== null, sourceRef(ctx.project.source.dynamic)),\n );\n\n if (framework === 'react-router') await registerReactRouterRoutes(ctx, [route]);\n if (framework === 'next' && engine === 'takumi' && configFile) {\n await ctx.source(configFile, (file) => {\n const config = getConfigObject(file);\n if (config && !getProperty(config, 'serverExternalPackages'))\n addElements(file, config, [\"serverExternalPackages: ['@takumi-rs/core']\"]);\n });\n }\n if (framework === 'next' && i18n) await extendNextProxy(ctx, route);\n\n ctx.note(\n \"The image URL of a page is `getPageImageUrl(page).url` from '@/lib/shared', reference it in the page metadata (e.g. `og:image`).\",\n );\n },\n};\n"],"mappings":";;;;;;;AAmBA,MAAM,YAAY,KAAa,SAAkB;iDACA,OAAO,WAAW,GAAG;;;4CAG1B,IAAI;;;;AAKhD,MAAM,WAAW,WACf,kDAAkD,WAAW,WAAW,YAAY,GAAG;AAEzF,MAAM,SAAS,WAAmB;;;qBAGb,WAAW,WAAW,0BAA0B,GAAG;;AAGxE,MAAM,OAAO,WAAoB,WAAW,WAAW,SAAS;AAIhE,MAAM,YAA8C;CAClD,OAAO,OAAO,QAAQ,MAAM,QAAQ,YAAY,IAAI,IAAI;;EAExD,QAAQ,MAAM,EAAE;;;;qEAImD,MAAM,KAAK;gBAChE,OAAO,WAAW,GAAG;iBACpB,IAAI,SAAS,4BAA4B,OAAO,WAAW,GAAG;;;WAGpE,MAAM,MAAM,EAAE;;;SAGhB,IAAI,UAAU,WAAW,GAAG;WAC1B,IAAI,SAAS;;kCAEU,IAAI,MAAM,EAAE;;;;CAI5C,iBAAiB,OAAO,QAAQ,MAAM,QAAQ,GAAG,iBAAiB,KAAK,EAAE;WAChE,IAAI,IAAI;EACjB,QAAQ,MAAM,EAAE;;;;iBAID,IAAI,SAAS,6BAA6B,OAAO,kBAAkB,GAAG;;;WAG5E,MAAM,MAAM,EAAE;;;CAGvB,mBACE,OACA,QACA,MACA,QACG;WACI,IAAI,IAAI;EACjB,QAAQ,MAAM,EAAE;;wCAEsB,MAAM,KAAK;;;;;uBAK5B,IAAI,SAAS,6BAA6B,OAAO,kBAAkB,GAAG;;;iBAG5E,MAAM,MAAM,CAAC,CAAC,WAAW,QAAQ,YAAY,EAAE;;;;;;CAM9D,OAAO,OAAO,QAAQ,MAAM,QAAQ,YAAY,IAAI,IAAI;;EAExD,QAAQ,MAAM,EAAE;;gEAE8C,MAAM,KAAK;iBAC1D,IAAI,SAAS,sBAAsB,OAAO,kBAAkB,GAAG;;;WAGrE,MAAM,MAAM,EAAE;;;;;;mBAMN,IAAI,SAAS,kCAAkC,OAAO,8BAA8B,YAAY;;;;AAInH;AAEA,MAAa,KAAkC;CAC7C,IAAI;CACJ,OAAO;CACP,aAAa;CACb,UAAU,CAAC,IAAI;CACf,SAAS,EACP,QAAQ;EACN,SAAS;EACT,SAAS,CACP;GAAE,OAAO;GAAU,OAAO;GAAU,MAAM;EAAmC,GAC7E;GAAE,OAAO;GAAW,OAAO;GAAW,MAAM;EAA4B,CAC1E;CACF,EACF;CACA,MAAM,MAAM,KAAK,EAAE,UAAU;EAC3B,MAAM,EAAE,SAAS,YAAY,MAAM,WAAW,IAAI;EAClD,MAAM,YAAY,eAAe,IAAI,OAAO;EAC5C,IAAI,WAAW,aAAa,cAAc,QACxC,MAAM,IAAI,MAAM,sCAAsC;EACxD,IAAI,WAAW,UAAU,IAAI,gBAAgB,EAAE,aAAa,KAAK,CAAC;EAElE,MAAM,SAAS,KAAK,KAAK,SAAS,eAAe;EACjD,MAAM,aAAa,MAAM,YACvB,KACA,kBACA,MAAM,OAAO,QAAQ,QAAQ,OAAO,EAAE,GACxC;EAEA,IAAI,CAAC,MADe,WAAW,KAAK,mBAAmB,SAAS,IAAI,MAAM,GAAG,SAAS,IAAI,CAAC,KAC7E,WAAW,UACvB,MAAM,IAAI,OAAO,SAAS,SAAS;GACjC,KAAK,EAAE,WAAW,aAAa,YAAY;EAC7C,CAAC;EAGH,MAAM,QAAQ,YACZ;GACE,UAAU,CAAC,YAAY;IAAE,OAAO;IAAQ,UAAU;IAAM,QAAQ,SAAS,IAAI,MAAM;GAAI,CAAC;GACxF,QAAQ;EACV,GACA,WACA,MACA,KACF;EACA,MAAM,IAAI,MACR,KAAK,KAAK,SAAS,MAAM,IAAI,GAC7B,UAAU,UAAU,CAAC,OAAO,QAAQ,SAAS,MAAM,UAAU,IAAI,QAAQ,OAAO,OAAO,CAAC,CAC1F;EAEA,IAAI,cAAc,gBAAgB,MAAM,0BAA0B,KAAK,CAAC,KAAK,CAAC;EAC9E,IAAI,cAAc,UAAU,WAAW,YAAY,YACjD,MAAM,IAAI,OAAO,aAAa,SAAS;GACrC,MAAM,SAAS,gBAAgB,IAAI;GACnC,IAAI,UAAU,CAAC,YAAY,QAAQ,wBAAwB,GACzD,YAAY,MAAM,QAAQ,CAAC,6CAA6C,CAAC;EAC7E,CAAC;EAEH,IAAI,cAAc,UAAU,MAAM,MAAM,gBAAgB,KAAK,KAAK;EAElE,IAAI,KACF,kIACF;CACF;AACF"}
@@ -1,5 +1,5 @@
1
1
  import { b as findJsxElement, m as addJsxAttribute, o as filterReactRouterPrerenderArray, p as addImport, r as addTanstackPrerender, s as filterReactRouterRoute } from "./codemod-auol-p4R.js";
2
- import { a as routeModule, r as formatRoute, t as docs } from "./docs--WqEHTyl.js";
2
+ import { a as routeModule, r as formatRoute, t as docs } from "./docs-qRGwd9Yp.js";
3
3
  import { a as reactFramework, d as sourceRef, i as posix, l as scripts, r as findSource, s as registerReactRouterRoutes } from "./utils-DbMe7h6c.js";
4
4
  import fs from "node:fs/promises";
5
5
  import path from "node:path";
@@ -24,8 +24,9 @@ const dialogImports = `import {
24
24
  type SharedProps,
25
25
  } from 'fumadocs-ui/components/dialog/search';
26
26
  import { useI18n } from 'fumadocs-ui/contexts/i18n';`;
27
- const dialogBody = (footer, url) => ` return (
28
- <SearchDialog search={search} onSearchChange={setSearch} isLoading={query.isLoading} {...props}>
27
+ /** `legacy` for hooks in the shape of the deprecated `useDocsSearch()`, like the Typesense adapter */
28
+ const dialogBody = (footer, url, legacy = false) => ` return (
29
+ <SearchDialog ${legacy ? "search={search} onSearchChange={setSearch} isLoading={query.isLoading}" : "{...search}"} {...props}>
29
30
  <SearchDialogOverlay />
30
31
  <SearchDialogContent>
31
32
  <SearchDialogHeader>
@@ -33,7 +34,7 @@ const dialogBody = (footer, url) => ` return (
33
34
  <SearchDialogInput />
34
35
  <SearchDialogClose />
35
36
  </SearchDialogHeader>
36
- <SearchDialogList items={query.data !== 'empty' ? query.data : null} />
37
+ <SearchDialogList ${legacy ? "items={query.data !== 'empty' ? query.data : null} " : ""}/>
37
38
  <SearchDialogFooter>
38
39
  <a
39
40
  href="${url}"
@@ -89,7 +90,7 @@ const providers = {
89
90
  static: true,
90
91
  dialog: (env) => `'use client';
91
92
  ${dialogImports}
92
- import { useDocsSearch } from 'fumadocs-core/search/client';
93
+ import { useOramaCloudSearch } from 'fumadocs-core/search/client';
93
94
  import { OramaCloud } from '@orama/core';
94
95
 
95
96
  const client = new OramaCloud({
@@ -99,8 +100,7 @@ const client = new OramaCloud({
99
100
 
100
101
  export default function CustomSearchDialog(props: SharedProps) {
101
102
  const { locale } = useI18n();
102
- const { search, setSearch, query } = useDocsSearch({
103
- type: 'orama-cloud',
103
+ const search = useOramaCloudSearch({
104
104
  client,
105
105
  locale,
106
106
  });
@@ -132,20 +132,17 @@ await sync(orama, {
132
132
  static: true,
133
133
  dialog: (env) => `'use client';
134
134
  ${dialogImports}
135
- import { useDocsSearch } from 'fumadocs-core/search/client';
136
- import { algoliaClient } from 'fumadocs-core/search/client/algolia';
135
+ import { useAlgoliaSearch } from 'fumadocs-core/search/client';
137
136
  import { liteClient } from 'algoliasearch/lite';
138
137
 
139
138
  const algolia = liteClient(${env.read}ALGOLIA_APP_ID!, ${env.read}ALGOLIA_SEARCH_KEY!);
140
139
 
141
140
  export default function CustomSearchDialog(props: SharedProps) {
142
141
  const { locale } = useI18n();
143
- const { search, setSearch, query } = useDocsSearch({
144
- client: algoliaClient({
145
- client: algolia,
146
- indexName: 'document',
147
- locale,
148
- }),
142
+ const search = useAlgoliaSearch({
143
+ client: algolia,
144
+ indexName: 'document',
145
+ locale,
149
146
  });
150
147
 
151
148
  ${dialogBody("Algolia", "https://algolia.com")}`,
@@ -175,8 +172,7 @@ await sync(client, {
175
172
  static: true,
176
173
  dialog: (env) => `'use client';
177
174
  ${dialogImports}
178
- import { useDocsSearch } from 'fumadocs-core/search/client';
179
- import { meilisearchClient } from 'fumadocs-core/search/client/meilisearch';
175
+ import { useMeilisearch } from 'fumadocs-core/search/client';
180
176
  import { Meilisearch } from 'meilisearch';
181
177
 
182
178
  const client = new Meilisearch({
@@ -186,12 +182,10 @@ const client = new Meilisearch({
186
182
 
187
183
  export default function CustomSearchDialog(props: SharedProps) {
188
184
  const { locale } = useI18n();
189
- const { search, setSearch, query } = useDocsSearch({
190
- client: meilisearchClient({
191
- client,
192
- indexName: 'docs',
193
- locale,
194
- }),
185
+ const search = useMeilisearch({
186
+ client,
187
+ indexName: 'docs',
188
+ locale,
195
189
  });
196
190
 
197
191
  ${dialogBody("Meilisearch", "https://www.meilisearch.com")}`,
@@ -240,7 +234,7 @@ export default function CustomSearchDialog(props: SharedProps) {
240
234
  client,
241
235
  });
242
236
 
243
- ${dialogBody("Typesense", "https://typesense.org")}`,
237
+ ${dialogBody("Typesense", "https://typesense.org", true)}`,
244
238
  exportIndexes: (src, async) => ({
245
239
  head: typesenseDocuments(src, async),
246
240
  documents: "getDocuments()"
@@ -263,42 +257,39 @@ await sync(client, {
263
257
  hint: "AI search, signup needed",
264
258
  dependencies: { "@mixedbread/sdk": null },
265
259
  publicEnv: [],
266
- privateEnv: ["MIXEDBREAD_API_KEY", "MIXEDBREAD_STORE_ID"],
260
+ privateEnv: ["MXBAI_API_KEY", "MIXEDBREAD_STORE_ID"],
267
261
  static: false,
268
262
  dialog: () => `'use client';
269
263
  ${dialogImports}
270
- import { useDocsSearch } from 'fumadocs-core/search/client';
271
- import { fetchClient } from 'fumadocs-core/search/client/fetch';
264
+ import { useFetchSearch } from 'fumadocs-core/search/client';
272
265
 
273
266
  export default function CustomSearchDialog(props: SharedProps) {
274
267
  const { locale } = useI18n();
275
- const { search, setSearch, query } = useDocsSearch({
276
- client: fetchClient({
277
- api: '/api/search',
278
- locale,
279
- }),
268
+ const search = useFetchSearch({
269
+ api: '/api/search',
270
+ locale,
271
+ // every search is a request to Mixedbread
272
+ delayMs: 300,
280
273
  });
281
274
 
282
275
  ${dialogBody("Mixedbread", "https://mixedbread.com")}`,
283
- sync: (_env, { dir }) => `import { spawnSync } from 'node:child_process';
284
-
285
- // sync the content with Mixedbread CLI
286
- const result = spawnSync(
287
- 'npx',
288
- ['--yes', '@mixedbread/cli', 'vs', 'sync', process.env.MIXEDBREAD_STORE_ID!, '${dir}', '--ci'],
289
- { stdio: 'inherit' },
290
- );
291
-
292
- process.exit(result.status ?? 1);
293
- `,
276
+ exportIndexes: ({ ref }) => ({
277
+ head: `import { ${ref} } from '@/lib/source';
278
+ import { toDocuments } from 'fumadocs-core/search/mixedbread';`,
279
+ documents: `toDocuments(${ref})`
280
+ }),
281
+ sync: (_env, { output }) => syncScript(output, `import { type DocumentRecord, sync } from 'fumadocs-core/search/mixedbread';
282
+ import Mixedbread from '@mixedbread/sdk';`, `await sync(new Mixedbread(), {
283
+ storeIdentifier: process.env.MIXEDBREAD_STORE_ID!,
284
+ documents: records,
285
+ });
286
+ `),
294
287
  searchRoute: (() => {
295
288
  const server = `import { createMixedbreadSearchAPI } from 'fumadocs-core/search/mixedbread';
296
289
  import Mixedbread from '@mixedbread/sdk';
297
290
 
298
291
  const server = createMixedbreadSearchAPI({
299
- client: new Mixedbread({
300
- apiKey: process.env.MIXEDBREAD_API_KEY,
301
- }),
292
+ client: new Mixedbread(),
302
293
  storeIdentifier: process.env.MIXEDBREAD_STORE_ID!,
303
294
  });
304
295
  `;
@@ -359,10 +350,7 @@ function templates(name, framework, { baseDir, source }, output) {
359
350
  const { head, documents } = provider.exportIndexes(sourceRef(source.dynamic), source.async);
360
351
  files.push([path.posix.join(baseDir, staticJson(framework).file), staticRoute(framework, head, documents)]);
361
352
  }
362
- if (provider.sync) files.push(["scripts/sync-content.ts", provider.sync(env, {
363
- dir: source.dir,
364
- output
365
- })]);
353
+ if (provider.sync) files.push(["scripts/sync-content.ts", provider.sync(env, { output })]);
366
354
  return files;
367
355
  }
368
356
  const search = {
@@ -435,4 +423,4 @@ async function removeDefaultSearch(ctx, file) {
435
423
  //#endregion
436
424
  export { syncCommand as a, staticRoute as i, staticJson as n, syncScript as o, staticOutput as r, templates as s, search as t };
437
425
 
438
- //# sourceMappingURL=search-BbzvfJal.js.map
426
+ //# sourceMappingURL=search-BCHEW0-K.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"search-BCHEW0-K.js","names":[],"sources":["../src/features/search.ts"],"sourcesContent":["import fs from 'node:fs/promises';\nimport path from 'node:path';\nimport type { Feature, FeatureContext } from '@/features';\nimport type { ReactFramework, SourceInfo } from '@/project';\nimport { formatRoute, routeModule } from '@/project/route';\nimport {\n addImport,\n addJsxAttribute,\n addTanstackPrerender,\n filterReactRouterPrerenderArray,\n filterReactRouterRoute,\n findJsxElement,\n} from '@/codemod';\nimport { docs } from './docs';\nimport {\n findSource,\n posix,\n reactFramework,\n registerReactRouterRoutes,\n scripts,\n type SourceRef,\n sourceRef,\n} from './utils';\n\n/** how client code reads public env variables */\ninterface Env {\n prefix: string;\n read: string;\n}\n\nconst publicEnv = (next: boolean): Env =>\n next\n ? { prefix: 'NEXT_PUBLIC_', read: 'process.env.NEXT_PUBLIC_' }\n : { prefix: 'VITE_', read: 'import.meta.env.VITE_' };\n\nconst dialogImports = `import {\n SearchDialog,\n SearchDialogClose,\n SearchDialogContent,\n SearchDialogFooter,\n SearchDialogHeader,\n SearchDialogIcon,\n SearchDialogInput,\n SearchDialogList,\n SearchDialogOverlay,\n type SharedProps,\n} from 'fumadocs-ui/components/dialog/search';\nimport { useI18n } from 'fumadocs-ui/contexts/i18n';`;\n\n/** `legacy` for hooks in the shape of the deprecated `useDocsSearch()`, like the Typesense adapter */\nconst dialogBody = (footer: string, url: string, legacy = false) => ` return (\n <SearchDialog ${legacy ? 'search={search} onSearchChange={setSearch} isLoading={query.isLoading}' : '{...search}'} {...props}>\n <SearchDialogOverlay />\n <SearchDialogContent>\n <SearchDialogHeader>\n <SearchDialogIcon />\n <SearchDialogInput />\n <SearchDialogClose />\n </SearchDialogHeader>\n <SearchDialogList ${legacy ? \"items={query.data !== 'empty' ? query.data : null} \" : ''}/>\n <SearchDialogFooter>\n <a\n href=\"${url}\"\n rel=\"noreferrer noopener\"\n className=\"ms-auto text-xs text-fd-muted-foreground\"\n >\n Search powered by ${footer}\n </a>\n </SearchDialogFooter>\n </SearchDialogContent>\n </SearchDialog>\n );\n}\n`;\n\n/** `structuredData` is a function on async collections (React Router & TanStack Start) */\nconst typesenseDocuments = (\n { ref, resolved }: SourceRef,\n async: boolean,\n) => `import { ${ref} } from '@/lib/source';\nimport type { DocumentRecord } from 'typesense-fumadocs-adapter';\n\nasync function getDocuments() {\n const documents: DocumentRecord[] = [];\n for (const page of ${resolved}.getPages()) {\n documents.push({\n _id: page.url,\n structured: ${async ? 'await page.data.structuredData()' : 'page.data.structuredData'},\n url: page.url,\n title: page.data.title,\n description: page.data.description,\n locale: page.locale,\n });\n }\n return documents;\n}`;\n\n/** `scripts/sync-content.ts`, syncing the search indexes pre-rendered to `output` */\nexport const syncScript = (\n output: string,\n imports: string,\n body: string,\n) => `import * as fs from 'node:fs/promises';\n${imports}\n\nconst records = JSON.parse(await fs.readFile('${output}', 'utf-8')) as DocumentRecord[];\n\n${body}\nconsole.log(\\`search updated: \\${records.length} records\\`);\n`;\n\ninterface Provider {\n label: string;\n hint: string;\n dependencies: Record<string, string | null>;\n /** keys exposed to the client, prefixed per framework */\n publicEnv: string[];\n privateEnv: string[];\n /** works without a server at runtime */\n static: boolean;\n dialog: (env: Env) => string;\n /** exports search indexes to a pre-rendered `static.json`, synced by `sync` after build */\n exportIndexes?: (\n src: SourceRef,\n async: boolean,\n ) => {\n /** code before the route handler */\n head: string;\n /** expression producing the documents */\n documents: string;\n };\n /** `scripts/sync-content.ts`, run after build */\n sync?: (env: Env, paths: { output: string }) => string;\n /** server-side search, replaces the default search route */\n searchRoute?: Record<ReactFramework, string>;\n}\n\nconst providers = {\n 'orama-cloud': {\n label: 'Orama Cloud',\n hint: 'signup needed',\n dependencies: { '@orama/core': null },\n publicEnv: ['ORAMA_DATASOURCE_ID', 'ORAMA_PROJECT_ID', 'ORAMA_API_KEY'],\n privateEnv: ['ORAMA_PRIVATE_API_KEY'],\n static: true,\n dialog: (env) => `'use client';\n${dialogImports}\nimport { useOramaCloudSearch } from 'fumadocs-core/search/client';\nimport { OramaCloud } from '@orama/core';\n\nconst client = new OramaCloud({\n projectId: ${env.read}ORAMA_PROJECT_ID!,\n apiKey: ${env.read}ORAMA_API_KEY!,\n});\n\nexport default function CustomSearchDialog(props: SharedProps) {\n const { locale } = useI18n();\n const search = useOramaCloudSearch({\n client,\n locale,\n });\n\n${dialogBody('Orama', 'https://orama.com')}`,\n exportIndexes: ({ ref }) => ({\n head: `import { ${ref} } from '@/lib/source';\nimport { toDocuments } from 'fumadocs-core/search/orama-cloud';`,\n documents: `toDocuments(${ref})`,\n }),\n sync: (env, { output }) =>\n syncScript(\n output,\n `import { type OramaDocument as DocumentRecord, sync } from 'fumadocs-core/search/orama-cloud';\nimport { OramaCloud } from '@orama/core';`,\n `const orama = new OramaCloud({\n projectId: process.env.${env.prefix}ORAMA_PROJECT_ID!,\n apiKey: process.env.ORAMA_PRIVATE_API_KEY!,\n});\n\nawait sync(orama, {\n index: process.env.${env.prefix}ORAMA_DATASOURCE_ID!,\n documents: records,\n});\n`,\n ),\n },\n algolia: {\n label: 'Algolia',\n hint: 'signup needed',\n dependencies: { algoliasearch: null },\n publicEnv: ['ALGOLIA_APP_ID', 'ALGOLIA_SEARCH_KEY'],\n privateEnv: ['ALGOLIA_WRITE_KEY'],\n static: true,\n dialog: (env) => `'use client';\n${dialogImports}\nimport { useAlgoliaSearch } from 'fumadocs-core/search/client';\nimport { liteClient } from 'algoliasearch/lite';\n\nconst algolia = liteClient(${env.read}ALGOLIA_APP_ID!, ${env.read}ALGOLIA_SEARCH_KEY!);\n\nexport default function CustomSearchDialog(props: SharedProps) {\n const { locale } = useI18n();\n const search = useAlgoliaSearch({\n client: algolia,\n indexName: 'document',\n locale,\n });\n\n${dialogBody('Algolia', 'https://algolia.com')}`,\n exportIndexes: ({ ref }) => ({\n head: `import { ${ref} } from '@/lib/source';\nimport { toDocuments } from 'fumadocs-core/search/algolia';`,\n documents: `toDocuments(${ref})`,\n }),\n sync: (env, { output }) =>\n syncScript(\n output,\n `import { type DocumentRecord, sync } from 'fumadocs-core/search/algolia';\nimport { algoliasearch } from 'algoliasearch';`,\n `const client = algoliasearch(\n process.env.${env.prefix}ALGOLIA_APP_ID!,\n process.env.ALGOLIA_WRITE_KEY!,\n);\n\nawait sync(client, {\n indexName: 'document',\n documents: records,\n});\n`,\n ),\n },\n meilisearch: {\n label: 'Meilisearch',\n hint: 'self-hosted or cloud',\n dependencies: { meilisearch: null },\n publicEnv: ['MEILISEARCH_HOST', 'MEILISEARCH_SEARCH_KEY'],\n privateEnv: ['MEILISEARCH_ADMIN_KEY'],\n static: true,\n dialog: (env) => `'use client';\n${dialogImports}\nimport { useMeilisearch } from 'fumadocs-core/search/client';\nimport { Meilisearch } from 'meilisearch';\n\nconst client = new Meilisearch({\n host: ${env.read}MEILISEARCH_HOST!,\n apiKey: ${env.read}MEILISEARCH_SEARCH_KEY!,\n});\n\nexport default function CustomSearchDialog(props: SharedProps) {\n const { locale } = useI18n();\n const search = useMeilisearch({\n client,\n indexName: 'docs',\n locale,\n });\n\n${dialogBody('Meilisearch', 'https://www.meilisearch.com')}`,\n exportIndexes: ({ ref }) => ({\n head: `import { ${ref} } from '@/lib/source';\nimport { toDocuments } from 'fumadocs-core/search/meilisearch';`,\n documents: `toDocuments(${ref})`,\n }),\n sync: (env, { output }) =>\n syncScript(\n output,\n `import { type DocumentRecord, sync } from 'fumadocs-core/search/meilisearch';\nimport { Meilisearch } from 'meilisearch';`,\n `const client = new Meilisearch({\n host: process.env.${env.prefix}MEILISEARCH_HOST!,\n apiKey: process.env.MEILISEARCH_ADMIN_KEY!,\n});\n\nawait sync(client, {\n indexName: 'docs',\n documents: records,\n});\n`,\n ),\n },\n typesense: {\n label: 'Typesense',\n hint: 'self-hosted or cloud, community adapter',\n dependencies: { typesense: null, 'typesense-fumadocs-adapter': null },\n publicEnv: ['TYPESENSE_URL', 'TYPESENSE_SEARCH_KEY'],\n privateEnv: ['TYPESENSE_API_KEY'],\n static: true,\n dialog: (env) => `'use client';\n${dialogImports}\nimport { Client } from 'typesense';\nimport { useTypesenseSearch } from 'typesense-fumadocs-adapter/client';\n\nconst client = new Client({\n nodes: [{ url: ${env.read}TYPESENSE_URL! }],\n apiKey: ${env.read}TYPESENSE_SEARCH_KEY!,\n});\n\nexport default function CustomSearchDialog(props: SharedProps) {\n const { locale } = useI18n();\n const { search, setSearch, query } = useTypesenseSearch({\n typesenseCollectionName: 'docs',\n locale,\n client,\n });\n\n${dialogBody('Typesense', 'https://typesense.org', true)}`,\n exportIndexes: (src, async) => ({\n head: typesenseDocuments(src, async),\n documents: 'getDocuments()',\n }),\n sync: (env, { output }) =>\n syncScript(\n output,\n `import { type DocumentRecord, sync } from 'typesense-fumadocs-adapter';\nimport { Client } from 'typesense';`,\n `const client = new Client({\n nodes: [{ url: process.env.${env.prefix}TYPESENSE_URL! }],\n apiKey: process.env.TYPESENSE_API_KEY!,\n connectionTimeoutSeconds: 60 * 15,\n});\n\nawait sync(client, {\n typesenseCollectionName: 'docs',\n documents: records,\n});\n`,\n ),\n },\n mixedbread: {\n label: 'Mixedbread',\n hint: 'AI search, signup needed',\n dependencies: { '@mixedbread/sdk': null },\n publicEnv: [],\n privateEnv: ['MXBAI_API_KEY', 'MIXEDBREAD_STORE_ID'],\n static: false,\n dialog: () => `'use client';\n${dialogImports}\nimport { useFetchSearch } from 'fumadocs-core/search/client';\n\nexport default function CustomSearchDialog(props: SharedProps) {\n const { locale } = useI18n();\n const search = useFetchSearch({\n api: '/api/search',\n locale,\n // every search is a request to Mixedbread\n delayMs: 300,\n });\n\n${dialogBody('Mixedbread', 'https://mixedbread.com')}`,\n exportIndexes: ({ ref }) => ({\n head: `import { ${ref} } from '@/lib/source';\nimport { toDocuments } from 'fumadocs-core/search/mixedbread';`,\n documents: `toDocuments(${ref})`,\n }),\n sync: (_env, { output }) =>\n syncScript(\n output,\n `import { type DocumentRecord, sync } from 'fumadocs-core/search/mixedbread';\nimport Mixedbread from '@mixedbread/sdk';`,\n `await sync(new Mixedbread(), {\n storeIdentifier: process.env.MIXEDBREAD_STORE_ID!,\n documents: records,\n});\n`,\n ),\n searchRoute: (() => {\n const server = `import { createMixedbreadSearchAPI } from 'fumadocs-core/search/mixedbread';\nimport Mixedbread from '@mixedbread/sdk';\n\nconst server = createMixedbreadSearchAPI({\n client: new Mixedbread(),\n storeIdentifier: process.env.MIXEDBREAD_STORE_ID!,\n});\n`;\n return {\n next: `${server}\nexport const { GET } = server;\n`,\n 'react-router': `import type { Route } from './+types/search';\n${server}\nexport async function loader({ request }: Route.LoaderArgs) {\n return server.GET(request);\n}\n`,\n 'tanstack-start': `import { createFileRoute } from '@tanstack/react-router';\n${server}\nexport const Route = createFileRoute('/api/search')({\n server: {\n handlers: {\n GET: async ({ request }) => server.GET(request),\n },\n },\n});\n`,\n waku: `${server}\nexport const { GET } = server;\n`,\n };\n })(),\n },\n} satisfies Record<string, Provider>;\n\nexport type SearchProvider = keyof typeof providers;\n\n/** pre-rendered on build to export the search indexes */\nexport const staticJson = (framework: ReactFramework) =>\n formatRoute({ segments: ['static.json'] }, framework, null);\n\nexport const staticRoute = (framework: ReactFramework, head: string, documents: string) =>\n routeModule(framework, staticJson(framework), {\n imports: head,\n body: `Response.json(await ${documents})`,\n revalidate: true,\n static: true,\n });\n\n/** where `static.json` is pre-rendered on build, `config` is the Vite config of TanStack Start */\nexport function staticOutput(framework: ReactFramework, config = ''): string {\n switch (framework) {\n case 'next':\n return '.next/server/app/static.json.body';\n case 'react-router':\n return 'build/client/static.json';\n case 'waku':\n return 'dist/public/static.json';\n case 'tanstack-start':\n // Nitro moves the client output to the directory of its preset\n if (/preset:\\s*['\"]vercel['\"]/.test(config)) return '.vercel/output/static/static.json';\n return /\\bnitro\\(/.test(config) ? '.output/public/static.json' : 'dist/client/static.json';\n }\n}\n\n/** runs the sync script after build */\nexport const syncCommand = 'node --env-file-if-exists=.env.local scripts/sync-content.ts';\n\n/** the files written by a provider, relative to the project, `output` is where `static.json` is pre-rendered */\nexport function templates(\n name: SearchProvider,\n framework: ReactFramework,\n { baseDir, source }: { baseDir: string; source: Pick<SourceInfo, 'dynamic' | 'async'> },\n output: string,\n): [file: string, content: string][] {\n const provider: Provider = providers[name];\n const env = publicEnv(framework === 'next');\n const files: [string, string][] = [\n [path.posix.join(baseDir, 'components/search.tsx'), provider.dialog(env)],\n ];\n\n if (provider.exportIndexes) {\n const { head, documents } = provider.exportIndexes(sourceRef(source.dynamic), source.async);\n files.push([\n path.posix.join(baseDir, staticJson(framework).file),\n staticRoute(framework, head, documents),\n ]);\n }\n if (provider.sync) files.push(['scripts/sync-content.ts', provider.sync(env, { output })]);\n return files;\n}\n\nexport const search: Feature<{ provider: SearchProvider }> = {\n id: 'search',\n title: 'Search',\n description: 'replace the default search with a 3rd party search solution',\n requires: [docs],\n supports: (project) =>\n project.source.loader ||\n project.source.dynamic ||\n '`lib/source.ts` must export a `source` loader',\n options: {\n provider: {\n message: 'Choose a search provider',\n choices: Object.entries(providers).map(([value, { label, hint }]) => ({\n value: value as SearchProvider,\n label,\n hint,\n })),\n },\n },\n async apply(ctx, { provider: name }) {\n const { project } = ctx;\n const { baseDir } = project;\n const framework = reactFramework(project);\n const provider: Provider = providers[name];\n if (project.static && !provider.static)\n throw new Error(`${provider.label} requires a server at runtime`);\n\n const config =\n framework === 'tanstack-start' && project.configFile\n ? await fs.readFile(path.join(project.cwd, project.configFile), 'utf-8')\n : '';\n const output = staticOutput(framework, config);\n\n ctx.addDependencies(provider.dependencies);\n for (const [file, content] of templates(name, framework, project, output))\n await ctx.write(file, content);\n\n // other routes (e.g. MCP) also create a search server, only match the search route itself\n const searchRoute = await findSource(\n project.cwd,\n path.join(baseDir, project.info.routesDir),\n 'createFromSource(',\n (file) => /(^|[\\\\/])search(\\.ts|[\\\\/]route\\.ts)$/.test(file),\n );\n if (provider.searchRoute) {\n if (searchRoute) await ctx.write(searchRoute, provider.searchRoute[framework]);\n else\n ctx.note(\n 'Create the search route with `createMixedbreadSearchAPI`, see https://fumadocs.dev/docs/headless/search/mixedbread.',\n );\n } else if (searchRoute) {\n await removeDefaultSearch(ctx, searchRoute);\n }\n\n if (provider.exportIndexes) {\n const route = staticJson(framework);\n if (framework === 'react-router') {\n await registerReactRouterRoutes(ctx, [route]);\n } else if (framework === 'tanstack-start' && project.configFile) {\n await ctx.source(project.configFile, (file) => addTanstackPrerender(file, [route.path]));\n }\n }\n\n const rootProvider = await findSource(project.cwd, baseDir, '<RootProvider');\n const edited =\n rootProvider !== undefined &&\n (await ctx.source(rootProvider, (file) => {\n const element = findJsxElement(file, 'RootProvider')?.openingElement;\n if (!element || file.code.slice(element.start, element.end).includes('search=')) return;\n addJsxAttribute(file, element, 'search={{ SearchDialog }}');\n addImport(file, { from: '@/components/search', default: 'SearchDialog' });\n }));\n if (!edited)\n ctx.note(\n 'Pass `search={{ SearchDialog }}` from `@/components/search` to `<RootProvider />`.',\n );\n\n if (provider.sync) {\n await ctx.packageJson((data) => {\n const build = data.scripts?.build ?? '';\n if (build.includes('sync-content')) return;\n scripts(data, { build: build ? `${build} && ${syncCommand}` : syncCommand });\n });\n }\n\n const env = publicEnv(framework === 'next');\n for (const key of provider.publicEnv) ctx.env(env.prefix + key, '');\n for (const key of provider.privateEnv) ctx.env(key, '');\n ctx.note(\n `Fill the ${provider.label} keys in \\`.env.local\\`, the search index is synced after each build.\\nSee https://fumadocs.dev/docs/headless/search/${name}.`,\n );\n },\n};\n\n/** remove the default search route, which conflicts with the search provider */\nasync function removeDefaultSearch(ctx: FeatureContext, file: string) {\n const { baseDir, framework } = ctx.project;\n await ctx.remove(file);\n if (framework !== 'react-router') return;\n\n const entry = posix(path.relative(baseDir, file));\n await ctx.source(path.join(baseDir, 'routes.ts'), (source) => {\n filterReactRouterRoute(source, (item) => item.entry !== entry);\n });\n await ctx.source('react-router.config.ts', (source) => {\n filterReactRouterPrerenderArray(source, 'excluded', (item) => item !== '/api/search');\n });\n}\n"],"mappings":"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"}
@@ -1,6 +1,6 @@
1
1
  import { b as findJsxElement, h as appendJsxChildren, p as addImport } from "./codemod-auol-p4R.js";
2
2
  import { a as reactFramework, c as requiresMarkdown, r as findSource } from "./utils-DbMe7h6c.js";
3
- import { r as llms } from "./llms-Br0RJXXg.js";
3
+ import { r as llms } from "./llms-DQzAst6R.js";
4
4
  import path from "node:path";
5
5
  //#region src/features/webmcp.ts
6
6
  const component = ({ static: isStatic, i18n, tanstack }) => `'use client';
@@ -132,4 +132,4 @@ const webmcp = {
132
132
  //#endregion
133
133
  export { webmcp as n, component as t };
134
134
 
135
- //# sourceMappingURL=webmcp-DyTLrsXL.js.map
135
+ //# sourceMappingURL=webmcp-I9L7xzmw.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"webmcp-DyTLrsXL.js","names":[],"sources":["../src/features/webmcp.ts"],"sourcesContent":["import path from 'node:path';\nimport type { Feature } from '@/features';\nimport { addImport, appendJsxChildren, findJsxElement } from '@/codemod';\nimport { llms } from './llms';\nimport { findSource, reactFramework, requiresMarkdown } from './utils';\n\ninterface Input {\n static: boolean;\n i18n: boolean;\n /** the Markdown route of TanStack Start is `/docs/*.md` */\n tanstack: boolean;\n}\n\nexport const component = ({ static: isStatic, i18n, tanstack }: Input) => `'use client';\nimport { useEffect } from 'react';\nimport { useRouter } from 'fumadocs-core/framework';\nimport { ${isStatic ? 'staticClient' : 'fetchClient'} } from 'fumadocs-core/search/client/${isStatic ? 'orama-static' : 'fetch'}';\n${i18n ? \"import { useI18n } from 'fumadocs-ui/contexts/i18n';\\n\" : ''}import { ${tanstack ? 'docsRoute' : 'docsContentRoute, docsRoute'} } from '@/lib/shared';\n\ninterface ModelContextTool<Input> {\n name: string;\n description: string;\n inputSchema?: object;\n execute: (input: Input) => Promise<unknown>;\n}\n\ndeclare global {\n interface Document {\n /** WebMCP, see https://webmachinelearning.github.io/webmcp/ */\n modelContext?: {\n registerTool: <Input>(\n tool: ModelContextTool<Input>,\n options?: { signal?: AbortSignal },\n ) => Promise<void>;\n };\n }\n}\n\n/** the Markdown URL of a page, e.g. ${tanstack ? '`/docs/page` -> `/docs/page.md`' : '`/docs/page` -> `/llms.mdx/docs/page/content.md`'} */\nfunction markdownUrl(url: string) {\n${\n tanstack\n ? ' return url.endsWith(docsRoute) ? `${url}/index.md` : `${url}.md`;'\n : ` const start = url.indexOf(docsRoute);\n const slugs = url.slice(start + docsRoute.length).replace(/^\\\\//, '');\n return [url.slice(0, start) + docsContentRoute, slugs, 'content.md'].filter(Boolean).join('/');`\n}\n}\n\n/** expose the docs to AI agents in the browser */\nexport function WebMCP() {\n const router = useRouter();\n${i18n ? ' const { locale } = useI18n();\\n' : ''}\n useEffect(() => {\n const context = document.modelContext;\n if (!context) return;\n const controller = new AbortController();\n const options = { signal: controller.signal };\n const client = ${isStatic ? 'staticClient' : 'fetchClient'}(${i18n ? '{ locale }' : ''});\n\n void context.registerTool(\n {\n name: 'search_docs',\n description: 'Search the docs, returns matching pages and headings with their URLs',\n inputSchema: {\n type: 'object',\n properties: { query: { type: 'string' } },\n required: ['query'],\n },\n async execute({ query }: { query: string }) {\n return client.search(query);\n },\n },\n options,\n );\n void context.registerTool(\n {\n name: 'read_page',\n description: 'Read a docs page as Markdown by its URL',\n inputSchema: {\n type: 'object',\n properties: { url: { type: 'string' } },\n required: ['url'],\n },\n async execute({ url }: { url: string }) {\n const res = await fetch(markdownUrl(url));\n if (!res.ok) throw new Error(\\`page not found: \\${url}\\`);\n return res.text();\n },\n },\n options,\n );\n void context.registerTool(\n {\n name: 'open_page',\n description: 'Navigate to a docs page by its URL',\n inputSchema: {\n type: 'object',\n properties: { url: { type: 'string' } },\n required: ['url'],\n },\n async execute({ url }: { url: string }) {\n await router.push(url);\n return \\`opened \\${url}\\`;\n },\n },\n options,\n );\n\n return () => controller.abort();\n }, [router${i18n ? ', locale' : ''}]);\n\n return null;\n}\n`;\n\nexport const webmcp: Feature = {\n id: 'webmcp',\n title: 'WebMCP',\n description:\n 'expose tools to AI agents in the browser (experimental): search, read and open docs pages',\n requires: [llms],\n supports: requiresMarkdown,\n async apply(ctx) {\n const { cwd, baseDir, i18n } = ctx.project;\n const framework = reactFramework(ctx.project);\n await ctx.write(\n path.join(baseDir, 'components/webmcp.tsx'),\n component({\n static: ctx.project.static,\n i18n: i18n !== null,\n tanstack: framework === 'tanstack-start',\n }),\n );\n\n const provider = await findSource(cwd, baseDir, '<RootProvider');\n const edited =\n provider !== undefined &&\n (await ctx.source(provider, (file) => {\n if (file.code.includes('<WebMCP')) return;\n const element = findJsxElement(file, 'RootProvider');\n if (!element) return;\n appendJsxChildren(file, element, '<WebMCP />');\n addImport(file, { from: '@/components/webmcp', named: ['WebMCP'] });\n }));\n if (!edited)\n ctx.note('Render `<WebMCP />` from `@/components/webmcp` inside `<RootProvider />`.');\n\n ctx.note(\n 'WebMCP is experimental, test it with the `#enable-webmcp-testing` flag of Chrome. The `search_docs` tool uses the search API route, see https://fumadocs.dev/docs/integrations/llms#webmcp.',\n );\n },\n};\n"],"mappings":";;;;;AAaA,MAAa,aAAa,EAAE,QAAQ,UAAU,MAAM,eAAsB;;;WAG/D,WAAW,iBAAiB,cAAc,uCAAuC,WAAW,iBAAiB,QAAQ;EAC9H,OAAO,2DAA2D,GAAG,WAAW,WAAW,cAAc,8BAA8B;;;;;;;;;;;;;;;;;;;;;uCAqBlG,WAAW,oCAAoC,mDAAmD;;EAGvI,WACI,wEACA;;mGAGL;;;;;;EAMC,OAAO,sCAAsC,GAAG;;;;;;qBAM7B,WAAW,iBAAiB,cAAc,GAAG,OAAO,eAAe,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;cAoD7E,OAAO,aAAa,GAAG;;;;;AAMrC,MAAa,SAAkB;CAC7B,IAAI;CACJ,OAAO;CACP,aACE;CACF,UAAU,CAAC,IAAI;CACf,UAAU;CACV,MAAM,MAAM,KAAK;EACf,MAAM,EAAE,KAAK,SAAS,SAAS,IAAI;EACnC,MAAM,YAAY,eAAe,IAAI,OAAO;EAC5C,MAAM,IAAI,MACR,KAAK,KAAK,SAAS,uBAAuB,GAC1C,UAAU;GACR,QAAQ,IAAI,QAAQ;GACpB,MAAM,SAAS;GACf,UAAU,cAAc;EAC1B,CAAC,CACH;EAEA,MAAM,WAAW,MAAM,WAAW,KAAK,SAAS,eAAe;EAU/D,IAAI,EARF,aAAa,KAAA,KACZ,MAAM,IAAI,OAAO,WAAW,SAAS;GACpC,IAAI,KAAK,KAAK,SAAS,SAAS,GAAG;GACnC,MAAM,UAAU,eAAe,MAAM,cAAc;GACnD,IAAI,CAAC,SAAS;GACd,kBAAkB,MAAM,SAAS,YAAY;GAC7C,UAAU,MAAM;IAAE,MAAM;IAAuB,OAAO,CAAC,QAAQ;GAAE,CAAC;EACpE,CAAC,IAED,IAAI,KAAK,2EAA2E;EAEtF,IAAI,KACF,6LACF;CACF;AACF"}
1
+ {"version":3,"file":"webmcp-I9L7xzmw.js","names":[],"sources":["../src/features/webmcp.ts"],"sourcesContent":["import path from 'node:path';\nimport type { Feature } from '@/features';\nimport { addImport, appendJsxChildren, findJsxElement } from '@/codemod';\nimport { llms } from './llms';\nimport { findSource, reactFramework, requiresMarkdown } from './utils';\n\ninterface Input {\n static: boolean;\n i18n: boolean;\n /** the Markdown route of TanStack Start is `/docs/*.md` */\n tanstack: boolean;\n}\n\nexport const component = ({ static: isStatic, i18n, tanstack }: Input) => `'use client';\nimport { useEffect } from 'react';\nimport { useRouter } from 'fumadocs-core/framework';\nimport { ${isStatic ? 'staticClient' : 'fetchClient'} } from 'fumadocs-core/search/client/${isStatic ? 'orama-static' : 'fetch'}';\n${i18n ? \"import { useI18n } from 'fumadocs-ui/contexts/i18n';\\n\" : ''}import { ${tanstack ? 'docsRoute' : 'docsContentRoute, docsRoute'} } from '@/lib/shared';\n\ninterface ModelContextTool<Input> {\n name: string;\n description: string;\n inputSchema?: object;\n execute: (input: Input) => Promise<unknown>;\n}\n\ndeclare global {\n interface Document {\n /** WebMCP, see https://webmachinelearning.github.io/webmcp/ */\n modelContext?: {\n registerTool: <Input>(\n tool: ModelContextTool<Input>,\n options?: { signal?: AbortSignal },\n ) => Promise<void>;\n };\n }\n}\n\n/** the Markdown URL of a page, e.g. ${tanstack ? '`/docs/page` -> `/docs/page.md`' : '`/docs/page` -> `/llms.mdx/docs/page/content.md`'} */\nfunction markdownUrl(url: string) {\n${\n tanstack\n ? ' return url.endsWith(docsRoute) ? `${url}/index.md` : `${url}.md`;'\n : ` const start = url.indexOf(docsRoute);\n const slugs = url.slice(start + docsRoute.length).replace(/^\\\\//, '');\n return [url.slice(0, start) + docsContentRoute, slugs, 'content.md'].filter(Boolean).join('/');`\n}\n}\n\n/** expose the docs to AI agents in the browser */\nexport function WebMCP() {\n const router = useRouter();\n${i18n ? ' const { locale } = useI18n();\\n' : ''}\n useEffect(() => {\n const context = document.modelContext;\n if (!context) return;\n const controller = new AbortController();\n const options = { signal: controller.signal };\n const client = ${isStatic ? 'staticClient' : 'fetchClient'}(${i18n ? '{ locale }' : ''});\n\n void context.registerTool(\n {\n name: 'search_docs',\n description: 'Search the docs, returns matching pages and headings with their URLs',\n inputSchema: {\n type: 'object',\n properties: { query: { type: 'string' } },\n required: ['query'],\n },\n async execute({ query }: { query: string }) {\n return client.search(query);\n },\n },\n options,\n );\n void context.registerTool(\n {\n name: 'read_page',\n description: 'Read a docs page as Markdown by its URL',\n inputSchema: {\n type: 'object',\n properties: { url: { type: 'string' } },\n required: ['url'],\n },\n async execute({ url }: { url: string }) {\n const res = await fetch(markdownUrl(url));\n if (!res.ok) throw new Error(\\`page not found: \\${url}\\`);\n return res.text();\n },\n },\n options,\n );\n void context.registerTool(\n {\n name: 'open_page',\n description: 'Navigate to a docs page by its URL',\n inputSchema: {\n type: 'object',\n properties: { url: { type: 'string' } },\n required: ['url'],\n },\n async execute({ url }: { url: string }) {\n await router.push(url);\n return \\`opened \\${url}\\`;\n },\n },\n options,\n );\n\n return () => controller.abort();\n }, [router${i18n ? ', locale' : ''}]);\n\n return null;\n}\n`;\n\nexport const webmcp: Feature = {\n id: 'webmcp',\n title: 'WebMCP',\n description:\n 'expose tools to AI agents in the browser (experimental): search, read and open docs pages',\n requires: [llms],\n supports: requiresMarkdown,\n async apply(ctx) {\n const { cwd, baseDir, i18n } = ctx.project;\n const framework = reactFramework(ctx.project);\n await ctx.write(\n path.join(baseDir, 'components/webmcp.tsx'),\n component({\n static: ctx.project.static,\n i18n: i18n !== null,\n tanstack: framework === 'tanstack-start',\n }),\n );\n\n const provider = await findSource(cwd, baseDir, '<RootProvider');\n const edited =\n provider !== undefined &&\n (await ctx.source(provider, (file) => {\n if (file.code.includes('<WebMCP')) return;\n const element = findJsxElement(file, 'RootProvider');\n if (!element) return;\n appendJsxChildren(file, element, '<WebMCP />');\n addImport(file, { from: '@/components/webmcp', named: ['WebMCP'] });\n }));\n if (!edited)\n ctx.note('Render `<WebMCP />` from `@/components/webmcp` inside `<RootProvider />`.');\n\n ctx.note(\n 'WebMCP is experimental, test it with the `#enable-webmcp-testing` flag of Chrome. The `search_docs` tool uses the search API route, see https://fumadocs.dev/docs/integrations/llms#webmcp.',\n );\n },\n};\n"],"mappings":";;;;;AAaA,MAAa,aAAa,EAAE,QAAQ,UAAU,MAAM,eAAsB;;;WAG/D,WAAW,iBAAiB,cAAc,uCAAuC,WAAW,iBAAiB,QAAQ;EAC9H,OAAO,2DAA2D,GAAG,WAAW,WAAW,cAAc,8BAA8B;;;;;;;;;;;;;;;;;;;;;uCAqBlG,WAAW,oCAAoC,mDAAmD;;EAGvI,WACI,wEACA;;mGAGL;;;;;;EAMC,OAAO,sCAAsC,GAAG;;;;;;qBAM7B,WAAW,iBAAiB,cAAc,GAAG,OAAO,eAAe,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;cAoD7E,OAAO,aAAa,GAAG;;;;;AAMrC,MAAa,SAAkB;CAC7B,IAAI;CACJ,OAAO;CACP,aACE;CACF,UAAU,CAAC,IAAI;CACf,UAAU;CACV,MAAM,MAAM,KAAK;EACf,MAAM,EAAE,KAAK,SAAS,SAAS,IAAI;EACnC,MAAM,YAAY,eAAe,IAAI,OAAO;EAC5C,MAAM,IAAI,MACR,KAAK,KAAK,SAAS,uBAAuB,GAC1C,UAAU;GACR,QAAQ,IAAI,QAAQ;GACpB,MAAM,SAAS;GACf,UAAU,cAAc;EAC1B,CAAC,CACH;EAEA,MAAM,WAAW,MAAM,WAAW,KAAK,SAAS,eAAe;EAU/D,IAAI,EARF,aAAa,KAAA,KACZ,MAAM,IAAI,OAAO,WAAW,SAAS;GACpC,IAAI,KAAK,KAAK,SAAS,SAAS,GAAG;GACnC,MAAM,UAAU,eAAe,MAAM,cAAc;GACnD,IAAI,CAAC,SAAS;GACd,kBAAkB,MAAM,SAAS,YAAY;GAC7C,UAAU,MAAM;IAAE,MAAM;IAAuB,OAAO,CAAC,QAAQ;GAAE,CAAC;EACpE,CAAC,IAED,IAAI,KAAK,2EAA2E;EAEtF,IAAI,KACF,6LACF;CACF;AACF"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fumadocs/cli",
3
- "version": "1.7.3",
3
+ "version": "1.7.4",
4
4
  "description": "The CLI tool for Fumadocs",
5
5
  "keywords": [
6
6
  "Docs",
@@ -1,27 +0,0 @@
1
- import { t as docs } from "./docs--WqEHTyl.js";
2
- import { t as ai } from "./ai-BAvzj17c.js";
3
- import { r as llms } from "./llms-Br0RJXXg.js";
4
- import { t as og } from "./og-LFlX1Bl4.js";
5
- import { t as search } from "./search-BbzvfJal.js";
6
- import { t as mcp } from "./mcp-Ce_W93we.js";
7
- import { n as webmcp } from "./webmcp-DyTLrsXL.js";
8
- import { t as feedback } from "./feedback-Brj092vC.js";
9
- import { t as epub } from "./epub-jc_2B886.js";
10
- import { t as lint } from "./lint-zkXUULgO.js";
11
- //#region src/features/all.ts
12
- const features = [
13
- docs,
14
- ai,
15
- llms,
16
- mcp,
17
- webmcp,
18
- og,
19
- search,
20
- feedback,
21
- epub,
22
- lint
23
- ];
24
- //#endregion
25
- export { features as t };
26
-
27
- //# sourceMappingURL=all-4XIIhn0t.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"docs--WqEHTyl.js","names":[],"sources":["../src/project/route.ts","../src/features/docs/templates.ts","../src/features/docs/index.ts"],"sourcesContent":["import type { I18nInfo, ReactFramework } from '@/project';\n\nexport type RouteSegment =\n /** static path, can contain multiple segments like `/llms.mdx/docs` */\n | string\n | {\n param: string;\n catchAll?: boolean;\n /** the catch-all also matches zero segments */\n optional?: boolean;\n /** static file name after the catch-all like `content.md`, part of the parameter when the framework can't express it */\n suffix?: string;\n };\n\nexport interface RouteDescriptor {\n segments: RouteSegment[];\n /** under the locale parameter when the project has i18n */\n locale?: boolean;\n}\n\nexport interface FormattedRoute {\n /** route file, relative to the base dir */\n file: string;\n /** route string of the framework, e.g. for `RouteContext<'...'>`, `createFileRoute('...')`, `route('...')` or `ApiContext<'...'>` */\n path: string;\n /** path-to-regexp pattern, for the Next.js proxy matcher and rewrites */\n pattern: string;\n}\n\ninterface Convention {\n locale: (optional: boolean) => string;\n param: (name: string, catchAll: boolean, optional: boolean, suffix?: string) => string;\n file: (segments: string[], ext: string) => string;\n path: (segments: string[]) => string;\n}\n\nconst conventions: Record<ReactFramework, Convention> = {\n next: {\n locale: () => '[lang]',\n param: (name, catchAll, optional) =>\n catchAll ? (optional ? `[[...${name}]]` : `[...${name}]`) : `[${name}]`,\n file: (segments, ext) => `app/${segments.join('/')}/route.${ext}`,\n path: (segments) => `/${segments.join('/')}`,\n },\n 'react-router': {\n locale: (optional) => (optional ? ':lang?' : ':lang'),\n param: (name, catchAll) => (catchAll ? '*' : `:${name}`),\n // `routes.ts` maps paths to files, name files after the static segments like flat routes\n file: (segments, ext) =>\n `routes/${segments.filter((segment) => !/^[:*]/.test(segment)).join('.')}.${ext}`,\n path: (segments) => segments.join('/'),\n },\n 'tanstack-start': {\n locale: (optional) => (optional ? '{-$lang}' : '$lang'),\n param: (name, catchAll) => (catchAll ? '$' : `$${name}`),\n file: (segments, ext) => `routes/${segments.join('/').replaceAll('.', '[.]')}.${ext}`,\n path: (segments) => `/${segments.join('/')}`,\n },\n waku: {\n locale: () => '[lang]',\n param: (name, catchAll, _optional, suffix) =>\n catchAll ? `[...${name}]${suffix ? `/${suffix}` : ''}` : `[${name}]`,\n file: (segments, ext) => `pages/_api/${segments.join('/')}.${ext}`,\n path: (segments) => `/${segments.join('/')}`,\n },\n};\n\n/** the locale segment of route files, with a trailing slash */\nexport function localeSegment(framework: ReactFramework, i18n: I18nInfo | null) {\n return i18n ? `${conventions[framework].locale(i18n.optionalLocale)}/` : '';\n}\n\nexport function formatRoute(\n route: RouteDescriptor,\n framework: ReactFramework,\n i18n: I18nInfo | null,\n ext = 'ts',\n): FormattedRoute {\n const convention = conventions[framework];\n const segments: string[] = [];\n const patterns: string[] = [];\n if (route.locale && i18n) {\n segments.push(convention.locale(i18n.optionalLocale));\n patterns.push(':lang');\n }\n\n for (const segment of route.segments) {\n if (typeof segment === 'string') {\n for (const part of segment.split('/')) {\n if (part.length === 0) continue;\n segments.push(part);\n patterns.push(part);\n }\n } else {\n const { param, catchAll = false, optional = false, suffix } = segment;\n segments.push(convention.param(param, catchAll, optional, suffix));\n patterns.push(catchAll ? `:${param}*` : `:${param}`);\n }\n }\n\n return {\n file: convention.file(segments, ext),\n path: convention.path(segments),\n pattern: `/${patterns.join('/')}`,\n };\n}\n\nexport interface RouteModule {\n /** import statements of the handler */\n imports: string;\n /** expression returning a `Response` */\n body: string;\n /** the handler reads `request` */\n request?: boolean;\n /** @defaultValue `['GET']` */\n methods?: string[];\n /** Next.js: never revalidate the route */\n revalidate?: boolean;\n /** Waku: prerender the route */\n static?: boolean;\n}\n\n/** the route module of a handler that only reads the request, per framework */\nexport function routeModule(\n framework: ReactFramework,\n route: FormattedRoute,\n mod: RouteModule,\n): string {\n const { imports, body, request, methods = ['GET'], revalidate, static: isStatic } = mod;\n const arg = request ? 'request: Request' : '';\n const head = `${imports.trim()}\\n\\n`;\n\n if (framework === 'react-router') {\n const out: string[] = [];\n // GET is the loader, every other method is the action\n out.push(handler('loader', 'Route.LoaderArgs'));\n if (methods.length > 1) out.push(handler('action', 'Route.ActionArgs'));\n\n return `${request ? `${reactRouterTypes(route)}\\n` : ''}${head}${out.join('\\n')}`;\n }\n\n if (framework === 'tanstack-start') {\n const out: string[] = [];\n for (const method of methods)\n out.push(` ${method}: async (${request ? '{ request }' : ''}) => ${body},`);\n\n return `import { createFileRoute } from '@tanstack/react-router';\n${head}export const Route = createFileRoute('${route.path}')({\n server: {\n handlers: {\n${out.join('\\n')}\n },\n },\n});\n`;\n }\n\n const out: string[] = [];\n if (framework === 'next' && revalidate) out.push('export const revalidate = false;\\n');\n for (const method of methods)\n out.push(`export async function ${method}(${arg}) {\n return ${body};\n}\n`);\n if (framework === 'waku' && isStatic)\n out.push(`export async function getConfig() {\n return {\n render: 'static' as const,\n } as const;\n}\n`);\n\n return `${head}${out.join('\\n')}`;\n\n function handler(name: string, type: string) {\n return `export async function ${name}(${request ? `{ request }: ${type}` : ''}) {\n return ${body};\n}\n`;\n }\n}\n\n/** import of the generated types of a React Router route module */\nexport const reactRouterTypes = (route: FormattedRoute) =>\n `import type { Route } from './+types/${route.file\n .split('/')\n .pop()!\n .replace(/\\.tsx?$/, '')}';`;\n","import type { I18nInfo, ReactFramework } from '@/project';\nimport { localeSegment } from '@/project/route';\n\nexport interface TemplateInput {\n static: boolean;\n i18n: I18nInfo | null;\n /** import specifier of `RootProvider` */\n provider: string;\n /** extra props of `RootProvider` */\n providerProps: string;\n}\n\n/** files relative to the base dir */\nexport type Template = (input: TemplateInput) => Record<string, string>;\n\nconst wrapper = (children: string) =>\n `<div className=\"flex flex-col min-h-screen\">${children}</div>`;\n\nconst i18nConfig = (optionalLocale: boolean) => `import { defineI18n } from 'fumadocs-core/i18n';\n\nexport const i18n = defineI18n({\n defaultLanguage: 'en',\n languages: ['en', 'cn'],${optionalLocale ? \"\\n hideLocale: 'default-locale',\" : ''}\n});\n`;\n\nconst sharedConstants = `export const docsRoute = '/docs';\n`;\n\nconst source = (async: boolean, i18n: boolean) => `import { loader } from 'fumadocs-core/source';\nimport { defineDocs } from 'fumadocs-mdx/macro';\nimport { docsRoute } from './shared';\n${i18n ? \"import { i18n } from '@/lib/i18n';\\n\" : ''}\nexport const docs = defineDocs({\n dir: 'content/docs',${async ? '\\n docs: {\\n async: true,\\n },' : ''}\n});\n\nexport const source = loader({\n baseUrl: docsRoute,\n source: docs.toFumadocsSource(),${i18n ? '\\n i18n,' : ''}\n});\n`;\n\nconst layoutShared = (i18n: boolean) =>\n i18n\n ? `import type { BaseLayoutProps } from 'fumadocs-ui/layouts/shared';\nimport { uiTranslations } from 'fumadocs-ui/i18n';\nimport { i18n } from '@/lib/i18n';\n\nexport const translations = i18n.translations().extend(uiTranslations());\n\nexport function baseOptions(locale: string = i18n.defaultLanguage): BaseLayoutProps {\n return {\n nav: {\n title: 'My App',\n url: \\`/\\${locale}\\`,\n },\n };\n}\n`\n : `import type { BaseLayoutProps } from 'fumadocs-ui/layouts/shared';\n\nexport function baseOptions(): BaseLayoutProps {\n return {\n nav: {\n title: 'My App',\n },\n };\n}\n`;\n\nconst mdx = `import defaultMdxComponents from 'fumadocs-ui/mdx';\nimport type { MDXComponents } from 'mdx/types';\n\nexport function getMDXComponents(components?: MDXComponents) {\n return {\n ...defaultMdxComponents,\n ...components,\n } satisfies MDXComponents;\n}\n\nexport const useMDXComponents = getMDXComponents;\n\ndeclare global {\n type MDXProvidedComponents = ReturnType<typeof getMDXComponents>;\n}\n`;\n\nconst search = (i18n: boolean) => `'use client';\nimport {\n SearchDialog,\n SearchDialogClose,\n SearchDialogContent,\n SearchDialogHeader,\n SearchDialogIcon,\n SearchDialogInput,\n SearchDialogList,\n SearchDialogOverlay,\n type SharedProps,\n} from 'fumadocs-ui/components/dialog/search';\nimport { useDocsSearch } from 'fumadocs-core/search/client';\nimport { staticClient } from 'fumadocs-core/search/client/orama-static';\n${i18n ? \"import { useI18n } from 'fumadocs-ui/contexts/i18n';\\n\" : ''}\nexport default function DefaultSearchDialog(props: SharedProps) {\n${i18n ? ' const { locale } = useI18n();\\n' : ''} const { search, setSearch, query } = useDocsSearch({\n client: staticClient(${i18n ? '{ locale }' : ''}),\n });\n\n return (\n <SearchDialog search={search} onSearchChange={setSearch} isLoading={query.isLoading} {...props}>\n <SearchDialogOverlay />\n <SearchDialogContent>\n <SearchDialogHeader>\n <SearchDialogIcon />\n <SearchDialogInput />\n <SearchDialogClose />\n </SearchDialogHeader>\n <SearchDialogList items={query.data !== 'empty' ? query.data : null} />\n </SearchDialogContent>\n </SearchDialog>\n );\n}\n`;\n\nfunction shared(input: TemplateInput, async: boolean): Record<string, string> {\n const i18n = input.i18n !== null;\n return {\n 'lib/shared.ts': sharedConstants,\n 'lib/source.ts': source(async, i18n),\n 'lib/layout.shared.tsx': layoutShared(i18n),\n 'components/mdx.tsx': mdx,\n ...(input.static ? { 'components/search.tsx': search(i18n) } : {}),\n ...(input.i18n ? { 'lib/i18n.ts': i18nConfig(input.i18n.optionalLocale) } : {}),\n };\n}\n\nconst searchImport = (input: TemplateInput) =>\n input.static ? `import SearchDialog from '@/components/search';\\n` : '';\n\n/** imports of the root provider, with the i18n provider */\nconst providerImports = (input: TemplateInput) =>\n `import { RootProvider } from '${input.provider}';\n${searchImport(input)}${\n input.i18n\n ? `import { i18nProvider } from 'fumadocs-ui/i18n';\nimport { translations } from '@/lib/layout.shared';\nimport { i18n } from '@/lib/i18n';\n`\n : ''\n }`;\n\nconst providerProps = (input: TemplateInput) =>\n `${input.i18n ? ' i18n={i18nProvider(translations, lang)}' : ''}${input.providerProps}`;\n\n/** the docs page for React Router & TanStack Start, where MDX content is loaded lazily on client */\nconst clientContent = `function Content({ path }: { path: string }) {\n const page = docs.getPage(path);\n if (!page) throw new Error(\\`unknown page: \\${path}\\`);\n\n const { toc } = use(page.load());\n const MDX = page.body;\n\n return (\n <DocsPage toc={toc}>\n <DocsTitle>{page.title}</DocsTitle>\n <DocsDescription>{page.description}</DocsDescription>\n <DocsBody>\n <MDX components={useMDXComponents()} />\n </DocsBody>\n </DocsPage>\n );\n}`;\n\nconst searchRoute = {\n next: (input: TemplateInput) => `import { source } from '@/lib/source';\nimport { createFromSource } from 'fumadocs-core/search/server';\n${input.static ? '\\nexport const revalidate = false;\\n' : ''}\nexport const { ${input.static ? 'staticGET: GET' : 'GET'} } = createFromSource(source);\n`,\n waku: `import { createFromSource } from 'fumadocs-core/search/server';\nimport { source } from '@/lib/source';\n\nexport const { GET } = createFromSource(source);\n`,\n};\n\nexport const templates: Record<ReactFramework, Template> = {\n next: (input) => {\n const i18n = input.i18n !== null;\n // route group with `[lang]` segment for i18n\n const dir = i18n ? 'app/(docs)/[lang]' : 'app/(docs)';\n const route = i18n ? '/[lang]' : '';\n const lang = i18n ? ', params.lang' : '';\n\n return {\n ...shared(input, false),\n [`${dir}/layout.tsx`]: input.static\n ? `'use client';\n${providerImports(input)}${i18n ? \"import { use } from 'react';\\n\" : ''}\nexport default function Layout({ ${i18n ? 'params, ' : ''}children }: LayoutProps<'${route || '/'}'>) {\n${i18n ? ' const { lang } = use(params);\\n\\n' : ''} return (\n <RootProvider${providerProps(input)}>\n ${wrapper('{children}')}\n </RootProvider>\n );\n}\n`\n : `${providerImports(input)}\nexport default ${i18n ? 'async ' : ''}function Layout({ ${i18n ? 'params, ' : ''}children }: LayoutProps<'${route || '/'}'>) {\n${i18n ? ' const { lang } = await params;\\n\\n' : ''} return (\n <RootProvider${providerProps(input)}>\n ${wrapper('{children}')}\n </RootProvider>\n );\n}\n`,\n [`${dir}/docs/layout.tsx`]: `import { source } from '@/lib/source';\nimport { DocsLayout } from 'fumadocs-ui/layouts/docs';\nimport { baseOptions } from '@/lib/layout.shared';\n\nexport default ${i18n ? 'async ' : ''}function Layout({ ${i18n ? 'params, ' : ''}children }: LayoutProps<'${route}/docs'>) {\n${i18n ? ' const { lang } = await params;\\n\\n' : ''} return (\n <DocsLayout tree={source.getPageTree(${i18n ? 'lang' : ''})} {...baseOptions(${i18n ? 'lang' : ''})}>\n {children}\n </DocsLayout>\n );\n}\n`,\n [`${dir}/docs/[[...slug]]/page.tsx`]: `import { source } from '@/lib/source';\nimport { DocsBody, DocsDescription, DocsPage, DocsTitle } from 'fumadocs-ui/layouts/docs/page';\nimport { notFound } from 'next/navigation';\nimport { getMDXComponents } from '@/components/mdx';\nimport type { Metadata } from 'next';\nimport { createRelativeLink } from 'fumadocs-ui/mdx';\n\nexport default async function Page(props: PageProps<'${route}/docs/[[...slug]]'>) {\n const params = await props.params;\n const page = source.getPage(params.slug${lang});\n if (!page) notFound();\n\n const MDX = page.data.body;\n\n return (\n <DocsPage toc={page.data.toc} full={page.data.full}>\n <DocsTitle>{page.data.title}</DocsTitle>\n <DocsDescription>{page.data.description}</DocsDescription>\n <DocsBody>\n <MDX\n components={getMDXComponents({\n // this allows you to link to other pages with relative file paths\n a: createRelativeLink(source, page),\n })}\n />\n </DocsBody>\n </DocsPage>\n );\n}\n\nexport async function generateStaticParams() {\n return source.generateParams();\n}\n\nexport async function generateMetadata(props: PageProps<'${route}/docs/[[...slug]]'>): Promise<Metadata> {\n const params = await props.params;\n const page = source.getPage(params.slug${lang});\n if (!page) notFound();\n\n return {\n title: page.data.title,\n description: page.data.description,\n };\n}\n`,\n 'app/api/search/route.ts': searchRoute.next(input),\n };\n },\n\n 'react-router': (input) => {\n const i18n = input.i18n !== null;\n return {\n ...shared(input, true),\n 'routes/docs/layout.tsx': `import { Outlet${i18n ? ', useParams' : ''} } from 'react-router';\n${providerImports(input)}\nexport default function Layout() {\n${i18n ? ' const { lang = i18n.defaultLanguage } = useParams();\\n\\n' : ''} return (\n <RootProvider${providerProps(input)}>\n ${wrapper('\\n <Outlet />\\n ')}\n </RootProvider>\n );\n}\n`,\n 'routes/docs/page.tsx': `import type { Route } from './+types/page';\nimport { DocsLayout } from 'fumadocs-ui/layouts/docs';\nimport { DocsBody, DocsDescription, DocsPage, DocsTitle } from 'fumadocs-ui/layouts/docs/page';\nimport { docs, source } from '@/lib/source';\nimport { baseOptions } from '@/lib/layout.shared';\nimport { useFumadocsLoader } from 'fumadocs-core/source/client';\nimport { useMDXComponents } from '@/components/mdx';\nimport { use } from 'react';\n\nexport async function loader({ params }: Route.LoaderArgs) {\n const slugs = params['*'].split('/').filter((v) => v.length > 0);\n const page = source.getPage(slugs${i18n ? ', params.lang' : ''});\n if (!page) throw new Response('Not found', { status: 404 });\n\n return {\n path: page.path,\n url: page.url,\n pageTree: await source.serializePageTree(source.getPageTree(${i18n ? 'params.lang' : ''})),\n };\n}\n\n${clientContent.replace('<DocsPage toc={toc}>', '<DocsPage toc={toc}>\\n <title>{page.title}</title>\\n <meta name=\"description\" content={page.description} />')}\n\nexport default function Page({ loaderData${i18n ? ', params' : ''} }: Route.ComponentProps) {\n const { path, pageTree } = useFumadocsLoader(loaderData);\n\n return (\n <DocsLayout {...baseOptions(${i18n ? 'params.lang' : ''})} tree={pageTree}>\n <Content path={path} />\n </DocsLayout>\n );\n}\n`,\n 'routes/docs/search.ts': `import type { Route } from './+types/search';\nimport { createFromSource } from 'fumadocs-core/search/server';\nimport { source } from '@/lib/source';\n\nconst server = createFromSource(source);\n\nexport async function loader(${input.static ? '' : '{ request }: Route.LoaderArgs'}) {\n return server.${input.static ? 'staticGET()' : 'GET(request)'};\n}\n`,\n };\n },\n\n 'tanstack-start': (input) => {\n const seg = localeSegment('tanstack-start', input.i18n);\n const i18n = input.i18n !== null;\n return {\n ...shared(input, true),\n 'routes/_docs.tsx': `import { createFileRoute, Outlet${i18n ? ', useParams' : ''} } from '@tanstack/react-router';\n${providerImports(input)}\nexport const Route = createFileRoute('/_docs')({\n component: Layout,\n});\n\nfunction Layout() {\n${i18n ? ' const { lang = i18n.defaultLanguage } = useParams({ strict: false });\\n\\n' : ''} return (\n <RootProvider${providerProps(input)}>\n ${wrapper('\\n <Outlet />\\n ')}\n </RootProvider>\n );\n}\n`,\n [`routes/_docs/${seg}docs/$.tsx`]: `import { createFileRoute, notFound } from '@tanstack/react-router';\nimport { DocsLayout } from 'fumadocs-ui/layouts/docs';\nimport { createServerFn } from '@tanstack/react-start';\nimport { docs, source } from '@/lib/source';\nimport { DocsBody, DocsDescription, DocsPage, DocsTitle } from 'fumadocs-ui/layouts/docs/page';\nimport { baseOptions } from '@/lib/layout.shared';\nimport { useFumadocsLoader } from 'fumadocs-core/source/client';\nimport { Suspense, use } from 'react';\nimport { useMDXComponents } from '@/components/mdx';\n\nexport const Route = createFileRoute('/_docs/${seg}docs/$')({\n component: Page,\n loader: async ({ params }) => {\n const data = await serverLoader({\n data: {\n slugs: params._splat?.split('/') ?? [],${i18n ? '\\n lang: params.lang,' : ''}\n },\n });\n await docs.getPage(data.path)?.preload();\n return data;\n },\n});\n\nconst serverLoader = createServerFn({\n method: 'GET',\n})\n .validator((params: { slugs: string[]${i18n ? '; lang?: string' : ''} }) => params)\n .handler(async ({ data: { slugs${i18n ? ', lang' : ''} } }) => {\n const page = source.getPage(slugs${i18n ? ', lang' : ''});\n if (!page) throw notFound();\n\n return {\n path: page.path,\n pageTree: await source.serializePageTree(source.getPageTree(${i18n ? 'lang' : ''})),\n };\n });\n\n${clientContent}\n\nfunction Page() {\n${i18n ? ' const { lang } = Route.useParams();\\n' : ''} const data = useFumadocsLoader(Route.useLoaderData());\n\n return (\n <DocsLayout {...baseOptions(${i18n ? 'lang' : ''})} tree={data.pageTree}>\n <Suspense>\n <Content path={data.path} />\n </Suspense>\n </DocsLayout>\n );\n}\n`,\n 'routes/api/search.ts': `import { createFileRoute } from '@tanstack/react-router';\nimport { source } from '@/lib/source';\nimport { createFromSource } from 'fumadocs-core/search/server';\n\nconst server = createFromSource(source);\n\nexport const Route = createFileRoute('/api/search')({\n server: {\n handlers: {\n GET: ${input.static ? '() => server.staticGET()' : 'async ({ request }) => server.GET(request)'},\n },\n },\n});\n`,\n };\n },\n\n waku: (input) => {\n const i18n = input.i18n !== null;\n const page = `import { source } from '@/lib/source';\nimport type { PageProps } from 'waku/router';\nimport { unstable_notFound } from 'waku/router/server';\nimport { createRelativeLink } from 'fumadocs-ui/mdx';\nimport { DocsBody, DocsDescription, DocsPage, DocsTitle } from 'fumadocs-ui/layouts/docs/page';\nimport { getMDXComponents } from '@/components/mdx';\n${i18n ? \"import { DocsLayout } from 'fumadocs-ui/layouts/docs';\\nimport { baseOptions } from '@/lib/layout.shared';\\n\" : ''}\nexport default function Page({ slug${i18n ? ', lang' : ''} }: PageProps<'${i18n ? '/[lang]' : ''}/docs/[...slug]'>) {\n const page = source.getPage(slug${i18n ? ', lang' : ''});\n if (!page) unstable_notFound();\n\n const MDX = page.data.body;\n const content = (\n <DocsPage toc={page.data.toc}>\n <DocsTitle>{page.data.title}</DocsTitle>\n <DocsDescription>{page.data.description}</DocsDescription>\n <DocsBody>\n <MDX\n components={getMDXComponents({\n // this allows you to link to other pages with relative file paths\n a: createRelativeLink(source, page),\n })}\n />\n </DocsBody>\n </DocsPage>\n );\n${\n i18n\n ? `\n // layouts don't receive the locale, render the docs layout here\n return (\n <DocsLayout {...baseOptions(lang)} tree={source.getPageTree(lang)}>\n {content}\n </DocsLayout>\n );`\n : `\n return content;`\n}\n}\n\nexport async function getConfig() {\n return {\n render: 'static',\n staticPaths: source.generateParams().map((item) => ${i18n ? '[item.lang, ...item.slug]' : 'item.slug'}),\n } as const;\n}\n`;\n\n return {\n ...shared(input, false),\n 'pages/(docs)/_layout.tsx': i18n\n ? `import type { ReactNode } from 'react';\nimport { Provider } from '@/components/provider';\n\nexport default function Layout({ children }: { children: ReactNode }) {\n return (\n <Provider>\n ${wrapper('{children}')}\n </Provider>\n );\n}\n`\n : `import type { ReactNode } from 'react';\n${providerImports(input)}\nexport default function Layout({ children }: { children: ReactNode }) {\n return (\n <RootProvider${providerProps(input)}>\n ${wrapper('{children}')}\n </RootProvider>\n );\n}\n`,\n ...(i18n\n ? {\n 'components/provider.tsx': `'use client';\nimport type { ReactNode } from 'react';\nimport { useRouter } from 'waku/router/client';\n${providerImports(input)}\nexport function Provider({ children }: { children: ReactNode }) {\n const router = useRouter();\n const lang = router.path.split('/')[1] || i18n.defaultLanguage;\n\n return <RootProvider${providerProps(input)}>{children}</RootProvider>;\n}\n`,\n 'pages/(docs)/[lang]/docs/[...slug].tsx': page,\n }\n : {\n 'pages/(docs)/docs/_layout.tsx': `import type { ReactNode } from 'react';\nimport { DocsLayout } from 'fumadocs-ui/layouts/docs';\nimport { source } from '@/lib/source';\nimport { baseOptions } from '@/lib/layout.shared';\n\nexport default function Layout({ children }: { children: ReactNode }) {\n return (\n <DocsLayout {...baseOptions()} tree={source.getPageTree()}>\n {children}\n </DocsLayout>\n );\n}\n`,\n 'pages/(docs)/docs/[...slug].tsx': page,\n }),\n 'pages/_api/api/search.ts': searchRoute.waku,\n };\n },\n};\n\nexport const nextProxy = `import { createI18nMiddleware } from 'fumadocs-core/i18n/middleware';\nimport { i18n } from '@/lib/i18n';\n\nexport default createI18nMiddleware(i18n);\n\nexport const config = {\n // only the docs routes, add the ones of other features here\n matcher: ['/docs/:path*', '/:lang/docs/:path*'],\n};\n`;\n\nexport const sampleContent = `---\ntitle: Hello World\ndescription: Your first document\n---\n\nWelcome to the docs! You can edit this page in \\`content/docs/index.mdx\\`.\n\n## Heading\n\n<Cards>\n <Card title=\"Learn more about Fumadocs\" href=\"https://fumadocs.dev\" />\n</Cards>\n`;\n\nexport const sampleContentCn = `---\ntitle: 你好,世界\ndescription: 你的第一份文檔\n---\n\n歡迎來到文檔!你可以在 \\`content/docs/index.cn.mdx\\` 編輯這個頁面。\n`;\n","import fs from 'node:fs/promises';\nimport path from 'node:path';\nimport type { Feature, FeatureContext } from '@/features';\nimport type { I18nInfo, Project } from '@/project';\nimport {\n addImport,\n addJsxAttribute,\n addVitePlugin,\n findJsxElement,\n wrapNextConfig,\n} from '@/codemod';\nimport { exists } from '@/utils/fs';\nimport { localeSegment } from '@/project/route';\nimport { nextProxy, sampleContent, sampleContentCn, templates } from './templates';\nimport { reactFramework, reactOnly, registerReactRouterRoutes } from '../utils';\n\nconst cssImports = (preset: string) => [\n `@import 'fumadocs-ui/css/${preset}.css';`,\n `@import 'fumadocs-ui/css/preset.css';`,\n];\n\nexport const docs: Feature<{ i18n: boolean }> = {\n id: 'docs',\n title: 'Docs',\n description: 'set up Fumadocs on your app, in a dedicated route group',\n supports: reactOnly,\n options: {\n i18n: { message: 'Enable internationalization?', initialValue: false },\n },\n async detect(project) {\n return exists(path.join(project.cwd, project.baseDir, 'lib/source.ts'));\n },\n async apply(ctx, options) {\n const { project } = ctx;\n const { baseDir, packageJson, config } = project;\n const framework = reactFramework(project);\n // Waku has no optional route segments, the locale is always in the URL\n const i18n = project.i18n ?? (options.i18n ? { optionalLocale: framework !== 'waku' } : null);\n const providerProps: string[] = [];\n if (project.static) providerProps.push('search={{ SearchDialog }}');\n if ('next-themes' in (packageJson.dependencies ?? {}))\n providerProps.push('theme={{ enabled: false }}');\n\n ctx.addDependencies({\n 'fumadocs-core': null,\n 'fumadocs-mdx': null,\n 'fumadocs-ui': config.uiLibrary === 'base-ui' ? 'npm:@fumadocs/base-ui' : null,\n });\n ctx.addDependencies({ '@types/mdx': null }, true);\n\n const files = templates[framework]({\n static: project.static,\n i18n,\n provider: project.info.provider,\n providerProps: providerProps.map((prop) => ` ${prop}`).join(''),\n });\n for (const [file, content] of Object.entries(files)) {\n await ctx.write(path.posix.join(baseDir, file), content);\n }\n await ctx.write('content/docs/index.mdx', sampleContent);\n if (i18n) await ctx.write('content/docs/index.cn.mdx', sampleContentCn);\n await ctx.append('.gitignore', ['.source']);\n if (i18n && framework === 'next') await configureProxy(ctx);\n\n await configureBundler(ctx);\n await configureCss(ctx);\n await suppressHydrationWarning(ctx);\n if (framework === 'react-router') await configureRoutes(ctx, i18n);\n\n const tsconfig = await fs\n .readFile(path.join(project.cwd, 'tsconfig.json'), 'utf-8')\n .catch(() => '');\n if (!tsconfig.includes('\"@/*\"')) {\n ctx.note(\n `Generated files import from the \\`@/*\\` alias, add it to \\`tsconfig.json\\`:\\n \"paths\": { \"@/*\": [\"./${baseDir ? `${baseDir}/` : ''}*\"] }`,\n );\n }\n if (project.static && framework === 'tanstack-start') {\n ctx.note(\n 'In SPA mode, prerender the docs pages and search index, see https://fumadocs.dev/docs/manual-installation/tanstack-start.',\n );\n }\n if (i18n && framework === 'react-router') {\n ctx.note(\n 'When prerendering, list the docs pages with `source.getPages().map((page) => page.url)` instead of globbing content files, see https://fumadocs.dev/docs/internationalization/react-router.',\n );\n }\n if (i18n) {\n ctx.note(\n `Languages are configured in \\`lib/i18n.ts\\`, translated content uses a locale suffix like \\`index.cn.mdx\\`.\\nUI translations: https://fumadocs.dev/docs/internationalization#translations`,\n );\n }\n ctx.note(\n `Start the dev server and open ${i18n && !i18n.optionalLocale ? '/en/docs' : '/docs'} to see your docs.`,\n );\n },\n};\n\n/** the Next.js proxy (middleware) file of the project, relative to cwd */\nexport async function findNextProxy({ cwd, baseDir }: Project) {\n for (const file of ['proxy.ts', 'middleware.ts']) {\n for (const dir of new Set([baseDir, ''])) {\n const target = path.join(dir, file);\n if (await exists(path.join(cwd, target))) return target;\n }\n }\n}\n\n/** the i18n middleware redirects & rewrites docs URLs without locale */\nasync function configureProxy(ctx: FeatureContext) {\n const existing = await findNextProxy(ctx.project);\n if (!existing) {\n await ctx.write(path.join(ctx.project.baseDir, 'proxy.ts'), nextProxy);\n return;\n }\n\n ctx.note(\n `A proxy already exists at ${existing}, handle the docs locale there. Fumadocs provides \\`createI18nMiddleware\\` from 'fumadocs-core/i18n/middleware', see https://fumadocs.dev/docs/internationalization/next.`,\n );\n}\n\nasync function configureBundler(ctx: FeatureContext) {\n const { framework, configFile } = ctx.project;\n const edited =\n configFile !== undefined &&\n (await ctx\n .source(configFile, (file) => {\n const ok =\n framework === 'next'\n ? wrapNextConfig(file)\n : addVitePlugin(\n file,\n { name: 'fumadocsMdx', from: 'fumadocs-mdx/vite' },\n framework === 'waku' ? ['vite', 'plugins'] : ['plugins'],\n );\n if (!ok) throw new Error(`cannot update ${configFile}`);\n })\n .catch(() => false));\n\n if (!edited) {\n ctx.note(\n framework === 'next'\n ? \"Wrap your Next.js config with `createMDX()` from 'fumadocs-mdx/next', see https://fumadocs.dev/docs/mdx/next.\"\n : \"Add `fumadocsMdx()` from 'fumadocs-mdx/vite' to the Vite plugins of your config file.\",\n );\n }\n}\n\n/** resolve the global CSS file imported by the root file */\nasync function findCssEntry({ cwd, baseDir, info }: Project): Promise<string | undefined> {\n const rootFile = path.join(baseDir, info.rootFile);\n const content = await fs.readFile(path.join(cwd, rootFile), 'utf-8').catch(() => '');\n const match = /import\\s+(?:\\w+\\s+from\\s+)?['\"]([^'\"]+\\.css)(?:\\?url)?['\"]/.exec(content);\n if (!match) return;\n const specifier = match[1];\n\n if (specifier.startsWith('@/')) return path.join(baseDir, specifier.slice(2));\n if (specifier.startsWith('.')) return path.join(path.dirname(rootFile), specifier);\n}\n\nasync function configureCss(ctx: FeatureContext) {\n const { cwd } = ctx.project;\n const preset = (await exists(path.join(cwd, 'components.json'))) ? 'shadcn' : 'neutral';\n const entry = await findCssEntry(ctx.project);\n if (entry && (await exists(path.join(cwd, entry)))) {\n await ctx.append(entry, cssImports(preset));\n return;\n }\n\n ctx.note(\n `Add the styles to your global CSS file (after \\`@import 'tailwindcss'\\`):\\n ${cssImports(preset).join('\\n ')}`,\n );\n}\n\n/** `next-themes` sets the theme class on `<html>`, React warns about the mismatch without this attribute */\nasync function suppressHydrationWarning(ctx: FeatureContext) {\n const { baseDir, info } = ctx.project;\n const edited = await ctx.source(path.join(baseDir, info.rootFile), (file) => {\n const html = findJsxElement(file, 'html')?.openingElement;\n if (!html || file.code.slice(html.start, html.end).includes('suppressHydrationWarning')) return;\n addJsxAttribute(file, html, 'suppressHydrationWarning');\n });\n\n if (!edited)\n ctx.note('Add `suppressHydrationWarning` to the `<html>` element of your root layout.');\n}\n\nasync function configureRoutes(ctx: FeatureContext, i18n: I18nInfo | null) {\n const added = await registerReactRouterRoutes(ctx, [\n `layout('routes/docs/layout.tsx', [route('${localeSegment('react-router', i18n)}docs/*', 'routes/docs/page.tsx')])`,\n { path: 'api/search', file: 'routes/docs/search.ts', pattern: '/api/search' },\n ]);\n if (!added) return;\n await ctx.source(path.join(ctx.project.baseDir, 'routes.ts'), (file) => {\n addImport(file, { from: '@react-router/dev/routes', named: ['layout', 'route'] });\n });\n}\n"],"mappings":";;;;;;AAoCA,MAAM,cAAkD;CACtD,MAAM;EACJ,cAAc;EACd,QAAQ,MAAM,UAAU,aACtB,WAAY,WAAW,QAAQ,KAAK,MAAM,OAAO,KAAK,KAAM,IAAI,KAAK;EACvE,OAAO,UAAU,QAAQ,OAAO,SAAS,KAAK,GAAG,EAAE,SAAS;EAC5D,OAAO,aAAa,IAAI,SAAS,KAAK,GAAG;CAC3C;CACA,gBAAgB;EACd,SAAS,aAAc,WAAW,WAAW;EAC7C,QAAQ,MAAM,aAAc,WAAW,MAAM,IAAI;EAEjD,OAAO,UAAU,QACf,UAAU,SAAS,QAAQ,YAAY,CAAC,QAAQ,KAAK,OAAO,CAAC,CAAC,CAAC,KAAK,GAAG,EAAE,GAAG;EAC9E,OAAO,aAAa,SAAS,KAAK,GAAG;CACvC;CACA,kBAAkB;EAChB,SAAS,aAAc,WAAW,aAAa;EAC/C,QAAQ,MAAM,aAAc,WAAW,MAAM,IAAI;EACjD,OAAO,UAAU,QAAQ,UAAU,SAAS,KAAK,GAAG,CAAC,CAAC,WAAW,KAAK,KAAK,EAAE,GAAG;EAChF,OAAO,aAAa,IAAI,SAAS,KAAK,GAAG;CAC3C;CACA,MAAM;EACJ,cAAc;EACd,QAAQ,MAAM,UAAU,WAAW,WACjC,WAAW,OAAO,KAAK,GAAG,SAAS,IAAI,WAAW,OAAO,IAAI,KAAK;EACpE,OAAO,UAAU,QAAQ,cAAc,SAAS,KAAK,GAAG,EAAE,GAAG;EAC7D,OAAO,aAAa,IAAI,SAAS,KAAK,GAAG;CAC3C;AACF;;AAGA,SAAgB,cAAc,WAA2B,MAAuB;CAC9E,OAAO,OAAO,GAAG,YAAY,UAAU,CAAC,OAAO,KAAK,cAAc,EAAE,KAAK;AAC3E;AAEA,SAAgB,YACd,OACA,WACA,MACA,MAAM,MACU;CAChB,MAAM,aAAa,YAAY;CAC/B,MAAM,WAAqB,CAAC;CAC5B,MAAM,WAAqB,CAAC;CAC5B,IAAI,MAAM,UAAU,MAAM;EACxB,SAAS,KAAK,WAAW,OAAO,KAAK,cAAc,CAAC;EACpD,SAAS,KAAK,OAAO;CACvB;CAEA,KAAK,MAAM,WAAW,MAAM,UAC1B,IAAI,OAAO,YAAY,UACrB,KAAK,MAAM,QAAQ,QAAQ,MAAM,GAAG,GAAG;EACrC,IAAI,KAAK,WAAW,GAAG;EACvB,SAAS,KAAK,IAAI;EAClB,SAAS,KAAK,IAAI;CACpB;MACK;EACL,MAAM,EAAE,OAAO,WAAW,OAAO,WAAW,OAAO,WAAW;EAC9D,SAAS,KAAK,WAAW,MAAM,OAAO,UAAU,UAAU,MAAM,CAAC;EACjE,SAAS,KAAK,WAAW,IAAI,MAAM,KAAK,IAAI,OAAO;CACrD;CAGF,OAAO;EACL,MAAM,WAAW,KAAK,UAAU,GAAG;EACnC,MAAM,WAAW,KAAK,QAAQ;EAC9B,SAAS,IAAI,SAAS,KAAK,GAAG;CAChC;AACF;;AAkBA,SAAgB,YACd,WACA,OACA,KACQ;CACR,MAAM,EAAE,SAAS,MAAM,SAAS,UAAU,CAAC,KAAK,GAAG,YAAY,QAAQ,aAAa;CACpF,MAAM,MAAM,UAAU,qBAAqB;CAC3C,MAAM,OAAO,GAAG,QAAQ,KAAK,EAAE;CAE/B,IAAI,cAAc,gBAAgB;EAChC,MAAM,MAAgB,CAAC;EAEvB,IAAI,KAAK,QAAQ,UAAU,kBAAkB,CAAC;EAC9C,IAAI,QAAQ,SAAS,GAAG,IAAI,KAAK,QAAQ,UAAU,kBAAkB,CAAC;EAEtE,OAAO,GAAG,UAAU,GAAG,iBAAiB,KAAK,EAAE,MAAM,KAAK,OAAO,IAAI,KAAK,IAAI;CAChF;CAEA,IAAI,cAAc,kBAAkB;EAClC,MAAM,MAAgB,CAAC;EACvB,KAAK,MAAM,UAAU,SACnB,IAAI,KAAK,SAAS,OAAO,WAAW,UAAU,gBAAgB,GAAG,OAAO,KAAK,EAAE;EAEjF,OAAO;EACT,KAAK,wCAAwC,MAAM,KAAK;;;EAGxD,IAAI,KAAK,IAAI,EAAE;;;;;CAKf;CAEA,MAAM,MAAgB,CAAC;CACvB,IAAI,cAAc,UAAU,YAAY,IAAI,KAAK,oCAAoC;CACrF,KAAK,MAAM,UAAU,SACnB,IAAI,KAAK,yBAAyB,OAAO,GAAG,IAAI;WACzC,KAAK;;CAEf;CACC,IAAI,cAAc,UAAU,UAC1B,IAAI,KAAK;;;;;CAKZ;CAEC,OAAO,GAAG,OAAO,IAAI,KAAK,IAAI;CAE9B,SAAS,QAAQ,MAAc,MAAc;EAC3C,OAAO,yBAAyB,KAAK,GAAG,UAAU,gBAAgB,SAAS,GAAG;WACvE,KAAK;;;CAGd;AACF;;AAGA,MAAa,oBAAoB,UAC/B,wCAAwC,MAAM,KAC3C,MAAM,GAAG,CAAC,CACV,IAAI,CAAC,CACL,QAAQ,WAAW,EAAE,EAAE;;;AC5K5B,MAAM,WAAW,aACf,+CAA+C,SAAS;AAE1D,MAAM,cAAc,mBAA4B;;;;4BAIpB,iBAAiB,sCAAsC,GAAG;;;AAItF,MAAM,kBAAkB;;AAGxB,MAAM,UAAU,OAAgB,SAAkB;;;EAGhD,OAAO,yCAAyC,GAAG;;wBAE7B,QAAQ,wCAAwC,GAAG;;;;;oCAKvC,OAAO,cAAc,GAAG;;;AAI5D,MAAM,gBAAgB,SACpB,OACI;;;;;;;;;;;;;;IAeA;;;;;;;;;;AAWN,MAAM,MAAM;;;;;;;;;;;;;;;;AAiBZ,MAAM,UAAU,SAAkB;;;;;;;;;;;;;;EAchC,OAAO,2DAA2D,GAAG;;EAErE,OAAO,sCAAsC,GAAG;2BACvB,OAAO,eAAe,GAAG;;;;;;;;;;;;;;;;;;AAmBpD,SAAS,OAAO,OAAsB,OAAwC;CAC5E,MAAM,OAAO,MAAM,SAAS;CAC5B,OAAO;EACL,iBAAiB;EACjB,iBAAiB,OAAO,OAAO,IAAI;EACnC,yBAAyB,aAAa,IAAI;EAC1C,sBAAsB;EACtB,GAAI,MAAM,SAAS,EAAE,yBAAyB,OAAO,IAAI,EAAE,IAAI,CAAC;EAChE,GAAI,MAAM,OAAO,EAAE,eAAe,WAAW,MAAM,KAAK,cAAc,EAAE,IAAI,CAAC;CAC/E;AACF;AAEA,MAAM,gBAAgB,UACpB,MAAM,SAAS,sDAAsD;;AAGvE,MAAM,mBAAmB,UACvB,iCAAiC,MAAM,SAAS;EAChD,aAAa,KAAK,IAChB,MAAM,OACF;;;IAIA;AAGR,MAAM,iBAAiB,UACrB,GAAG,MAAM,OAAO,6CAA6C,KAAK,MAAM;;AAG1E,MAAM,gBAAgB;;;;;;;;;;;;;;;;;AAkBtB,MAAM,cAAc;CAClB,OAAO,UAAyB;;EAEhC,MAAM,SAAS,yCAAyC,GAAG;iBAC5C,MAAM,SAAS,mBAAmB,MAAM;;CAEvD,MAAM;;;;;AAKR;AAEA,MAAa,YAA8C;CACzD,OAAO,UAAU;EACf,MAAM,OAAO,MAAM,SAAS;EAE5B,MAAM,MAAM,OAAO,sBAAsB;EACzC,MAAM,QAAQ,OAAO,YAAY;EACjC,MAAM,OAAO,OAAO,kBAAkB;EAEtC,OAAO;GACL,GAAG,OAAO,OAAO,KAAK;IACrB,GAAG,IAAI,eAAe,MAAM,SACzB;EACR,gBAAgB,KAAK,IAAI,OAAO,mCAAmC,GAAG;mCACrC,OAAO,aAAa,GAAG,2BAA2B,SAAS,IAAI;EAChG,OAAO,wCAAwC,GAAG;mBACjC,cAAc,KAAK,EAAE;QAChC,QAAQ,YAAY,EAAE;;;;IAKpB,GAAG,gBAAgB,KAAK,EAAE;iBACnB,OAAO,WAAW,GAAG,oBAAoB,OAAO,aAAa,GAAG,2BAA2B,SAAS,IAAI;EACvH,OAAO,yCAAyC,GAAG;mBAClC,cAAc,KAAK,EAAE;QAChC,QAAQ,YAAY,EAAE;;;;;IAKvB,GAAG,IAAI,oBAAoB;;;;iBAIjB,OAAO,WAAW,GAAG,oBAAoB,OAAO,aAAa,GAAG,2BAA2B,MAAM;EAChH,OAAO,yCAAyC,GAAG;2CACV,OAAO,SAAS,GAAG,qBAAqB,OAAO,SAAS,GAAG;;;;;;IAM/F,GAAG,IAAI,8BAA8B;;;;;;;uDAOW,MAAM;;2CAElB,KAAK;;;;;;;;;;;;;;;;;;;;;;;;;2DAyBW,MAAM;;2CAEtB,KAAK;;;;;;;;;GAS1C,2BAA2B,YAAY,KAAK,KAAK;EACnD;CACF;CAEA,iBAAiB,UAAU;EACzB,MAAM,OAAO,MAAM,SAAS;EAC5B,OAAO;GACL,GAAG,OAAO,OAAO,IAAI;GACrB,0BAA0B,kBAAkB,OAAO,gBAAgB,GAAG;EAC1E,gBAAgB,KAAK,EAAE;;EAEvB,OAAO,+DAA+D,GAAG;mBACxD,cAAc,KAAK,EAAE;QAChC,QAAQ,8BAA8B,EAAE;;;;;GAK1C,wBAAwB;;;;;;;;;;;qCAWO,OAAO,kBAAkB,GAAG;;;;;;kEAMC,OAAO,gBAAgB,GAAG;;;;EAI1F,cAAc,QAAQ,wBAAwB,yHAAuH,EAAE;;2CAE9H,OAAO,aAAa,GAAG;;;;kCAIhC,OAAO,gBAAgB,GAAG;;;;;;GAMtD,yBAAyB;;;;;;+BAMA,MAAM,SAAS,KAAK,gCAAgC;kBACjE,MAAM,SAAS,gBAAgB,eAAe;;;EAG5D;CACF;CAEA,mBAAmB,UAAU;EAC3B,MAAM,MAAM,cAAc,kBAAkB,MAAM,IAAI;EACtD,MAAM,OAAO,MAAM,SAAS;EAC5B,OAAO;GACL,GAAG,OAAO,OAAO,IAAI;GACrB,oBAAoB,mCAAmC,OAAO,gBAAgB,GAAG;EACrF,gBAAgB,KAAK,EAAE;;;;;;EAMvB,OAAO,gFAAgF,GAAG;mBACzE,cAAc,KAAK,EAAE;QAChC,QAAQ,8BAA8B,EAAE;;;;;IAKzC,gBAAgB,IAAI,cAAc;;;;;;;;;;+CAUM,IAAI;;;;;iDAKF,OAAO,iCAAiC,GAAG;;;;;;;;;;;yCAWnD,OAAO,oBAAoB,GAAG;mCACpC,OAAO,WAAW,GAAG;uCACjB,OAAO,WAAW,GAAG;;;;;oEAKQ,OAAO,SAAS,GAAG;;;;EAIrF,cAAc;;;EAGd,OAAO,4CAA4C,GAAG;;;kCAGtB,OAAO,SAAS,GAAG;;;;;;;;GAQ/C,wBAAwB;;;;;;;;;aASjB,MAAM,SAAS,6BAA6B,6CAA6C;;;;;EAKlG;CACF;CAEA,OAAO,UAAU;EACf,MAAM,OAAO,MAAM,SAAS;EAC5B,MAAM,OAAO;;;;;;EAMf,OAAO,iHAAiH,GAAG;qCACxF,OAAO,WAAW,GAAG,iBAAiB,OAAO,YAAY,GAAG;oCAC7D,OAAO,WAAW,GAAG;;;;;;;;;;;;;;;;;;EAmBvD,OACI;;;;;;QAOA;mBAEL;;;;;;yDAMwD,OAAO,8BAA8B,YAAY;;;;EAKtG,OAAO;GACL,GAAG,OAAO,OAAO,KAAK;GACtB,4BAA4B,OACxB;;;;;;QAMF,QAAQ,YAAY,EAAE;;;;IAKpB;EACR,gBAAgB,KAAK,EAAE;;;mBAGN,cAAc,KAAK,EAAE;QAChC,QAAQ,YAAY,EAAE;;;;;GAKxB,GAAI,OACA;IACE,2BAA2B;;;EAGrC,gBAAgB,KAAK,EAAE;;;;;wBAKD,cAAc,KAAK,EAAE;;;IAGjC,0CAA0C;GAC5C,IACA;IACE,iCAAiC;;;;;;;;;;;;;IAajC,mCAAmC;GACrC;GACJ,4BAA4B,YAAY;EAC1C;CACF;AACF;AAEA,MAAa,YAAY;;;;;;;;;;AAWzB,MAAa,gBAAgB;;;;;;;;;;;;;AAc7B,MAAa,kBAAkB;;;;;;;;;AC/hB/B,MAAM,cAAc,WAAmB,CACrC,4BAA4B,OAAO,SACnC,uCACF;AAEA,MAAa,OAAmC;CAC9C,IAAI;CACJ,OAAO;CACP,aAAa;CACb,UAAU;CACV,SAAS,EACP,MAAM;EAAE,SAAS;EAAgC,cAAc;CAAM,EACvE;CACA,MAAM,OAAO,SAAS;EACpB,OAAO,OAAO,KAAK,KAAK,QAAQ,KAAK,QAAQ,SAAS,eAAe,CAAC;CACxE;CACA,MAAM,MAAM,KAAK,SAAS;EACxB,MAAM,EAAE,YAAY;EACpB,MAAM,EAAE,SAAS,aAAa,WAAW;EACzC,MAAM,YAAY,eAAe,OAAO;EAExC,MAAM,OAAO,QAAQ,SAAS,QAAQ,OAAO,EAAE,gBAAgB,cAAc,OAAO,IAAI;EACxF,MAAM,gBAA0B,CAAC;EACjC,IAAI,QAAQ,QAAQ,cAAc,KAAK,2BAA2B;EAClE,IAAI,kBAAkB,YAAY,gBAAgB,CAAC,IACjD,cAAc,KAAK,4BAA4B;EAEjD,IAAI,gBAAgB;GAClB,iBAAiB;GACjB,gBAAgB;GAChB,eAAe,OAAO,cAAc,YAAY,0BAA0B;EAC5E,CAAC;EACD,IAAI,gBAAgB,EAAE,cAAc,KAAK,GAAG,IAAI;EAEhD,MAAM,QAAQ,UAAU,UAAU,CAAC;GACjC,QAAQ,QAAQ;GAChB;GACA,UAAU,QAAQ,KAAK;GACvB,eAAe,cAAc,KAAK,SAAS,IAAI,MAAM,CAAC,CAAC,KAAK,EAAE;EAChE,CAAC;EACD,KAAK,MAAM,CAAC,MAAM,YAAY,OAAO,QAAQ,KAAK,GAChD,MAAM,IAAI,MAAM,KAAK,MAAM,KAAK,SAAS,IAAI,GAAG,OAAO;EAEzD,MAAM,IAAI,MAAM,0BAA0B,aAAa;EACvD,IAAI,MAAM,MAAM,IAAI,MAAM,6BAA6B,eAAe;EACtE,MAAM,IAAI,OAAO,cAAc,CAAC,SAAS,CAAC;EAC1C,IAAI,QAAQ,cAAc,QAAQ,MAAM,eAAe,GAAG;EAE1D,MAAM,iBAAiB,GAAG;EAC1B,MAAM,aAAa,GAAG;EACtB,MAAM,yBAAyB,GAAG;EAClC,IAAI,cAAc,gBAAgB,MAAM,gBAAgB,KAAK,IAAI;EAKjE,IAAI,EAAC,MAHkB,GACpB,SAAS,KAAK,KAAK,QAAQ,KAAK,eAAe,GAAG,OAAO,CAAC,CAC1D,YAAY,EAAE,EAAA,CACH,SAAS,SAAO,GAC5B,IAAI,KACF,wGAAwG,UAAU,GAAG,QAAQ,KAAK,GAAG,MACvI;EAEF,IAAI,QAAQ,UAAU,cAAc,kBAClC,IAAI,KACF,2HACF;EAEF,IAAI,QAAQ,cAAc,gBACxB,IAAI,KACF,6LACF;EAEF,IAAI,MACF,IAAI,KACF,2LACF;EAEF,IAAI,KACF,iCAAiC,QAAQ,CAAC,KAAK,iBAAiB,aAAa,QAAQ,mBACvF;CACF;AACF;;AAGA,eAAsB,cAAc,EAAE,KAAK,WAAoB;CAC7D,KAAK,MAAM,QAAQ,CAAC,YAAY,eAAe,GAC7C,KAAK,MAAM,uBAAO,IAAI,IAAI,CAAC,SAAS,EAAE,CAAC,GAAG;EACxC,MAAM,SAAS,KAAK,KAAK,KAAK,IAAI;EAClC,IAAI,MAAM,OAAO,KAAK,KAAK,KAAK,MAAM,CAAC,GAAG,OAAO;CACnD;AAEJ;;AAGA,eAAe,eAAe,KAAqB;CACjD,MAAM,WAAW,MAAM,cAAc,IAAI,OAAO;CAChD,IAAI,CAAC,UAAU;EACb,MAAM,IAAI,MAAM,KAAK,KAAK,IAAI,QAAQ,SAAS,UAAU,GAAG,SAAS;EACrE;CACF;CAEA,IAAI,KACF,6BAA6B,SAAS,0KACxC;AACF;AAEA,eAAe,iBAAiB,KAAqB;CACnD,MAAM,EAAE,WAAW,eAAe,IAAI;CAiBtC,IAAI,EAfF,eAAe,KAAA,KACd,MAAM,IACJ,OAAO,aAAa,SAAS;EAS5B,IAAI,EAPF,cAAc,SACV,eAAe,IAAI,IACnB,cACE,MACA;GAAE,MAAM;GAAe,MAAM;EAAoB,GACjD,cAAc,SAAS,CAAC,QAAQ,SAAS,IAAI,CAAC,SAAS,CACzD,IACG,MAAM,IAAI,MAAM,iBAAiB,YAAY;CACxD,CAAC,CAAC,CACD,YAAY,KAAK,IAGpB,IAAI,KACF,cAAc,SACV,kHACA,uFACN;AAEJ;;AAGA,eAAe,aAAa,EAAE,KAAK,SAAS,QAA8C;CACxF,MAAM,WAAW,KAAK,KAAK,SAAS,KAAK,QAAQ;CACjD,MAAM,UAAU,MAAM,GAAG,SAAS,KAAK,KAAK,KAAK,QAAQ,GAAG,OAAO,CAAC,CAAC,YAAY,EAAE;CACnF,MAAM,QAAQ,6DAA6D,KAAK,OAAO;CACvF,IAAI,CAAC,OAAO;CACZ,MAAM,YAAY,MAAM;CAExB,IAAI,UAAU,WAAW,IAAI,GAAG,OAAO,KAAK,KAAK,SAAS,UAAU,MAAM,CAAC,CAAC;CAC5E,IAAI,UAAU,WAAW,GAAG,GAAG,OAAO,KAAK,KAAK,KAAK,QAAQ,QAAQ,GAAG,SAAS;AACnF;AAEA,eAAe,aAAa,KAAqB;CAC/C,MAAM,EAAE,QAAQ,IAAI;CACpB,MAAM,SAAU,MAAM,OAAO,KAAK,KAAK,KAAK,iBAAiB,CAAC,IAAK,WAAW;CAC9E,MAAM,QAAQ,MAAM,aAAa,IAAI,OAAO;CAC5C,IAAI,SAAU,MAAM,OAAO,KAAK,KAAK,KAAK,KAAK,CAAC,GAAI;EAClD,MAAM,IAAI,OAAO,OAAO,WAAW,MAAM,CAAC;EAC1C;CACF;CAEA,IAAI,KACF,gFAAgF,WAAW,MAAM,CAAC,CAAC,KAAK,MAAM,GAChH;AACF;;AAGA,eAAe,yBAAyB,KAAqB;CAC3D,MAAM,EAAE,SAAS,SAAS,IAAI;CAO9B,IAAI,CAAC,MANgB,IAAI,OAAO,KAAK,KAAK,SAAS,KAAK,QAAQ,IAAI,SAAS;EAC3E,MAAM,OAAO,eAAe,MAAM,MAAM,CAAC,EAAE;EAC3C,IAAI,CAAC,QAAQ,KAAK,KAAK,MAAM,KAAK,OAAO,KAAK,GAAG,CAAC,CAAC,SAAS,0BAA0B,GAAG;EACzF,gBAAgB,MAAM,MAAM,0BAA0B;CACxD,CAAC,GAGC,IAAI,KAAK,6EAA6E;AAC1F;AAEA,eAAe,gBAAgB,KAAqB,MAAuB;CAKzE,IAAI,CAAC,MAJe,0BAA0B,KAAK,CACjD,4CAA4C,cAAc,gBAAgB,IAAI,EAAE,qCAChF;EAAE,MAAM;EAAc,MAAM;EAAyB,SAAS;CAAc,CAC9E,CAAC,GACW;CACZ,MAAM,IAAI,OAAO,KAAK,KAAK,IAAI,QAAQ,SAAS,WAAW,IAAI,SAAS;EACtE,UAAU,MAAM;GAAE,MAAM;GAA4B,OAAO,CAAC,UAAU,OAAO;EAAE,CAAC;CAClF,CAAC;AACH"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"search-BbzvfJal.js","names":[],"sources":["../src/features/search.ts"],"sourcesContent":["import fs from 'node:fs/promises';\nimport path from 'node:path';\nimport type { Feature, FeatureContext } from '@/features';\nimport type { ReactFramework, SourceInfo } from '@/project';\nimport { formatRoute, routeModule } from '@/project/route';\nimport {\n addImport,\n addJsxAttribute,\n addTanstackPrerender,\n filterReactRouterPrerenderArray,\n filterReactRouterRoute,\n findJsxElement,\n} from '@/codemod';\nimport { docs } from './docs';\nimport {\n findSource,\n posix,\n reactFramework,\n registerReactRouterRoutes,\n scripts,\n type SourceRef,\n sourceRef,\n} from './utils';\n\n/** how client code reads public env variables */\ninterface Env {\n prefix: string;\n read: string;\n}\n\nconst publicEnv = (next: boolean): Env =>\n next\n ? { prefix: 'NEXT_PUBLIC_', read: 'process.env.NEXT_PUBLIC_' }\n : { prefix: 'VITE_', read: 'import.meta.env.VITE_' };\n\nconst dialogImports = `import {\n SearchDialog,\n SearchDialogClose,\n SearchDialogContent,\n SearchDialogFooter,\n SearchDialogHeader,\n SearchDialogIcon,\n SearchDialogInput,\n SearchDialogList,\n SearchDialogOverlay,\n type SharedProps,\n} from 'fumadocs-ui/components/dialog/search';\nimport { useI18n } from 'fumadocs-ui/contexts/i18n';`;\n\nconst dialogBody = (footer: string, url: string) => ` return (\n <SearchDialog search={search} onSearchChange={setSearch} isLoading={query.isLoading} {...props}>\n <SearchDialogOverlay />\n <SearchDialogContent>\n <SearchDialogHeader>\n <SearchDialogIcon />\n <SearchDialogInput />\n <SearchDialogClose />\n </SearchDialogHeader>\n <SearchDialogList items={query.data !== 'empty' ? query.data : null} />\n <SearchDialogFooter>\n <a\n href=\"${url}\"\n rel=\"noreferrer noopener\"\n className=\"ms-auto text-xs text-fd-muted-foreground\"\n >\n Search powered by ${footer}\n </a>\n </SearchDialogFooter>\n </SearchDialogContent>\n </SearchDialog>\n );\n}\n`;\n\n/** `structuredData` is a function on async collections (React Router & TanStack Start) */\nconst typesenseDocuments = (\n { ref, resolved }: SourceRef,\n async: boolean,\n) => `import { ${ref} } from '@/lib/source';\nimport type { DocumentRecord } from 'typesense-fumadocs-adapter';\n\nasync function getDocuments() {\n const documents: DocumentRecord[] = [];\n for (const page of ${resolved}.getPages()) {\n documents.push({\n _id: page.url,\n structured: ${async ? 'await page.data.structuredData()' : 'page.data.structuredData'},\n url: page.url,\n title: page.data.title,\n description: page.data.description,\n locale: page.locale,\n });\n }\n return documents;\n}`;\n\n/** `scripts/sync-content.ts`, syncing the search indexes pre-rendered to `output` */\nexport const syncScript = (\n output: string,\n imports: string,\n body: string,\n) => `import * as fs from 'node:fs/promises';\n${imports}\n\nconst records = JSON.parse(await fs.readFile('${output}', 'utf-8')) as DocumentRecord[];\n\n${body}\nconsole.log(\\`search updated: \\${records.length} records\\`);\n`;\n\ninterface Provider {\n label: string;\n hint: string;\n dependencies: Record<string, string | null>;\n /** keys exposed to the client, prefixed per framework */\n publicEnv: string[];\n privateEnv: string[];\n /** works without a server at runtime */\n static: boolean;\n dialog: (env: Env) => string;\n /** exports search indexes to a pre-rendered `static.json`, synced by `sync` after build */\n exportIndexes?: (\n src: SourceRef,\n async: boolean,\n ) => {\n /** code before the route handler */\n head: string;\n /** expression producing the documents */\n documents: string;\n };\n /** `scripts/sync-content.ts`, run after build */\n sync?: (env: Env, paths: { dir: string; output: string }) => string;\n /** server-side search, replaces the default search route */\n searchRoute?: Record<ReactFramework, string>;\n}\n\nconst providers = {\n 'orama-cloud': {\n label: 'Orama Cloud',\n hint: 'signup needed',\n dependencies: { '@orama/core': null },\n publicEnv: ['ORAMA_DATASOURCE_ID', 'ORAMA_PROJECT_ID', 'ORAMA_API_KEY'],\n privateEnv: ['ORAMA_PRIVATE_API_KEY'],\n static: true,\n dialog: (env) => `'use client';\n${dialogImports}\nimport { useDocsSearch } from 'fumadocs-core/search/client';\nimport { OramaCloud } from '@orama/core';\n\nconst client = new OramaCloud({\n projectId: ${env.read}ORAMA_PROJECT_ID!,\n apiKey: ${env.read}ORAMA_API_KEY!,\n});\n\nexport default function CustomSearchDialog(props: SharedProps) {\n const { locale } = useI18n();\n const { search, setSearch, query } = useDocsSearch({\n type: 'orama-cloud',\n client,\n locale,\n });\n\n${dialogBody('Orama', 'https://orama.com')}`,\n exportIndexes: ({ ref }) => ({\n head: `import { ${ref} } from '@/lib/source';\nimport { toDocuments } from 'fumadocs-core/search/orama-cloud';`,\n documents: `toDocuments(${ref})`,\n }),\n sync: (env, { output }) =>\n syncScript(\n output,\n `import { type OramaDocument as DocumentRecord, sync } from 'fumadocs-core/search/orama-cloud';\nimport { OramaCloud } from '@orama/core';`,\n `const orama = new OramaCloud({\n projectId: process.env.${env.prefix}ORAMA_PROJECT_ID!,\n apiKey: process.env.ORAMA_PRIVATE_API_KEY!,\n});\n\nawait sync(orama, {\n index: process.env.${env.prefix}ORAMA_DATASOURCE_ID!,\n documents: records,\n});\n`,\n ),\n },\n algolia: {\n label: 'Algolia',\n hint: 'signup needed',\n dependencies: { algoliasearch: null },\n publicEnv: ['ALGOLIA_APP_ID', 'ALGOLIA_SEARCH_KEY'],\n privateEnv: ['ALGOLIA_WRITE_KEY'],\n static: true,\n dialog: (env) => `'use client';\n${dialogImports}\nimport { useDocsSearch } from 'fumadocs-core/search/client';\nimport { algoliaClient } from 'fumadocs-core/search/client/algolia';\nimport { liteClient } from 'algoliasearch/lite';\n\nconst algolia = liteClient(${env.read}ALGOLIA_APP_ID!, ${env.read}ALGOLIA_SEARCH_KEY!);\n\nexport default function CustomSearchDialog(props: SharedProps) {\n const { locale } = useI18n();\n const { search, setSearch, query } = useDocsSearch({\n client: algoliaClient({\n client: algolia,\n indexName: 'document',\n locale,\n }),\n });\n\n${dialogBody('Algolia', 'https://algolia.com')}`,\n exportIndexes: ({ ref }) => ({\n head: `import { ${ref} } from '@/lib/source';\nimport { toDocuments } from 'fumadocs-core/search/algolia';`,\n documents: `toDocuments(${ref})`,\n }),\n sync: (env, { output }) =>\n syncScript(\n output,\n `import { type DocumentRecord, sync } from 'fumadocs-core/search/algolia';\nimport { algoliasearch } from 'algoliasearch';`,\n `const client = algoliasearch(\n process.env.${env.prefix}ALGOLIA_APP_ID!,\n process.env.ALGOLIA_WRITE_KEY!,\n);\n\nawait sync(client, {\n indexName: 'document',\n documents: records,\n});\n`,\n ),\n },\n meilisearch: {\n label: 'Meilisearch',\n hint: 'self-hosted or cloud',\n dependencies: { meilisearch: null },\n publicEnv: ['MEILISEARCH_HOST', 'MEILISEARCH_SEARCH_KEY'],\n privateEnv: ['MEILISEARCH_ADMIN_KEY'],\n static: true,\n dialog: (env) => `'use client';\n${dialogImports}\nimport { useDocsSearch } from 'fumadocs-core/search/client';\nimport { meilisearchClient } from 'fumadocs-core/search/client/meilisearch';\nimport { Meilisearch } from 'meilisearch';\n\nconst client = new Meilisearch({\n host: ${env.read}MEILISEARCH_HOST!,\n apiKey: ${env.read}MEILISEARCH_SEARCH_KEY!,\n});\n\nexport default function CustomSearchDialog(props: SharedProps) {\n const { locale } = useI18n();\n const { search, setSearch, query } = useDocsSearch({\n client: meilisearchClient({\n client,\n indexName: 'docs',\n locale,\n }),\n });\n\n${dialogBody('Meilisearch', 'https://www.meilisearch.com')}`,\n exportIndexes: ({ ref }) => ({\n head: `import { ${ref} } from '@/lib/source';\nimport { toDocuments } from 'fumadocs-core/search/meilisearch';`,\n documents: `toDocuments(${ref})`,\n }),\n sync: (env, { output }) =>\n syncScript(\n output,\n `import { type DocumentRecord, sync } from 'fumadocs-core/search/meilisearch';\nimport { Meilisearch } from 'meilisearch';`,\n `const client = new Meilisearch({\n host: process.env.${env.prefix}MEILISEARCH_HOST!,\n apiKey: process.env.MEILISEARCH_ADMIN_KEY!,\n});\n\nawait sync(client, {\n indexName: 'docs',\n documents: records,\n});\n`,\n ),\n },\n typesense: {\n label: 'Typesense',\n hint: 'self-hosted or cloud, community adapter',\n dependencies: { typesense: null, 'typesense-fumadocs-adapter': null },\n publicEnv: ['TYPESENSE_URL', 'TYPESENSE_SEARCH_KEY'],\n privateEnv: ['TYPESENSE_API_KEY'],\n static: true,\n dialog: (env) => `'use client';\n${dialogImports}\nimport { Client } from 'typesense';\nimport { useTypesenseSearch } from 'typesense-fumadocs-adapter/client';\n\nconst client = new Client({\n nodes: [{ url: ${env.read}TYPESENSE_URL! }],\n apiKey: ${env.read}TYPESENSE_SEARCH_KEY!,\n});\n\nexport default function CustomSearchDialog(props: SharedProps) {\n const { locale } = useI18n();\n const { search, setSearch, query } = useTypesenseSearch({\n typesenseCollectionName: 'docs',\n locale,\n client,\n });\n\n${dialogBody('Typesense', 'https://typesense.org')}`,\n exportIndexes: (src, async) => ({\n head: typesenseDocuments(src, async),\n documents: 'getDocuments()',\n }),\n sync: (env, { output }) =>\n syncScript(\n output,\n `import { type DocumentRecord, sync } from 'typesense-fumadocs-adapter';\nimport { Client } from 'typesense';`,\n `const client = new Client({\n nodes: [{ url: process.env.${env.prefix}TYPESENSE_URL! }],\n apiKey: process.env.TYPESENSE_API_KEY!,\n connectionTimeoutSeconds: 60 * 15,\n});\n\nawait sync(client, {\n typesenseCollectionName: 'docs',\n documents: records,\n});\n`,\n ),\n },\n mixedbread: {\n label: 'Mixedbread',\n hint: 'AI search, signup needed',\n dependencies: { '@mixedbread/sdk': null },\n publicEnv: [],\n privateEnv: ['MIXEDBREAD_API_KEY', 'MIXEDBREAD_STORE_ID'],\n static: false,\n dialog: () => `'use client';\n${dialogImports}\nimport { useDocsSearch } from 'fumadocs-core/search/client';\nimport { fetchClient } from 'fumadocs-core/search/client/fetch';\n\nexport default function CustomSearchDialog(props: SharedProps) {\n const { locale } = useI18n();\n const { search, setSearch, query } = useDocsSearch({\n client: fetchClient({\n api: '/api/search',\n locale,\n }),\n });\n\n${dialogBody('Mixedbread', 'https://mixedbread.com')}`,\n sync: (_env, { dir }) => `import { spawnSync } from 'node:child_process';\n\n// sync the content with Mixedbread CLI\nconst result = spawnSync(\n 'npx',\n ['--yes', '@mixedbread/cli', 'vs', 'sync', process.env.MIXEDBREAD_STORE_ID!, '${dir}', '--ci'],\n { stdio: 'inherit' },\n);\n\nprocess.exit(result.status ?? 1);\n`,\n searchRoute: (() => {\n const server = `import { createMixedbreadSearchAPI } from 'fumadocs-core/search/mixedbread';\nimport Mixedbread from '@mixedbread/sdk';\n\nconst server = createMixedbreadSearchAPI({\n client: new Mixedbread({\n apiKey: process.env.MIXEDBREAD_API_KEY,\n }),\n storeIdentifier: process.env.MIXEDBREAD_STORE_ID!,\n});\n`;\n return {\n next: `${server}\nexport const { GET } = server;\n`,\n 'react-router': `import type { Route } from './+types/search';\n${server}\nexport async function loader({ request }: Route.LoaderArgs) {\n return server.GET(request);\n}\n`,\n 'tanstack-start': `import { createFileRoute } from '@tanstack/react-router';\n${server}\nexport const Route = createFileRoute('/api/search')({\n server: {\n handlers: {\n GET: async ({ request }) => server.GET(request),\n },\n },\n});\n`,\n waku: `${server}\nexport const { GET } = server;\n`,\n };\n })(),\n },\n} satisfies Record<string, Provider>;\n\nexport type SearchProvider = keyof typeof providers;\n\n/** pre-rendered on build to export the search indexes */\nexport const staticJson = (framework: ReactFramework) =>\n formatRoute({ segments: ['static.json'] }, framework, null);\n\nexport const staticRoute = (framework: ReactFramework, head: string, documents: string) =>\n routeModule(framework, staticJson(framework), {\n imports: head,\n body: `Response.json(await ${documents})`,\n revalidate: true,\n static: true,\n });\n\n/** where `static.json` is pre-rendered on build, `config` is the Vite config of TanStack Start */\nexport function staticOutput(framework: ReactFramework, config = ''): string {\n switch (framework) {\n case 'next':\n return '.next/server/app/static.json.body';\n case 'react-router':\n return 'build/client/static.json';\n case 'waku':\n return 'dist/public/static.json';\n case 'tanstack-start':\n // Nitro moves the client output to the directory of its preset\n if (/preset:\\s*['\"]vercel['\"]/.test(config)) return '.vercel/output/static/static.json';\n return /\\bnitro\\(/.test(config) ? '.output/public/static.json' : 'dist/client/static.json';\n }\n}\n\n/** runs the sync script after build */\nexport const syncCommand = 'node --env-file-if-exists=.env.local scripts/sync-content.ts';\n\n/** the files written by a provider, relative to the project, `output` is where `static.json` is pre-rendered */\nexport function templates(\n name: SearchProvider,\n framework: ReactFramework,\n { baseDir, source }: { baseDir: string; source: Pick<SourceInfo, 'dynamic' | 'async' | 'dir'> },\n output: string,\n): [file: string, content: string][] {\n const provider: Provider = providers[name];\n const env = publicEnv(framework === 'next');\n const files: [string, string][] = [\n [path.posix.join(baseDir, 'components/search.tsx'), provider.dialog(env)],\n ];\n\n if (provider.exportIndexes) {\n const { head, documents } = provider.exportIndexes(sourceRef(source.dynamic), source.async);\n files.push([\n path.posix.join(baseDir, staticJson(framework).file),\n staticRoute(framework, head, documents),\n ]);\n }\n if (provider.sync)\n files.push(['scripts/sync-content.ts', provider.sync(env, { dir: source.dir, output })]);\n return files;\n}\n\nexport const search: Feature<{ provider: SearchProvider }> = {\n id: 'search',\n title: 'Search',\n description: 'replace the default search with a 3rd party search solution',\n requires: [docs],\n supports: (project) =>\n project.source.loader ||\n project.source.dynamic ||\n '`lib/source.ts` must export a `source` loader',\n options: {\n provider: {\n message: 'Choose a search provider',\n choices: Object.entries(providers).map(([value, { label, hint }]) => ({\n value: value as SearchProvider,\n label,\n hint,\n })),\n },\n },\n async apply(ctx, { provider: name }) {\n const { project } = ctx;\n const { baseDir } = project;\n const framework = reactFramework(project);\n const provider: Provider = providers[name];\n if (project.static && !provider.static)\n throw new Error(`${provider.label} requires a server at runtime`);\n\n const config =\n framework === 'tanstack-start' && project.configFile\n ? await fs.readFile(path.join(project.cwd, project.configFile), 'utf-8')\n : '';\n const output = staticOutput(framework, config);\n\n ctx.addDependencies(provider.dependencies);\n for (const [file, content] of templates(name, framework, project, output))\n await ctx.write(file, content);\n\n // other routes (e.g. MCP) also create a search server, only match the search route itself\n const searchRoute = await findSource(\n project.cwd,\n path.join(baseDir, project.info.routesDir),\n 'createFromSource(',\n (file) => /(^|[\\\\/])search(\\.ts|[\\\\/]route\\.ts)$/.test(file),\n );\n if (provider.searchRoute) {\n if (searchRoute) await ctx.write(searchRoute, provider.searchRoute[framework]);\n else\n ctx.note(\n 'Create the search route with `createMixedbreadSearchAPI`, see https://fumadocs.dev/docs/headless/search/mixedbread.',\n );\n } else if (searchRoute) {\n await removeDefaultSearch(ctx, searchRoute);\n }\n\n if (provider.exportIndexes) {\n const route = staticJson(framework);\n if (framework === 'react-router') {\n await registerReactRouterRoutes(ctx, [route]);\n } else if (framework === 'tanstack-start' && project.configFile) {\n await ctx.source(project.configFile, (file) => addTanstackPrerender(file, [route.path]));\n }\n }\n\n const rootProvider = await findSource(project.cwd, baseDir, '<RootProvider');\n const edited =\n rootProvider !== undefined &&\n (await ctx.source(rootProvider, (file) => {\n const element = findJsxElement(file, 'RootProvider')?.openingElement;\n if (!element || file.code.slice(element.start, element.end).includes('search=')) return;\n addJsxAttribute(file, element, 'search={{ SearchDialog }}');\n addImport(file, { from: '@/components/search', default: 'SearchDialog' });\n }));\n if (!edited)\n ctx.note(\n 'Pass `search={{ SearchDialog }}` from `@/components/search` to `<RootProvider />`.',\n );\n\n if (provider.sync) {\n await ctx.packageJson((data) => {\n const build = data.scripts?.build ?? '';\n if (build.includes('sync-content')) return;\n scripts(data, { build: build ? `${build} && ${syncCommand}` : syncCommand });\n });\n }\n\n const env = publicEnv(framework === 'next');\n for (const key of provider.publicEnv) ctx.env(env.prefix + key, '');\n for (const key of provider.privateEnv) ctx.env(key, '');\n ctx.note(\n `Fill the ${provider.label} keys in \\`.env.local\\`, the search index is synced after each build.\\nSee https://fumadocs.dev/docs/headless/search/${name}.`,\n );\n },\n};\n\n/** remove the default search route, which conflicts with the search provider */\nasync function removeDefaultSearch(ctx: FeatureContext, file: string) {\n const { baseDir, framework } = ctx.project;\n await ctx.remove(file);\n if (framework !== 'react-router') return;\n\n const entry = posix(path.relative(baseDir, file));\n await ctx.source(path.join(baseDir, 'routes.ts'), (source) => {\n filterReactRouterRoute(source, (item) => item.entry !== entry);\n });\n await ctx.source('react-router.config.ts', (source) => {\n filterReactRouterPrerenderArray(source, 'excluded', (item) => item !== '/api/search');\n });\n}\n"],"mappings":";;;;;;AA8BA,MAAM,aAAa,SACjB,OACI;CAAE,QAAQ;CAAgB,MAAM;AAA2B,IAC3D;CAAE,QAAQ;CAAS,MAAM;AAAwB;AAEvD,MAAM,gBAAgB;;;;;;;;;;;;;AActB,MAAM,cAAc,QAAgB,QAAgB;;;;;;;;;;;;oBAYhC,IAAI;;;;gCAIQ,OAAO;;;;;;;;;AAUvC,MAAM,sBACJ,EAAE,KAAK,YACP,UACG,YAAY,IAAI;;;;;uBAKE,SAAS;;;oBAGZ,QAAQ,qCAAqC,2BAA2B;;;;;;;;;;AAW5F,MAAa,cACX,QACA,SACA,SACG;EACH,QAAQ;;gDAEsC,OAAO;;EAErD,KAAK;;;AA8BP,MAAM,YAAY;CAChB,eAAe;EACb,OAAO;EACP,MAAM;EACN,cAAc,EAAE,eAAe,KAAK;EACpC,WAAW;GAAC;GAAuB;GAAoB;EAAe;EACtE,YAAY,CAAC,uBAAuB;EACpC,QAAQ;EACR,SAAS,QAAQ;EACnB,cAAc;;;;;eAKD,IAAI,KAAK;YACZ,IAAI,KAAK;;;;;;;;;;;EAWnB,WAAW,SAAS,mBAAmB;EACrC,gBAAgB,EAAE,WAAW;GAC3B,MAAM,YAAY,IAAI;;GAEtB,WAAW,eAAe,IAAI;EAChC;EACA,OAAO,KAAK,EAAE,aACZ,WACE,QACA;4CAEA;2BACmB,IAAI,OAAO;;;;;uBAKf,IAAI,OAAO;;;CAI5B;CACJ;CACA,SAAS;EACP,OAAO;EACP,MAAM;EACN,cAAc,EAAE,eAAe,KAAK;EACpC,WAAW,CAAC,kBAAkB,oBAAoB;EAClD,YAAY,CAAC,mBAAmB;EAChC,QAAQ;EACR,SAAS,QAAQ;EACnB,cAAc;;;;;6BAKa,IAAI,KAAK,mBAAmB,IAAI,KAAK;;;;;;;;;;;;EAYhE,WAAW,WAAW,qBAAqB;EACzC,gBAAgB,EAAE,WAAW;GAC3B,MAAM,YAAY,IAAI;;GAEtB,WAAW,eAAe,IAAI;EAChC;EACA,OAAO,KAAK,EAAE,aACZ,WACE,QACA;iDAEA;gBACQ,IAAI,OAAO;;;;;;;;CASrB;CACJ;CACA,aAAa;EACX,OAAO;EACP,MAAM;EACN,cAAc,EAAE,aAAa,KAAK;EAClC,WAAW,CAAC,oBAAoB,wBAAwB;EACxD,YAAY,CAAC,uBAAuB;EACpC,QAAQ;EACR,SAAS,QAAQ;EACnB,cAAc;;;;;;UAMN,IAAI,KAAK;YACP,IAAI,KAAK;;;;;;;;;;;;;EAanB,WAAW,eAAe,6BAA6B;EACrD,gBAAgB,EAAE,WAAW;GAC3B,MAAM,YAAY,IAAI;;GAEtB,WAAW,eAAe,IAAI;EAChC;EACA,OAAO,KAAK,EAAE,aACZ,WACE,QACA;6CAEA;sBACc,IAAI,OAAO;;;;;;;;CAS3B;CACJ;CACA,WAAW;EACT,OAAO;EACP,MAAM;EACN,cAAc;GAAE,WAAW;GAAM,8BAA8B;EAAK;EACpE,WAAW,CAAC,iBAAiB,sBAAsB;EACnD,YAAY,CAAC,mBAAmB;EAChC,QAAQ;EACR,SAAS,QAAQ;EACnB,cAAc;;;;;mBAKG,IAAI,KAAK;YAChB,IAAI,KAAK;;;;;;;;;;;EAWnB,WAAW,aAAa,uBAAuB;EAC7C,gBAAgB,KAAK,WAAW;GAC9B,MAAM,mBAAmB,KAAK,KAAK;GACnC,WAAW;EACb;EACA,OAAO,KAAK,EAAE,aACZ,WACE,QACA;sCAEA;+BACuB,IAAI,OAAO;;;;;;;;;CAUpC;CACJ;CACA,YAAY;EACV,OAAO;EACP,MAAM;EACN,cAAc,EAAE,mBAAmB,KAAK;EACxC,WAAW,CAAC;EACZ,YAAY,CAAC,sBAAsB,qBAAqB;EACxD,QAAQ;EACR,cAAc;EAChB,cAAc;;;;;;;;;;;;;EAad,WAAW,cAAc,wBAAwB;EAC/C,OAAO,MAAM,EAAE,UAAU;;;;;kFAKqD,IAAI;;;;;;EAMlF,oBAAoB;GAClB,MAAM,SAAS;;;;;;;;;;GAUf,OAAO;IACL,MAAM,GAAG,OAAO;;;IAGhB,gBAAgB;EACtB,OAAO;;;;;IAKD,kBAAkB;EACxB,OAAO;;;;;;;;;IASD,MAAM,GAAG,OAAO;;;GAGlB;EACF,EAAA,CAAG;CACL;AACF;;AAKA,MAAa,cAAc,cACzB,YAAY,EAAE,UAAU,CAAC,aAAa,EAAE,GAAG,WAAW,IAAI;AAE5D,MAAa,eAAe,WAA2B,MAAc,cACnE,YAAY,WAAW,WAAW,SAAS,GAAG;CAC5C,SAAS;CACT,MAAM,uBAAuB,UAAU;CACvC,YAAY;CACZ,QAAQ;AACV,CAAC;;AAGH,SAAgB,aAAa,WAA2B,SAAS,IAAY;CAC3E,QAAQ,WAAR;EACE,KAAK,QACH,OAAO;EACT,KAAK,gBACH,OAAO;EACT,KAAK,QACH,OAAO;EACT,KAAK;GAEH,IAAI,2BAA2B,KAAK,MAAM,GAAG,OAAO;GACpD,OAAO,YAAY,KAAK,MAAM,IAAI,+BAA+B;CACrE;AACF;;AAGA,MAAa,cAAc;;AAG3B,SAAgB,UACd,MACA,WACA,EAAE,SAAS,UACX,QACmC;CACnC,MAAM,WAAqB,UAAU;CACrC,MAAM,MAAM,UAAU,cAAc,MAAM;CAC1C,MAAM,QAA4B,CAChC,CAAC,KAAK,MAAM,KAAK,SAAS,uBAAuB,GAAG,SAAS,OAAO,GAAG,CAAC,CAC1E;CAEA,IAAI,SAAS,eAAe;EAC1B,MAAM,EAAE,MAAM,cAAc,SAAS,cAAc,UAAU,OAAO,OAAO,GAAG,OAAO,KAAK;EAC1F,MAAM,KAAK,CACT,KAAK,MAAM,KAAK,SAAS,WAAW,SAAS,CAAC,CAAC,IAAI,GACnD,YAAY,WAAW,MAAM,SAAS,CACxC,CAAC;CACH;CACA,IAAI,SAAS,MACX,MAAM,KAAK,CAAC,2BAA2B,SAAS,KAAK,KAAK;EAAE,KAAK,OAAO;EAAK;CAAO,CAAC,CAAC,CAAC;CACzF,OAAO;AACT;AAEA,MAAa,SAAgD;CAC3D,IAAI;CACJ,OAAO;CACP,aAAa;CACb,UAAU,CAAC,IAAI;CACf,WAAW,YACT,QAAQ,OAAO,UACf,QAAQ,OAAO,WACf;CACF,SAAS,EACP,UAAU;EACR,SAAS;EACT,SAAS,OAAO,QAAQ,SAAS,CAAC,CAAC,KAAK,CAAC,OAAO,EAAE,OAAO,aAAa;GAC7D;GACP;GACA;EACF,EAAE;CACJ,EACF;CACA,MAAM,MAAM,KAAK,EAAE,UAAU,QAAQ;EACnC,MAAM,EAAE,YAAY;EACpB,MAAM,EAAE,YAAY;EACpB,MAAM,YAAY,eAAe,OAAO;EACxC,MAAM,WAAqB,UAAU;EACrC,IAAI,QAAQ,UAAU,CAAC,SAAS,QAC9B,MAAM,IAAI,MAAM,GAAG,SAAS,MAAM,8BAA8B;EAMlE,MAAM,SAAS,aAAa,WAH1B,cAAc,oBAAoB,QAAQ,aACtC,MAAM,GAAG,SAAS,KAAK,KAAK,QAAQ,KAAK,QAAQ,UAAU,GAAG,OAAO,IACrE,EACuC;EAE7C,IAAI,gBAAgB,SAAS,YAAY;EACzC,KAAK,MAAM,CAAC,MAAM,YAAY,UAAU,MAAM,WAAW,SAAS,MAAM,GACtE,MAAM,IAAI,MAAM,MAAM,OAAO;EAG/B,MAAM,cAAc,MAAM,WACxB,QAAQ,KACR,KAAK,KAAK,SAAS,QAAQ,KAAK,SAAS,GACzC,sBACC,SAAS,wCAAwC,KAAK,IAAI,CAC7D;EACA,IAAI,SAAS,aAAa;GACxB,IAAI,aAAa,MAAM,IAAI,MAAM,aAAa,SAAS,YAAY,UAAU;QAE3E,IAAI,KACF,qHACF;EACJ,OAAO,IAAI,aACT,MAAM,oBAAoB,KAAK,WAAW;EAG5C,IAAI,SAAS,eAAe;GAC1B,MAAM,QAAQ,WAAW,SAAS;GAClC,IAAI,cAAc,gBAChB,MAAM,0BAA0B,KAAK,CAAC,KAAK,CAAC;QACvC,IAAI,cAAc,oBAAoB,QAAQ,YACnD,MAAM,IAAI,OAAO,QAAQ,aAAa,SAAS,qBAAqB,MAAM,CAAC,MAAM,IAAI,CAAC,CAAC;EAE3F;EAEA,MAAM,eAAe,MAAM,WAAW,QAAQ,KAAK,SAAS,eAAe;EAS3E,IAAI,EAPF,iBAAiB,KAAA,KAChB,MAAM,IAAI,OAAO,eAAe,SAAS;GACxC,MAAM,UAAU,eAAe,MAAM,cAAc,CAAC,EAAE;GACtD,IAAI,CAAC,WAAW,KAAK,KAAK,MAAM,QAAQ,OAAO,QAAQ,GAAG,CAAC,CAAC,SAAS,SAAS,GAAG;GACjF,gBAAgB,MAAM,SAAS,2BAA2B;GAC1D,UAAU,MAAM;IAAE,MAAM;IAAuB,SAAS;GAAe,CAAC;EAC1E,CAAC,IAED,IAAI,KACF,oFACF;EAEF,IAAI,SAAS,MACX,MAAM,IAAI,aAAa,SAAS;GAC9B,MAAM,QAAQ,KAAK,SAAS,SAAS;GACrC,IAAI,MAAM,SAAS,cAAc,GAAG;GACpC,QAAQ,MAAM,EAAE,OAAO,QAAQ,GAAG,MAAM,MAAM,gBAAgB,YAAY,CAAC;EAC7E,CAAC;EAGH,MAAM,MAAM,UAAU,cAAc,MAAM;EAC1C,KAAK,MAAM,OAAO,SAAS,WAAW,IAAI,IAAI,IAAI,SAAS,KAAK,EAAE;EAClE,KAAK,MAAM,OAAO,SAAS,YAAY,IAAI,IAAI,KAAK,EAAE;EACtD,IAAI,KACF,YAAY,SAAS,MAAM,uHAAuH,KAAK,EACzJ;CACF;AACF;;AAGA,eAAe,oBAAoB,KAAqB,MAAc;CACpE,MAAM,EAAE,SAAS,cAAc,IAAI;CACnC,MAAM,IAAI,OAAO,IAAI;CACrB,IAAI,cAAc,gBAAgB;CAElC,MAAM,QAAQ,MAAM,KAAK,SAAS,SAAS,IAAI,CAAC;CAChD,MAAM,IAAI,OAAO,KAAK,KAAK,SAAS,WAAW,IAAI,WAAW;EAC5D,uBAAuB,SAAS,SAAS,KAAK,UAAU,KAAK;CAC/D,CAAC;CACD,MAAM,IAAI,OAAO,2BAA2B,WAAW;EACrD,gCAAgC,QAAQ,aAAa,SAAS,SAAS,aAAa;CACtF,CAAC;AACH"}