@fumadocs/cli 1.7.4 → 1.7.6
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.
- package/dist/{ai-BoUGzPvv.js → ai-CvrtLYh1.js} +8 -6
- package/dist/ai-CvrtLYh1.js.map +1 -0
- package/dist/all-bAINnjNQ.js +26 -0
- package/dist/{all-BmTB7dab.js.map → all-bAINnjNQ.js.map} +1 -1
- package/dist/codemod/index.d.ts +1 -1
- package/dist/codemod/index.js +1 -2
- package/dist/{codemod-auol-p4R.js → codemod-Wc9nRqrw.js} +60 -39
- package/dist/codemod-Wc9nRqrw.js.map +1 -0
- package/dist/{config-Bw0kmF01.js → config-BpiQyqBs.js} +13 -8
- package/dist/config-BpiQyqBs.js.map +1 -0
- package/dist/{config-Bfg4yG8S.d.ts → config-Z-4b_Y_C.d.ts} +1 -3
- package/dist/config-Z-4b_Y_C.d.ts.map +1 -0
- package/dist/config.d.ts +1 -1
- package/dist/config.js +1 -2
- package/dist/{docs-qRGwd9Yp.js → docs---dy-BSm.js} +286 -32
- package/dist/docs---dy-BSm.js.map +1 -0
- package/dist/{epub-DbOh5Q2D.js → epub-BKvZ4ppz.js} +12 -13
- package/dist/epub-BKvZ4ppz.js.map +1 -0
- package/dist/features/ai.d.ts +2 -3
- package/dist/features/ai.d.ts.map +1 -1
- package/dist/features/ai.js +1 -2
- package/dist/features/all.d.ts +1 -3
- package/dist/features/all.d.ts.map +1 -1
- package/dist/features/all.js +1 -2
- package/dist/features/docs/index.d.ts +2 -4
- package/dist/features/docs/index.d.ts.map +1 -1
- package/dist/features/docs/index.js +1 -2
- package/dist/features/epub.d.ts +1 -3
- package/dist/features/epub.d.ts.map +1 -1
- package/dist/features/epub.js +1 -2
- package/dist/features/feedback.d.ts +1 -3
- package/dist/features/feedback.d.ts.map +1 -1
- package/dist/features/feedback.js +1 -2
- package/dist/features/index.d.ts +1 -1
- package/dist/features/index.js +1 -2
- package/dist/features/lint.d.ts +1 -3
- package/dist/features/lint.d.ts.map +1 -1
- package/dist/features/lint.js +1 -2
- package/dist/features/llms.d.ts +6 -7
- package/dist/features/llms.d.ts.map +1 -1
- package/dist/features/llms.js +1 -2
- package/dist/features/mcp.d.ts +5 -7
- package/dist/features/mcp.d.ts.map +1 -1
- package/dist/features/mcp.js +1 -2
- package/dist/features/og.d.ts +2 -3
- package/dist/features/og.d.ts.map +1 -1
- package/dist/features/og.js +1 -2
- package/dist/features/search.d.ts +10 -10
- package/dist/features/search.d.ts.map +1 -1
- package/dist/features/search.js +1 -2
- package/dist/features/utils.d.ts +2 -2
- package/dist/features/utils.js +1 -2
- package/dist/features/webmcp.d.ts +2 -3
- package/dist/features/webmcp.d.ts.map +1 -1
- package/dist/features/webmcp.js +1 -2
- package/dist/{features-Do6x9Tsz.js → features-C_FG2WD0.js} +5 -6
- package/dist/{features-Do6x9Tsz.js.map → features-C_FG2WD0.js.map} +1 -1
- package/dist/{feedback-DpXYMKtX.js → feedback-7u1-eklc.js} +6 -7
- package/dist/feedback-7u1-eklc.js.map +1 -0
- package/dist/{fs-D6J9dOEx.js → fs-Dqr1VJSD.js} +2 -3
- package/dist/{fs-D6J9dOEx.js.map → fs-Dqr1VJSD.js.map} +1 -1
- package/dist/{index-Cs7hw2un.d.ts → index-CAIqrY1S.d.ts} +3 -5
- package/dist/index-CAIqrY1S.d.ts.map +1 -0
- package/dist/{index-DI66DoV8.d.ts → index-DCJbgkgf.d.ts} +4 -8
- package/dist/index-DCJbgkgf.d.ts.map +1 -0
- package/dist/{index-Ck0EPdgu.d.ts → index-w9FHKPPs.d.ts} +6 -16
- package/dist/index-w9FHKPPs.d.ts.map +1 -0
- package/dist/index.js +22 -31
- package/dist/index.js.map +1 -1
- package/dist/{installer-C4662Woc.js → installer-CEOV0fBv.js} +3 -5
- package/dist/{installer-C4662Woc.js.map → installer-CEOV0fBv.js.map} +1 -1
- package/dist/{lint-zkXUULgO.js → lint-AC2ClOfa.js} +3 -4
- package/dist/{lint-zkXUULgO.js.map → lint-AC2ClOfa.js.map} +1 -1
- package/dist/{llms-DQzAst6R.js → llms-Cge1uefC.js} +11 -12
- package/dist/llms-Cge1uefC.js.map +1 -0
- package/dist/{mcp-BpFZ_fdV.js → mcp-BVpVFl6G.js} +7 -9
- package/dist/mcp-BVpVFl6G.js.map +1 -0
- package/dist/{og-m0CwRlRz.js → og-DuPd9kNq.js} +12 -11
- package/dist/og-DuPd9kNq.js.map +1 -0
- package/dist/project/index.d.ts +2 -2
- package/dist/project/index.js +1 -2
- package/dist/{project-DwRVTm4p.js → project-D655VGTX.js} +19 -10
- package/dist/project-D655VGTX.js.map +1 -0
- package/dist/registry/installer.d.ts +1 -3
- package/dist/registry/installer.d.ts.map +1 -1
- package/dist/registry/installer.js +1 -2
- package/dist/{search-BCHEW0-K.js → search-B44II4bM.js} +39 -30
- package/dist/search-B44II4bM.js.map +1 -0
- package/dist/{utils-DbMe7h6c.js → utils-Btt-BJ53.js} +6 -12
- package/dist/utils-Btt-BJ53.js.map +1 -0
- package/dist/{utils-CxJ7kw39.d.ts → utils-DlGz_4va.d.ts} +6 -12
- package/dist/utils-DlGz_4va.d.ts.map +1 -0
- package/dist/{webmcp-I9L7xzmw.js → webmcp-lY3_RsR3.js} +6 -8
- package/dist/webmcp-lY3_RsR3.js.map +1 -0
- package/package.json +7 -7
- package/dist/ai-BoUGzPvv.js.map +0 -1
- package/dist/all-BmTB7dab.js +0 -27
- package/dist/codemod-auol-p4R.js.map +0 -1
- package/dist/config-Bfg4yG8S.d.ts.map +0 -1
- package/dist/config-Bw0kmF01.js.map +0 -1
- package/dist/docs-qRGwd9Yp.js.map +0 -1
- package/dist/epub-DbOh5Q2D.js.map +0 -1
- package/dist/feedback-DpXYMKtX.js.map +0 -1
- package/dist/index-Ck0EPdgu.d.ts.map +0 -1
- package/dist/index-Cs7hw2un.d.ts.map +0 -1
- package/dist/index-DI66DoV8.d.ts.map +0 -1
- package/dist/llms-DQzAst6R.js.map +0 -1
- package/dist/mcp-BpFZ_fdV.js.map +0 -1
- package/dist/og-m0CwRlRz.js.map +0 -1
- package/dist/project-DwRVTm4p.js.map +0 -1
- package/dist/search-BCHEW0-K.js.map +0 -1
- package/dist/utils-CxJ7kw39.d.ts.map +0 -1
- package/dist/utils-DbMe7h6c.js.map +0 -1
- package/dist/webmcp-I9L7xzmw.js.map +0 -1
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import { c as addNextRewrites, n as enableProcessedMarkdown, p as addImport, r as addTanstackPrerender, t as addProxyMatcher } from "./codemod-
|
|
2
|
-
import { a as
|
|
3
|
-
import { a as
|
|
1
|
+
import { c as addNextRewrites, n as enableProcessedMarkdown, p as addImport, r as addTanstackPrerender, t as addProxyMatcher } from "./codemod-Wc9nRqrw.js";
|
|
2
|
+
import { a as reactRouterTypes, i as formatRoute, n as findNextProxy, o as routeModule, r as astroPageRoute, t as docs } from "./docs---dy-BSm.js";
|
|
3
|
+
import { a as registerReactRouterRoutes, c as sharedRoute, l as sourceRef, n as addPageUrl, o as requiresMarkdown, t as addExport } from "./utils-Btt-BJ53.js";
|
|
4
4
|
import path from "node:path";
|
|
5
|
-
|
|
5
|
+
// src/features/llms.ts
|
|
6
6
|
/** runtime content sources expose raw Markdown as `page.data.content`, Fumadocs MDX processes it */
|
|
7
7
|
const docsLlms = ({ dynamic, ref }) => `
|
|
8
8
|
export const docsLlms = llms(${ref}, {
|
|
@@ -123,7 +123,8 @@ export async function getConfig() {
|
|
|
123
123
|
staticPaths: ${src.resolved}.generateParams().map((item) => ${i18n ? "[item.lang, ...item.slug]" : "item.slug"}),
|
|
124
124
|
} as const;
|
|
125
125
|
}
|
|
126
|
-
|
|
126
|
+
`,
|
|
127
|
+
astro: (_route, i18n, src) => astroPageRoute(i18n, pageImports(src), markdown)
|
|
127
128
|
};
|
|
128
129
|
function templates(framework, routes, i18n, src) {
|
|
129
130
|
const index = (body) => ({
|
|
@@ -164,12 +165,11 @@ const llms = {
|
|
|
164
165
|
requires: [docs],
|
|
165
166
|
supports: requiresMarkdown,
|
|
166
167
|
async apply(ctx) {
|
|
167
|
-
const { baseDir, i18n, source, configFile } = ctx.project;
|
|
168
|
-
const framework = reactFramework(ctx.project);
|
|
168
|
+
const { baseDir, i18n, source, configFile, framework } = ctx.project;
|
|
169
169
|
const sourceFile = path.join(baseDir, "lib/source.ts");
|
|
170
170
|
const src = sourceRef(ctx.project.source.dynamic);
|
|
171
171
|
if (source.collections) await ctx.source(source.collections, (file) => {
|
|
172
|
-
if (!enableProcessedMarkdown(file)) throw new Error(`cannot
|
|
172
|
+
if (!enableProcessedMarkdown(file)) throw new Error(`cannot enable \`includeProcessedMarkdown\` in ${source.collections}`);
|
|
173
173
|
});
|
|
174
174
|
if (await addExport(ctx, sourceFile, "docsLlms", docsLlms(src))) await ctx.source(sourceFile, (file) => addImport(file, {
|
|
175
175
|
from: "fumadocs-core/source",
|
|
@@ -185,7 +185,7 @@ const llms = {
|
|
|
185
185
|
await addPageUrl(ctx, "getPageMarkdownUrl", markdownUrl(i18n !== null));
|
|
186
186
|
}
|
|
187
187
|
const routes = llmsRoutes(framework, i18n, docsRoute, contentRoute);
|
|
188
|
-
for (const [route, content] of templates(framework, routes, i18n
|
|
188
|
+
for (const [route, content] of templates(framework, routes, i18n, src)) await ctx.write(path.join(baseDir, route.file), content);
|
|
189
189
|
if (framework === "react-router") await registerReactRouterRoutes(ctx, [
|
|
190
190
|
routes.index,
|
|
191
191
|
routes.full,
|
|
@@ -196,7 +196,7 @@ const llms = {
|
|
|
196
196
|
if (i18n) await extendNextProxy(ctx, routes.markdown);
|
|
197
197
|
await configureMarkdownUrl(ctx, docsRoute, routes.markdown);
|
|
198
198
|
}
|
|
199
|
-
if (ctx.project.static && framework
|
|
199
|
+
if (ctx.project.static && (framework === "react-router" || framework === "tanstack-start")) ctx.note("Per-page Markdown routes are dynamic, add the pages to your prerender config to serve them statically.");
|
|
200
200
|
if (framework === "react-router" || framework === "waku") ctx.note("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.");
|
|
201
201
|
ctx.note("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'.");
|
|
202
202
|
}
|
|
@@ -222,7 +222,6 @@ async function extendNextProxy(ctx, route) {
|
|
|
222
222
|
if (!addProxyMatcher(file, patterns)) throw new Error("cannot find the matcher");
|
|
223
223
|
}).catch(() => false))) ctx.note(`Add ${patterns.map((p) => `\`${p}\``).join(", ")} to the matcher of your i18n middleware.`);
|
|
224
224
|
}
|
|
225
|
-
//#endregion
|
|
226
225
|
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
226
|
|
|
228
|
-
//# sourceMappingURL=llms-
|
|
227
|
+
//# sourceMappingURL=llms-Cge1uefC.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"llms-Cge1uefC.js","names":[],"sources":["../src/features/llms.ts"],"sourcesContent":["import path from 'node:path';\nimport type { Feature, FeatureContext } from '@/features';\nimport type { Framework, I18nInfo } from '@/project';\nimport {\n astroPageRoute,\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 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: I18nInfo | null, src: SourceRef) => string;\n\n/** the per-page Markdown route, the only one reading route params */\nconst markdownTemplates: Record<Framework, 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 astro: (_route, i18n, src) => astroPageRoute(i18n, pageImports(src), markdown),\n};\n\nexport function templates(\n framework: Framework,\n routes: Routes,\n i18n: I18nInfo | null,\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: Framework,\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, framework } = 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 enable \\`includeProcessedMarkdown\\` 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, 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 === 'react-router' || framework === 'tanstack-start')) {\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,9 +1,9 @@
|
|
|
1
|
-
import { i as addReactRouterPrerenderArray } from "./codemod-
|
|
2
|
-
import {
|
|
3
|
-
import { a as
|
|
4
|
-
import { r as llms } from "./llms-
|
|
1
|
+
import { i as addReactRouterPrerenderArray } from "./codemod-Wc9nRqrw.js";
|
|
2
|
+
import { i as formatRoute, o as routeModule } from "./docs---dy-BSm.js";
|
|
3
|
+
import { a as registerReactRouterRoutes, l as sourceRef, o as requiresMarkdown } from "./utils-Btt-BJ53.js";
|
|
4
|
+
import { r as llms } from "./llms-Cge1uefC.js";
|
|
5
5
|
import path from "node:path";
|
|
6
|
-
|
|
6
|
+
// src/features/mcp.ts
|
|
7
7
|
const server = ({ ref }) => `import { createMcpHandler, McpServer } from '@modelcontextprotocol/server';
|
|
8
8
|
import { registerSearchTool, registerSourceTools } from 'fumadocs-core/mcp';
|
|
9
9
|
import { createFromSource } from 'fumadocs-core/search/server';
|
|
@@ -39,8 +39,7 @@ const mcp = {
|
|
|
39
39
|
requires: [llms],
|
|
40
40
|
supports: (project) => project.static ? "MCP requires a server at runtime" : requiresMarkdown(project),
|
|
41
41
|
async apply(ctx) {
|
|
42
|
-
const { baseDir } = ctx.project;
|
|
43
|
-
const framework = reactFramework(ctx.project);
|
|
42
|
+
const { baseDir, framework } = ctx.project;
|
|
44
43
|
ctx.addDependencies({
|
|
45
44
|
"@modelcontextprotocol/server": null,
|
|
46
45
|
zod: null
|
|
@@ -61,7 +60,6 @@ const mcp = {
|
|
|
61
60
|
}`);
|
|
62
61
|
}
|
|
63
62
|
};
|
|
64
|
-
//#endregion
|
|
65
63
|
export { mcpRoute as n, templates as r, mcp as t };
|
|
66
64
|
|
|
67
|
-
//# sourceMappingURL=mcp-
|
|
65
|
+
//# sourceMappingURL=mcp-BVpVFl6G.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"mcp-BVpVFl6G.js","names":[],"sources":["../src/features/mcp.ts"],"sourcesContent":["import path from 'node:path';\nimport type { Feature } from '@/features';\nimport type { Framework } from '@/project';\nimport { type FormattedRoute, formatRoute, routeModule } from '@/project/route';\nimport { addReactRouterPrerenderArray } from '@/codemod';\nimport { llms } from './llms';\nimport { registerReactRouterRoutes, requiresMarkdown, type SourceRef, sourceRef } 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: Framework, 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: Framework) =>\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, framework } = 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":";;;;;;AAQA,MAAM,UAAU,EACd,UACe;;;qBAGI,IAAI;;;;;;;;6BAQI,IAAI;6CACY,IAAI;;;;;AAMjD,MAAa,aAAa,WAAsB,OAAuB,QACrE,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,SAAS,cAAc,IAAI;EACnC,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,9 +1,9 @@
|
|
|
1
|
-
import { S as getProperty, f as addElements, u as getConfigObject } from "./codemod-
|
|
2
|
-
import {
|
|
3
|
-
import { a as
|
|
4
|
-
import { n as extendNextProxy } from "./llms-
|
|
1
|
+
import { S as getProperty, f as addElements, u as getConfigObject } from "./codemod-Wc9nRqrw.js";
|
|
2
|
+
import { a as reactRouterTypes, i as formatRoute, r as astroPageRoute, t as docs } from "./docs---dy-BSm.js";
|
|
3
|
+
import { a as registerReactRouterRoutes, c as sharedRoute, l as sourceRef, n as addPageUrl } from "./utils-Btt-BJ53.js";
|
|
4
|
+
import { n as extendNextProxy } from "./llms-Cge1uefC.js";
|
|
5
5
|
import path from "node:path";
|
|
6
|
-
|
|
6
|
+
// src/features/og.ts
|
|
7
7
|
/** the image URL of a page, `/og/docs/<slugs>/image.png` */
|
|
8
8
|
const imageUrl = (ext, i18n) => `
|
|
9
9
|
const getImageUrl = createGetUrl(docsImageRoute${i18n ? ", i18n" : ""});
|
|
@@ -89,7 +89,8 @@ export async function getConfig() {
|
|
|
89
89
|
staticPaths: ${src.resolved}.generateParams().map((item) => ${i18n ? "[item.lang, ...item.slug]" : "item.slug"}),
|
|
90
90
|
} as const;
|
|
91
91
|
}
|
|
92
|
-
|
|
92
|
+
`,
|
|
93
|
+
astro: (_route, engine, i18n, src) => astroPageRoute(i18n, `${imports(engine)}\nimport { ${src.ref} } from '@/lib/source';`, image(engine))
|
|
93
94
|
};
|
|
94
95
|
const og = {
|
|
95
96
|
id: "og",
|
|
@@ -109,8 +110,7 @@ const og = {
|
|
|
109
110
|
}]
|
|
110
111
|
} },
|
|
111
112
|
async apply(ctx, { engine }) {
|
|
112
|
-
const { baseDir, configFile, i18n, source } = ctx.project;
|
|
113
|
-
const framework = reactFramework(ctx.project);
|
|
113
|
+
const { baseDir, configFile, i18n, source, framework } = ctx.project;
|
|
114
114
|
if (engine === "next-og" && framework !== "next") throw new Error("next/og is only available on Next.js");
|
|
115
115
|
if (engine === "takumi") ctx.addDependencies({ "takumi-js": null });
|
|
116
116
|
const shared = path.join(baseDir, "lib/shared.ts");
|
|
@@ -126,17 +126,18 @@ const og = {
|
|
|
126
126
|
}],
|
|
127
127
|
locale: true
|
|
128
128
|
}, framework, i18n, "tsx");
|
|
129
|
-
await ctx.write(path.join(baseDir, route.file), templates[framework](route, engine, i18n
|
|
129
|
+
await ctx.write(path.join(baseDir, route.file), templates[framework](route, engine, i18n, sourceRef(ctx.project.source.dynamic)));
|
|
130
130
|
if (framework === "react-router") await registerReactRouterRoutes(ctx, [route]);
|
|
131
131
|
if (framework === "next" && engine === "takumi" && configFile) await ctx.source(configFile, (file) => {
|
|
132
132
|
const config = getConfigObject(file);
|
|
133
133
|
if (config && !getProperty(config, "serverExternalPackages")) addElements(file, config, ["serverExternalPackages: ['@takumi-rs/core']"]);
|
|
134
134
|
});
|
|
135
135
|
if (framework === "next" && i18n) await extendNextProxy(ctx, route);
|
|
136
|
+
if (ctx.project.static && framework === "react-router") ctx.note("Image routes are dynamic, add `getPageImageUrl(page).url` of each page to your prerender config.");
|
|
137
|
+
else if (ctx.project.static && framework === "tanstack-start") ctx.note("TanStack Start prerenders responses as text, which breaks images. Prerender the image routes with Nitro instead, see https://fumadocs.dev/docs/integrations/og/takumi.");
|
|
136
138
|
ctx.note("The image URL of a page is `getPageImageUrl(page).url` from '@/lib/shared', reference it in the page metadata (e.g. `og:image`).");
|
|
137
139
|
}
|
|
138
140
|
};
|
|
139
|
-
//#endregion
|
|
140
141
|
export { og as t };
|
|
141
142
|
|
|
142
|
-
//# sourceMappingURL=og-
|
|
143
|
+
//# sourceMappingURL=og-DuPd9kNq.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"og-DuPd9kNq.js","names":[],"sources":["../src/features/og.ts"],"sourcesContent":["import path from 'node:path';\nimport type { Feature } from '@/features';\nimport type { Framework, I18nInfo } from '@/project';\nimport {\n astroPageRoute,\n type FormattedRoute,\n formatRoute,\n reactRouterTypes,\n} from '@/project/route';\nimport { addElements, getConfigObject, getProperty } from '@/codemod';\nimport { docs } from './docs';\nimport { extendNextProxy } from './llms';\nimport {\n addPageUrl,\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 = (\n route: FormattedRoute,\n engine: Engine,\n i18n: I18nInfo | null,\n src: SourceRef,\n) => string;\n\nconst templates: Record<Framework, 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 astro: (_route, engine, i18n, src) =>\n astroPageRoute(\n i18n,\n `${imports(engine)}\\nimport { ${src.ref} } from '@/lib/source';`,\n image(engine),\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, framework } = 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, 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 if (ctx.project.static && framework === 'react-router') {\n ctx.note(\n 'Image routes are dynamic, add `getPageImageUrl(page).url` of each page to your prerender config.',\n );\n } else if (ctx.project.static && framework === 'tanstack-start') {\n ctx.note(\n 'TanStack Start prerenders responses as text, which breaks images. Prerender the image routes with Nitro instead, see https://fumadocs.dev/docs/integrations/og/takumi.',\n );\n }\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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"}
|
package/dist/project/index.d.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { a as Project, c as
|
|
2
|
-
export { Framework, FrameworkInfo, I18nInfo, PackageManager, Project,
|
|
1
|
+
import { a as Project, c as frameworks, i as PackageManager, l as isSupportedFramework, n as FrameworkInfo, o as SourceInfo, r as I18nInfo, s as detectFramework, t as Framework, u as loadProject } from "../index-DCJbgkgf.js";
|
|
2
|
+
export { Framework, FrameworkInfo, I18nInfo, PackageManager, Project, SourceInfo, detectFramework, frameworks, isSupportedFramework, loadProject };
|
package/dist/project/index.js
CHANGED
|
@@ -1,2 +1 @@
|
|
|
1
|
-
|
|
2
|
-
export { detectFramework, frameworks, isSupportedFramework, loadProject };
|
|
1
|
+
export { t as detectFramework, n as frameworks, r as isSupportedFramework, i as loadProject } from "../project-D655VGTX.js";
|
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import { t as exists } from "./fs-
|
|
1
|
+
import { t as exists } from "./fs-Dqr1VJSD.js";
|
|
2
2
|
import fs from "node:fs/promises";
|
|
3
3
|
import path from "node:path";
|
|
4
4
|
import { detectFramework as detectFramework$1, detectPackageManager } from "fuma-cli/detect";
|
|
5
|
-
|
|
5
|
+
// src/project/index.ts
|
|
6
6
|
const frameworks = {
|
|
7
7
|
next: {
|
|
8
8
|
provider: "fumadocs-ui/provider/next",
|
|
@@ -38,7 +38,13 @@ const frameworks = {
|
|
|
38
38
|
provider: "fumadocs-ui/provider/astro",
|
|
39
39
|
rootFile: "layouts/Layout.astro",
|
|
40
40
|
routesDir: "pages",
|
|
41
|
-
configFiles: [
|
|
41
|
+
configFiles: [
|
|
42
|
+
"astro.config.mjs",
|
|
43
|
+
"astro.config.ts",
|
|
44
|
+
"astro.config.js",
|
|
45
|
+
"astro.config.mts"
|
|
46
|
+
],
|
|
47
|
+
isStatic: (config) => !/output:\s*['"]server['"]/.test(config)
|
|
42
48
|
}
|
|
43
49
|
};
|
|
44
50
|
function isSupportedFramework(v) {
|
|
@@ -58,7 +64,7 @@ async function loadProject(config, cwd = process.cwd(), framework = assertFramew
|
|
|
58
64
|
if (framework === "react-router") {
|
|
59
65
|
const content = await fs.readFile(path.join(cwd, "react-router.config.ts"), "utf-8").catch(() => "");
|
|
60
66
|
isStatic = /ssr:\s*false/.test(content);
|
|
61
|
-
} else if (
|
|
67
|
+
} else if (info.isStatic) isStatic = info.isStatic(configContent ?? "");
|
|
62
68
|
const read = (file) => fs.readFile(path.join(cwd, file), "utf-8").catch(() => null);
|
|
63
69
|
const i18nConfig = await read(path.join(config.baseDir, "lib/i18n.ts"));
|
|
64
70
|
return {
|
|
@@ -72,16 +78,20 @@ async function loadProject(config, cwd = process.cwd(), framework = assertFramew
|
|
|
72
78
|
config,
|
|
73
79
|
configFile,
|
|
74
80
|
packageJson,
|
|
75
|
-
source: await detectSource(cwd, config.baseDir, read)
|
|
81
|
+
source: await detectSource(cwd, config.baseDir, read, configFile, configContent)
|
|
76
82
|
};
|
|
77
83
|
}
|
|
78
|
-
async function detectSource(cwd, baseDir, read) {
|
|
84
|
+
async function detectSource(cwd, baseDir, read, configFile, configContent) {
|
|
79
85
|
const sourceFile = path.join(baseDir, "lib/source.ts");
|
|
80
86
|
const source = await read(sourceFile) ?? "";
|
|
81
87
|
let collections = null;
|
|
82
88
|
let content = source;
|
|
89
|
+
const astro = source.includes("@fumadocs/astro/source");
|
|
83
90
|
if (source.includes("defineDocs(")) collections = sourceFile;
|
|
84
|
-
else if (
|
|
91
|
+
else if (astro) {
|
|
92
|
+
collections = configFile ?? null;
|
|
93
|
+
content = configContent ?? "";
|
|
94
|
+
} else if (await exists(path.join(cwd, "source.config.ts"))) {
|
|
85
95
|
collections = "source.config.ts";
|
|
86
96
|
content = await read(collections) ?? "";
|
|
87
97
|
}
|
|
@@ -95,7 +105,7 @@ async function detectSource(cwd, baseDir, read) {
|
|
|
95
105
|
loader: /export const source\b/.test(source),
|
|
96
106
|
dynamic: /export (async )?function getSource\b/.test(source),
|
|
97
107
|
collections,
|
|
98
|
-
async: /async:\s*true/.test(content),
|
|
108
|
+
async: astro || /async:\s*true/.test(content),
|
|
99
109
|
baseUrl: baseUrl ?? "/docs",
|
|
100
110
|
dir: /dir:\s*['"]([^'"]*)['"]/.exec(content)?.[1] ?? "content/docs"
|
|
101
111
|
};
|
|
@@ -104,7 +114,6 @@ function assertFramework(framework) {
|
|
|
104
114
|
if (isSupportedFramework(framework)) return framework;
|
|
105
115
|
throw new Error(`Unsupported framework "${framework}", supported: ${Object.keys(frameworks).join(", ")}.`);
|
|
106
116
|
}
|
|
107
|
-
//#endregion
|
|
108
117
|
export { loadProject as i, frameworks as n, isSupportedFramework as r, detectFramework$1 as t };
|
|
109
118
|
|
|
110
|
-
//# sourceMappingURL=project-
|
|
119
|
+
//# sourceMappingURL=project-D655VGTX.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"project-D655VGTX.js","names":[],"sources":["../src/project/index.ts"],"sourcesContent":["import fs from 'node:fs/promises';\nimport path from 'node:path';\nimport { detectFramework, detectPackageManager } from 'fuma-cli/detect';\nimport type { LoadedConfig } from '@/config';\nimport { exists } from '@/utils/fs';\n\nexport type PackageManager = NonNullable<Awaited<ReturnType<typeof detectPackageManager>>>['name'];\n\nexport type Framework = 'next' | 'react-router' | 'tanstack-start' | 'waku' | 'astro';\n\nexport interface FrameworkInfo {\n /** import specifier of `RootProvider` */\n provider: string;\n /** root file that imports the global CSS, relative to base dir */\n rootFile: string;\n /** routes directory, relative to base dir */\n routesDir: string;\n /** config file candidates (relative to cwd) that register bundler plugins */\n configFiles: string[];\n /** how to detect `static` mode from the config file */\n isStatic?: (config: string) => boolean;\n}\n\nexport const frameworks: Record<Framework, FrameworkInfo> = {\n next: {\n provider: 'fumadocs-ui/provider/next',\n rootFile: 'app/layout.tsx',\n routesDir: 'app',\n configFiles: ['next.config.mjs', 'next.config.ts', 'next.config.js'],\n isStatic: (config) => /output:\\s*['\"]export['\"]/.test(config),\n },\n 'react-router': {\n provider: 'fumadocs-ui/provider/react-router',\n rootFile: 'root.tsx',\n routesDir: 'routes',\n configFiles: ['vite.config.ts'],\n },\n 'tanstack-start': {\n provider: 'fumadocs-ui/provider/tanstack',\n rootFile: 'routes/__root.tsx',\n routesDir: 'routes',\n configFiles: ['vite.config.ts'],\n isStatic: (config) => /\\bspa:\\s*\\{/.test(config),\n },\n waku: {\n provider: 'fumadocs-ui/provider/waku',\n rootFile: 'pages/_root.tsx',\n routesDir: 'pages',\n configFiles: ['waku.config.ts'],\n },\n astro: {\n provider: 'fumadocs-ui/provider/astro',\n rootFile: 'layouts/Layout.astro',\n routesDir: 'pages',\n configFiles: ['astro.config.mjs', 'astro.config.ts', 'astro.config.js', 'astro.config.mts'],\n isStatic: (config) => !/output:\\s*['\"]server['\"]/.test(config),\n },\n};\n\n/** how docs content is loaded, detected from `lib/source.ts` */\nexport interface SourceInfo {\n /** `lib/source.ts` exports a `source` loader */\n loader: boolean;\n /** `lib/source.ts` exports `getSource()`, a runtime content source resolved on demand */\n dynamic: boolean;\n /** file configuring the collections, relative to cwd: `lib/source.ts` (Fumadocs MDX macro), `source.config.ts` or the Astro config; `null` for other content sources */\n collections: string | null;\n /** collections are loaded lazily, `structuredData` is a function */\n async: boolean;\n /** base URL of docs pages */\n baseUrl: string;\n /** content directory */\n dir: string;\n}\n\nexport interface I18nInfo {\n /** the default locale is hidden from URLs (`hideLocale: 'default-locale'`) */\n optionalLocale: boolean;\n}\n\nexport interface Project {\n cwd: string;\n framework: Framework;\n info: FrameworkInfo;\n /** prerendered to static HTML, no server at runtime */\n static: boolean;\n /** `lib/i18n.ts` exists */\n i18n: I18nInfo | null;\n source: SourceInfo;\n packageManager: PackageManager;\n /** directory of app code, relative to `cwd` */\n baseDir: string;\n config: LoadedConfig;\n /** config file that registers bundler plugins, relative to `cwd` */\n configFile?: string;\n packageJson: {\n dependencies?: Record<string, string>;\n devDependencies?: Record<string, string>;\n };\n}\n\nexport function isSupportedFramework(v: string): v is Framework {\n return v in frameworks;\n}\n\nexport async function loadProject(\n config: LoadedConfig,\n cwd = process.cwd(),\n framework: Framework = assertFramework(config.framework),\n): Promise<Project> {\n const info = frameworks[framework];\n const [packageJson, pm] = await Promise.all([\n fs\n .readFile(path.join(cwd, 'package.json'), 'utf-8')\n .then((content) => JSON.parse(content))\n .catch(() => ({})),\n detectPackageManager({ cwd }),\n ]);\n let configFile: string | undefined;\n let configContent: string | undefined;\n for (const file of info.configFiles) {\n if (await exists(path.join(cwd, file))) {\n configFile = file;\n configContent = await fs.readFile(path.join(cwd, file), 'utf-8');\n break;\n }\n }\n\n let isStatic = false;\n if (framework === 'react-router') {\n const content = await fs\n .readFile(path.join(cwd, 'react-router.config.ts'), 'utf-8')\n .catch(() => '');\n isStatic = /ssr:\\s*false/.test(content);\n } else if (info.isStatic) {\n isStatic = info.isStatic(configContent ?? '');\n }\n\n const read = (file: string) => fs.readFile(path.join(cwd, file), 'utf-8').catch(() => null);\n const i18nConfig = await read(path.join(config.baseDir, 'lib/i18n.ts'));\n\n return {\n cwd,\n framework,\n info,\n static: isStatic,\n i18n:\n i18nConfig === null\n ? null\n : { optionalLocale: /hideLocale:\\s*['\"]default-locale['\"]/.test(i18nConfig) },\n packageManager: pm?.name ?? 'npm',\n baseDir: config.baseDir,\n config,\n configFile,\n packageJson,\n source: await detectSource(cwd, config.baseDir, read, configFile, configContent),\n };\n}\n\nasync function detectSource(\n cwd: string,\n baseDir: string,\n read: (file: string) => Promise<string | null>,\n configFile?: string,\n configContent?: string,\n): Promise<SourceInfo> {\n const sourceFile = path.join(baseDir, 'lib/source.ts');\n const source = (await read(sourceFile)) ?? '';\n let collections: string | null = null;\n let content = source;\n const astro = source.includes('@fumadocs/astro/source');\n if (source.includes('defineDocs(')) {\n collections = sourceFile;\n } else if (astro) {\n // the options of `fumadocs()` in Astro config\n collections = configFile ?? null;\n content = configContent ?? '';\n } else if (await exists(path.join(cwd, 'source.config.ts'))) {\n collections = 'source.config.ts';\n content = (await read(collections)) ?? '';\n }\n\n let baseUrl = /baseUrl:\\s*['\"]([^'\"]*)['\"]/.exec(source)?.[1];\n // e.g. `baseUrl: docsRoute` from `lib/shared.ts`\n const ref = /baseUrl:\\s*(\\w+)/.exec(source)?.[1];\n if (!baseUrl && ref) {\n const shared = (await read(path.join(baseDir, 'lib/shared.ts'))) ?? '';\n baseUrl = new RegExp(`${ref}\\\\s*=\\\\s*['\"]([^'\"]*)['\"]`).exec(shared)?.[1];\n }\n\n return {\n loader: /export const source\\b/.test(source),\n dynamic: /export (async )?function getSource\\b/.test(source),\n collections,\n // pages of Astro collections load their data on demand\n async: astro || /async:\\s*true/.test(content),\n baseUrl: baseUrl ?? '/docs',\n dir: /dir:\\s*['\"]([^'\"]*)['\"]/.exec(content)?.[1] ?? 'content/docs',\n };\n}\n\nfunction assertFramework(framework: string): Framework {\n if (isSupportedFramework(framework)) return framework;\n throw new Error(\n `Unsupported framework \"${framework}\", supported: ${Object.keys(frameworks).join(', ')}.`,\n );\n}\n\nexport { detectFramework };\n"],"mappings":"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"}
|
|
@@ -1,10 +1,8 @@
|
|
|
1
|
-
import { r as LoadedConfig } from "../config-
|
|
1
|
+
import { r as LoadedConfig } from "../config-Z-4b_Y_C.js";
|
|
2
2
|
import { InteractiveInstaller } from "@fuma-cli/interactive";
|
|
3
3
|
import { IOInterface } from "fuma-cli/registry/installer";
|
|
4
4
|
import { RegistryConnector } from "fuma-cli/registry/connector";
|
|
5
|
-
//#region src/registry/installer.d.ts
|
|
6
5
|
export declare class FumadocsComponentInstaller extends InteractiveInstaller {
|
|
7
6
|
constructor(connector: RegistryConnector, config: LoadedConfig, cwd?: string, io?: Partial<IOInterface>);
|
|
8
7
|
}
|
|
9
|
-
//#endregion
|
|
10
8
|
//# sourceMappingURL=installer.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"installer.d.ts","names":[],"sources":["../../src/registry/installer.ts"],"mappings":"
|
|
1
|
+
{"version":3,"file":"installer.d.ts","names":[],"sources":["../../src/registry/installer.ts"],"mappings":";;;;qBAMa,mCAAmC;EAC9C,YACE,WAAW,mBACX,QAAQ,cACR,cACA,KAAK,QAAQ"}
|
|
@@ -1,2 +1 @@
|
|
|
1
|
-
|
|
2
|
-
export { FumadocsComponentInstaller };
|
|
1
|
+
export { t as FumadocsComponentInstaller } from "../installer-CEOV0fBv.js";
|
|
@@ -1,16 +1,20 @@
|
|
|
1
|
-
import { b as findJsxElement, m as addJsxAttribute, o as filterReactRouterPrerenderArray, p as addImport, r as addTanstackPrerender, s as filterReactRouterRoute } from "./codemod-
|
|
2
|
-
import {
|
|
3
|
-
import { a as
|
|
1
|
+
import { b as findJsxElement, m as addJsxAttribute, o as filterReactRouterPrerenderArray, p as addImport, r as addTanstackPrerender, s as filterReactRouterRoute } from "./codemod-Wc9nRqrw.js";
|
|
2
|
+
import { i as formatRoute, o as routeModule, t as docs } from "./docs---dy-BSm.js";
|
|
3
|
+
import { a as registerReactRouterRoutes, i as posix, l as sourceRef, r as findSource, s as scripts } from "./utils-Btt-BJ53.js";
|
|
4
4
|
import fs from "node:fs/promises";
|
|
5
5
|
import path from "node:path";
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
6
|
+
// src/features/search.ts
|
|
7
|
+
function publicEnv(framework) {
|
|
8
|
+
if (framework === "next") return {
|
|
9
|
+
prefix: "NEXT_PUBLIC_",
|
|
10
|
+
read: "process.env.NEXT_PUBLIC_"
|
|
11
|
+
};
|
|
12
|
+
const prefix = framework === "astro" ? "PUBLIC_" : "VITE_";
|
|
13
|
+
return {
|
|
14
|
+
prefix,
|
|
15
|
+
read: `import.meta.env.${prefix}`
|
|
16
|
+
};
|
|
17
|
+
}
|
|
14
18
|
const dialogImports = `import {
|
|
15
19
|
SearchDialog,
|
|
16
20
|
SearchDialogClose,
|
|
@@ -67,11 +71,13 @@ async function getDocuments() {
|
|
|
67
71
|
}
|
|
68
72
|
return documents;
|
|
69
73
|
}`;
|
|
70
|
-
/** `scripts/sync-content.ts`, syncing the search indexes pre-rendered to `output
|
|
74
|
+
/** `scripts/sync-content.ts`, syncing the search indexes pre-rendered to `output`, the first match of a glob pattern (Next.js) */
|
|
71
75
|
const syncScript = (output, imports, body) => `import * as fs from 'node:fs/promises';
|
|
72
76
|
${imports}
|
|
73
77
|
|
|
74
|
-
|
|
78
|
+
${output.includes("*") ? `// Next.js writes it under \`route-cache\` when built with an adapter, like on Vercel
|
|
79
|
+
const [file] = await Array.fromAsync(fs.glob('${output}'));
|
|
80
|
+
const records = JSON.parse(await fs.readFile(file, 'utf-8')) as DocumentRecord[];` : `const records = JSON.parse(await fs.readFile('${output}', 'utf-8')) as DocumentRecord[];`}
|
|
75
81
|
|
|
76
82
|
${body}
|
|
77
83
|
console.log(\`search updated: \${records.length} records\`);
|
|
@@ -285,26 +291,26 @@ import Mixedbread from '@mixedbread/sdk';`, `await sync(new Mixedbread(), {
|
|
|
285
291
|
});
|
|
286
292
|
`),
|
|
287
293
|
searchRoute: (() => {
|
|
288
|
-
const server = `import { createMixedbreadSearchAPI } from 'fumadocs-core/search/mixedbread';
|
|
289
|
-
import Mixedbread from '@mixedbread/sdk';
|
|
294
|
+
const server = (astro = false) => `import { createMixedbreadSearchAPI } from 'fumadocs-core/search/mixedbread';
|
|
295
|
+
import Mixedbread from '@mixedbread/sdk';${astro ? "\nimport { getSecret } from 'astro:env/server';" : ""}
|
|
290
296
|
|
|
291
297
|
const server = createMixedbreadSearchAPI({
|
|
292
|
-
client: new Mixedbread(),
|
|
293
|
-
storeIdentifier: process.env.MIXEDBREAD_STORE_ID!,
|
|
298
|
+
client: new Mixedbread(${astro ? "{ apiKey: getSecret('MXBAI_API_KEY') }" : ""}),
|
|
299
|
+
storeIdentifier: ${astro ? "getSecret('MIXEDBREAD_STORE_ID')" : "process.env.MIXEDBREAD_STORE_ID"}!,
|
|
294
300
|
});
|
|
295
301
|
`;
|
|
296
302
|
return {
|
|
297
|
-
next: `${server}
|
|
303
|
+
next: `${server()}
|
|
298
304
|
export const { GET } = server;
|
|
299
305
|
`,
|
|
300
306
|
"react-router": `import type { Route } from './+types/search';
|
|
301
|
-
${server}
|
|
307
|
+
${server()}
|
|
302
308
|
export async function loader({ request }: Route.LoaderArgs) {
|
|
303
309
|
return server.GET(request);
|
|
304
310
|
}
|
|
305
311
|
`,
|
|
306
312
|
"tanstack-start": `import { createFileRoute } from '@tanstack/react-router';
|
|
307
|
-
${server}
|
|
313
|
+
${server()}
|
|
308
314
|
export const Route = createFileRoute('/api/search')({
|
|
309
315
|
server: {
|
|
310
316
|
handlers: {
|
|
@@ -313,8 +319,12 @@ export const Route = createFileRoute('/api/search')({
|
|
|
313
319
|
},
|
|
314
320
|
});
|
|
315
321
|
`,
|
|
316
|
-
waku: `${server}
|
|
322
|
+
waku: `${server()}
|
|
317
323
|
export const { GET } = server;
|
|
324
|
+
`,
|
|
325
|
+
astro: `import type { APIRoute } from 'astro';
|
|
326
|
+
${server(true)}
|
|
327
|
+
export const GET: APIRoute = ({ request }) => server.GET(request);
|
|
318
328
|
`
|
|
319
329
|
};
|
|
320
330
|
})()
|
|
@@ -329,14 +339,15 @@ const staticRoute = (framework, head, documents) => routeModule(framework, stati
|
|
|
329
339
|
static: true
|
|
330
340
|
});
|
|
331
341
|
/** where `static.json` is pre-rendered on build, `config` is the Vite config of TanStack Start */
|
|
332
|
-
function staticOutput(framework, config = "") {
|
|
342
|
+
function staticOutput(framework, config = "", isStatic = true) {
|
|
333
343
|
switch (framework) {
|
|
334
|
-
case "next": return ".next/server/app/static.json.body";
|
|
344
|
+
case "next": return ".next/server/{app,route-cache/*/*/$}/static.json.body";
|
|
335
345
|
case "react-router": return "build/client/static.json";
|
|
336
346
|
case "waku": return "dist/public/static.json";
|
|
337
347
|
case "tanstack-start":
|
|
338
348
|
if (/preset:\s*['"]vercel['"]/.test(config)) return ".vercel/output/static/static.json";
|
|
339
349
|
return /\bnitro\(/.test(config) ? ".output/public/static.json" : "dist/client/static.json";
|
|
350
|
+
case "astro": return isStatic ? "dist/static.json" : "dist/client/static.json";
|
|
340
351
|
}
|
|
341
352
|
}
|
|
342
353
|
/** runs the sync script after build */
|
|
@@ -344,7 +355,7 @@ const syncCommand = "node --env-file-if-exists=.env.local scripts/sync-content.t
|
|
|
344
355
|
/** the files written by a provider, relative to the project, `output` is where `static.json` is pre-rendered */
|
|
345
356
|
function templates(name, framework, { baseDir, source }, output) {
|
|
346
357
|
const provider = providers[name];
|
|
347
|
-
const env = publicEnv(framework
|
|
358
|
+
const env = publicEnv(framework);
|
|
348
359
|
const files = [[path.posix.join(baseDir, "components/search.tsx"), provider.dialog(env)]];
|
|
349
360
|
if (provider.exportIndexes) {
|
|
350
361
|
const { head, documents } = provider.exportIndexes(sourceRef(source.dynamic), source.async);
|
|
@@ -369,11 +380,10 @@ const search = {
|
|
|
369
380
|
} },
|
|
370
381
|
async apply(ctx, { provider: name }) {
|
|
371
382
|
const { project } = ctx;
|
|
372
|
-
const { baseDir } = project;
|
|
373
|
-
const framework = reactFramework(project);
|
|
383
|
+
const { baseDir, framework } = project;
|
|
374
384
|
const provider = providers[name];
|
|
375
385
|
if (project.static && !provider.static) throw new Error(`${provider.label} requires a server at runtime`);
|
|
376
|
-
const output = staticOutput(framework, framework === "tanstack-start" && project.configFile ? await fs.readFile(path.join(project.cwd, project.configFile), "utf-8") : "");
|
|
386
|
+
const output = staticOutput(framework, framework === "tanstack-start" && project.configFile ? await fs.readFile(path.join(project.cwd, project.configFile), "utf-8") : "", project.static);
|
|
377
387
|
ctx.addDependencies(provider.dependencies);
|
|
378
388
|
for (const [file, content] of templates(name, framework, project, output)) await ctx.write(file, content);
|
|
379
389
|
const searchRoute = await findSource(project.cwd, path.join(baseDir, project.info.routesDir), "createFromSource(", (file) => /(^|[\\/])search(\.ts|[\\/]route\.ts)$/.test(file));
|
|
@@ -401,7 +411,7 @@ const search = {
|
|
|
401
411
|
if (build.includes("sync-content")) return;
|
|
402
412
|
scripts(data, { build: build ? `${build} && ${syncCommand}` : syncCommand });
|
|
403
413
|
});
|
|
404
|
-
const env = publicEnv(framework
|
|
414
|
+
const env = publicEnv(framework);
|
|
405
415
|
for (const key of provider.publicEnv) ctx.env(env.prefix + key, "");
|
|
406
416
|
for (const key of provider.privateEnv) ctx.env(key, "");
|
|
407
417
|
ctx.note(`Fill the ${provider.label} keys in \`.env.local\`, the search index is synced after each build.\nSee https://fumadocs.dev/docs/headless/search/${name}.`);
|
|
@@ -420,7 +430,6 @@ async function removeDefaultSearch(ctx, file) {
|
|
|
420
430
|
filterReactRouterPrerenderArray(source, "excluded", (item) => item !== "/api/search");
|
|
421
431
|
});
|
|
422
432
|
}
|
|
423
|
-
//#endregion
|
|
424
433
|
export { syncCommand as a, staticRoute as i, staticJson as n, syncScript as o, staticOutput as r, templates as s, search as t };
|
|
425
434
|
|
|
426
|
-
//# sourceMappingURL=search-
|
|
435
|
+
//# sourceMappingURL=search-B44II4bM.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"search-B44II4bM.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 { Framework, 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 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\nfunction publicEnv(framework: Framework): Env {\n if (framework === 'next') return { prefix: 'NEXT_PUBLIC_', read: 'process.env.NEXT_PUBLIC_' };\n const prefix = framework === 'astro' ? 'PUBLIC_' : 'VITE_';\n return { prefix, read: `import.meta.env.${prefix}` };\n}\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`, the first match of a glob pattern (Next.js) */\nexport const syncScript = (\n output: string,\n imports: string,\n body: string,\n) => `import * as fs from 'node:fs/promises';\n${imports}\n\n${\n output.includes('*')\n ? `// Next.js writes it under \\`route-cache\\` when built with an adapter, like on Vercel\nconst [file] = await Array.fromAsync(fs.glob('${output}'));\nconst records = JSON.parse(await fs.readFile(file, 'utf-8')) as DocumentRecord[];`\n : `const records = JSON.parse(await fs.readFile('${output}', 'utf-8')) as DocumentRecord[];`\n}\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<Framework, 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 // Astro inlines `import.meta.env` at build time, `getSecret()` reads secrets at runtime\n const server = (\n astro = false,\n ) => `import { createMixedbreadSearchAPI } from 'fumadocs-core/search/mixedbread';\nimport Mixedbread from '@mixedbread/sdk';${astro ? \"\\nimport { getSecret } from 'astro:env/server';\" : ''}\n\nconst server = createMixedbreadSearchAPI({\n client: new Mixedbread(${astro ? \"{ apiKey: getSecret('MXBAI_API_KEY') }\" : ''}),\n storeIdentifier: ${astro ? \"getSecret('MIXEDBREAD_STORE_ID')\" : '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 astro: `import type { APIRoute } from 'astro';\n${server(true)}\nexport const GET: APIRoute = ({ request }) => server.GET(request);\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: Framework) =>\n formatRoute({ segments: ['static.json'] }, framework, null);\n\nexport const staticRoute = (framework: Framework, 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: Framework, config = '', isStatic = true): string {\n switch (framework) {\n case 'next':\n return '.next/server/{app,route-cache/*/*/$}/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 case 'astro':\n // with routes rendered on demand, prerendered files are in the client output\n return isStatic ? 'dist/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: Framework,\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);\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, framework } = 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, project.static);\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);\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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"}
|