@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 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"project-DwRVTm4p.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/** frameworks supported by features that generate routes */\nexport type ReactFramework = Exclude<Framework, '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'],\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 defining the Fumadocs MDX collections, relative to cwd: `lib/source.ts` (macro) or `source.config.ts`; `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 (configContent && 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),\n };\n}\n\nasync function detectSource(\n cwd: string,\n baseDir: string,\n read: (file: string) => Promise<string | null>,\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 if (source.includes('defineDocs(')) {\n collections = sourceFile;\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 async: /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":";;;;;AAyBA,MAAa,aAA+C;CAC1D,MAAM;EACJ,UAAU;EACV,UAAU;EACV,WAAW;EACX,aAAa;GAAC;GAAmB;GAAkB;EAAgB;EACnE,WAAW,WAAW,2BAA2B,KAAK,MAAM;CAC9D;CACA,gBAAgB;EACd,UAAU;EACV,UAAU;EACV,WAAW;EACX,aAAa,CAAC,gBAAgB;CAChC;CACA,kBAAkB;EAChB,UAAU;EACV,UAAU;EACV,WAAW;EACX,aAAa,CAAC,gBAAgB;EAC9B,WAAW,WAAW,cAAc,KAAK,MAAM;CACjD;CACA,MAAM;EACJ,UAAU;EACV,UAAU;EACV,WAAW;EACX,aAAa,CAAC,gBAAgB;CAChC;CACA,OAAO;EACL,UAAU;EACV,UAAU;EACV,WAAW;EACX,aAAa,CAAC,oBAAoB,iBAAiB;CACrD;AACF;AA4CA,SAAgB,qBAAqB,GAA2B;CAC9D,OAAO,KAAK;AACd;AAEA,eAAsB,YACpB,QACA,MAAM,QAAQ,IAAI,GAClB,YAAuB,gBAAgB,OAAO,SAAS,GACrC;CAClB,MAAM,OAAO,WAAW;CACxB,MAAM,CAAC,aAAa,MAAM,MAAM,QAAQ,IAAI,CAC1C,GACG,SAAS,KAAK,KAAK,KAAK,cAAc,GAAG,OAAO,CAAC,CACjD,MAAM,YAAY,KAAK,MAAM,OAAO,CAAC,CAAC,CACtC,aAAa,CAAC,EAAE,GACnB,qBAAqB,EAAE,IAAI,CAAC,CAC9B,CAAC;CACD,IAAI;CACJ,IAAI;CACJ,KAAK,MAAM,QAAQ,KAAK,aACtB,IAAI,MAAM,OAAO,KAAK,KAAK,KAAK,IAAI,CAAC,GAAG;EACtC,aAAa;EACb,gBAAgB,MAAM,GAAG,SAAS,KAAK,KAAK,KAAK,IAAI,GAAG,OAAO;EAC/D;CACF;CAGF,IAAI,WAAW;CACf,IAAI,cAAc,gBAAgB;EAChC,MAAM,UAAU,MAAM,GACnB,SAAS,KAAK,KAAK,KAAK,wBAAwB,GAAG,OAAO,CAAC,CAC3D,YAAY,EAAE;EACjB,WAAW,eAAe,KAAK,OAAO;CACxC,OAAO,IAAI,iBAAiB,KAAK,UAC/B,WAAW,KAAK,SAAS,aAAa;CAGxC,MAAM,QAAQ,SAAiB,GAAG,SAAS,KAAK,KAAK,KAAK,IAAI,GAAG,OAAO,CAAC,CAAC,YAAY,IAAI;CAC1F,MAAM,aAAa,MAAM,KAAK,KAAK,KAAK,OAAO,SAAS,aAAa,CAAC;CAEtE,OAAO;EACL;EACA;EACA;EACA,QAAQ;EACR,MACE,eAAe,OACX,OACA,EAAE,gBAAgB,uCAAuC,KAAK,UAAU,EAAE;EAChF,gBAAgB,IAAI,QAAQ;EAC5B,SAAS,OAAO;EAChB;EACA;EACA;EACA,QAAQ,MAAM,aAAa,KAAK,OAAO,SAAS,IAAI;CACtD;AACF;AAEA,eAAe,aACb,KACA,SACA,MACqB;CACrB,MAAM,aAAa,KAAK,KAAK,SAAS,eAAe;CACrD,MAAM,SAAU,MAAM,KAAK,UAAU,KAAM;CAC3C,IAAI,cAA6B;CACjC,IAAI,UAAU;CACd,IAAI,OAAO,SAAS,aAAa,GAC/B,cAAc;MACT,IAAI,MAAM,OAAO,KAAK,KAAK,KAAK,kBAAkB,CAAC,GAAG;EAC3D,cAAc;EACd,UAAW,MAAM,KAAK,WAAW,KAAM;CACzC;CAEA,IAAI,UAAU,8BAA8B,KAAK,MAAM,CAAC,GAAG;CAE3D,MAAM,MAAM,mBAAmB,KAAK,MAAM,CAAC,GAAG;CAC9C,IAAI,CAAC,WAAW,KAAK;EACnB,MAAM,SAAU,MAAM,KAAK,KAAK,KAAK,SAAS,eAAe,CAAC,KAAM;EACpE,UAAU,IAAI,OAAO,GAAG,IAAI,0BAA0B,CAAC,CAAC,KAAK,MAAM,CAAC,GAAG;CACzE;CAEA,OAAO;EACL,QAAQ,wBAAwB,KAAK,MAAM;EAC3C,SAAS,uCAAuC,KAAK,MAAM;EAC3D;EACA,OAAO,gBAAgB,KAAK,OAAO;EACnC,SAAS,WAAW;EACpB,KAAK,0BAA0B,KAAK,OAAO,CAAC,GAAG,MAAM;CACvD;AACF;AAEA,SAAS,gBAAgB,WAA8B;CACrD,IAAI,qBAAqB,SAAS,GAAG,OAAO;CAC5C,MAAM,IAAI,MACR,0BAA0B,UAAU,gBAAgB,OAAO,KAAK,UAAU,CAAC,CAAC,KAAK,IAAI,EAAE,EACzF;AACF"}
|
|
@@ -1 +0,0 @@
|
|
|
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 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"utils-CxJ7kw39.d.ts","names":[],"sources":["../src/project/route.ts","../src/features/utils.ts"],"mappings":";;;UAoBiB;;EAEf;;EAEA;;EAEA;;;;;iBClBoB,WACpB,aACA,aACA,gBACA,SAAQ,2BAAqC;iBAiB/B,QAAQ,MAAM,aAAa,SAAS;cAIvC,YAAS,SAAa;iBAGnB,eAAe,SAAS,UAAU;;cAMrC,QAAK;;iBAGI,UAAU,KAAK,gBAAgB,cAAc,cAAc,eAAY;;iBAQvE,WAAW,KAAK,gBAAgB,cAAc,eAAY;;iBAe1D,YAAY,KAAK,gBAAgB,cAAc,gBAAa;;iBAY5D,0BACpB,KAAK,gBACL,SAAS,6BACR;;cAyBU,mBAAgB,SAAa;;UAQzB;;EAEf;;EAEA;;EAEA;;iBAGc,UAAU,mBAAmB"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"utils-DbMe7h6c.js","names":[],"sources":["../src/features/utils.ts"],"sourcesContent":["import fs from 'node:fs/promises';\nimport path from 'node:path';\nimport type { Project, ReactFramework } from '@/project';\nimport type { FormattedRoute } from '@/project/route';\nimport type { FeatureContext, PackageJson } from '@/features';\nimport { addImport, addReactRouterRoute } from '@/codemod';\n\n/** the shallowest `.ts`/`.tsx` file under `dir` whose content includes `needle`, relative to `cwd` */\nexport async function findSource(\n cwd: string,\n dir: string,\n needle: string,\n match: (file: string) => boolean = () => true,\n) {\n const entries = await fs\n .readdir(path.join(cwd, dir), { recursive: true, withFileTypes: true })\n .catch(() => []);\n const depth = (dir: string) => dir.split(path.sep).length;\n entries.sort((a, b) => depth(a.parentPath) - depth(b.parentPath));\n\n for (const entry of entries) {\n if (!entry.isFile() || !/\\.tsx?$/.test(entry.name) || entry.parentPath.includes('node_modules'))\n continue;\n const file = path.join(entry.parentPath, entry.name);\n if (!match(file)) continue;\n if ((await fs.readFile(file, 'utf-8')).includes(needle)) return path.relative(cwd, file);\n }\n}\n\nexport function scripts(json: PackageJson, scripts: Record<string, string>) {\n Object.assign((json.scripts ??= {}), scripts);\n}\n\nexport const reactOnly = (project: Project) =>\n project.framework !== 'astro' || 'Astro is not supported by this feature';\n\nexport function reactFramework(project: Project): ReactFramework {\n if (project.framework === 'astro') throw new Error('Astro is not supported by this feature');\n return project.framework;\n}\n\n/** POSIX path for generated code and route configs */\nexport const posix = (file: string) => file.split(path.sep).join('/');\n\n/** add an export to a source file, unless a declaration with the same name exists */\nexport async function addExport(ctx: FeatureContext, file: string, name: string, code: string) {\n const current = await fs.readFile(path.join(ctx.project.cwd, file), 'utf-8').catch(() => '');\n if (new RegExp(`export (async )?(const|function) ${name}\\\\b`).test(current)) return false;\n await ctx.append(file, [code]);\n return true;\n}\n\n/** add a page URL helper to `lib/shared.ts`, where the route constants live */\nexport async function addPageUrl(ctx: FeatureContext, name: string, code: string) {\n const file = path.join(ctx.project.baseDir, 'lib/shared.ts');\n if (!(await addExport(ctx, file, name, code))) return false;\n\n await ctx.source(file, (source) =>\n addImport(source, { from: 'fumadocs-core/source', named: ['createGetUrl'] }),\n );\n // a second pass, so the import lands after the one above instead of before it\n if (ctx.project.i18n) {\n await ctx.source(file, (source) => addImport(source, { from: './i18n', named: ['i18n'] }));\n }\n return true;\n}\n\n/** the value of a route constant in `lib/shared.ts`, defined with `value` when missing */\nexport async function sharedRoute(ctx: FeatureContext, name: string, value: string) {\n const file = path.join(ctx.project.baseDir, 'lib/shared.ts');\n const current = await fs.readFile(path.join(ctx.project.cwd, file), 'utf-8').catch(() => '');\n const existing = new RegExp(`export const ${name}\\\\s*=\\\\s*['\"]([^'\"]*)['\"]`).exec(current)?.[1];\n if (existing !== undefined) return existing;\n // keep a blank line after the declarations already in the file\n const separator = current.trimEnd().endsWith(';') || current.length === 0 ? '' : '\\n';\n await ctx.append(file, [`${separator}export const ${name} = '${value}';`]);\n return value;\n}\n\n/** add routes to `routes.ts`, or tell the user when the config isn't an array (e.g. file-based routes) */\nexport async function registerReactRouterRoutes(\n ctx: FeatureContext,\n routes: (FormattedRoute | string)[],\n): Promise<boolean> {\n const { baseDir } = ctx.project;\n const entries = routes.map((item) =>\n typeof item === 'string' ? item : { path: item.path, entry: item.file },\n );\n let added = false;\n const edited = await ctx.source(path.join(baseDir, 'routes.ts'), (file) => {\n const first = entries[0];\n if (file.code.includes(typeof first === 'string' ? first : first.entry)) {\n added = true;\n return;\n }\n added = addReactRouterRoute(file, entries);\n });\n\n if (!edited || !added) {\n const list = entries.map((item) =>\n typeof item === 'string' ? item : `route('${item.path}', '${item.entry}')`,\n );\n ctx.note(`Register the routes in your route config:\\n ${list.join('\\n ')}`);\n }\n return added;\n}\n\n/** features reading page Markdown need Fumadocs MDX collections, or a runtime source exposing `page.data.content` */\nexport const requiresMarkdown = (project: Project) =>\n project.framework === 'astro'\n ? 'Astro is not supported by this feature'\n : project.source.collections !== null ||\n project.source.dynamic ||\n 'requires Fumadocs MDX or a runtime content source';\n\n/** how generated code refers to the loader of `lib/source.ts` */\nexport interface SourceRef {\n /** resolved on demand, `lib/source.ts` exports `getSource()` instead of `source` */\n dynamic: boolean;\n /** passed to Fumadocs APIs, which accept a loader or a function resolving one */\n ref: string;\n /** reads the loader inside an async function */\n resolved: string;\n}\n\nexport function sourceRef(dynamic: boolean): SourceRef {\n return dynamic\n ? { dynamic: true, ref: 'getSource', resolved: '(await getSource())' }\n : { dynamic: false, ref: 'source', resolved: 'source' };\n}\n"],"mappings":"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"}
|
|
@@ -1 +0,0 @@
|
|
|
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"}
|