@fumadocs/cli 1.6.0 → 1.6.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (72) hide show
  1. package/dist/{ai-BVuNjYXU.js → ai-C19ZOP53.js} +2 -2
  2. package/dist/{ai-BVuNjYXU.js.map → ai-C19ZOP53.js.map} +1 -1
  3. package/dist/all-BBf7I8hG.js +27 -0
  4. package/dist/{all-aUHxtoXL.js.map → all-BBf7I8hG.js.map} +1 -1
  5. package/dist/{config-CT2jj39D.d.ts → config-Bfg4yG8S.d.ts} +3 -1
  6. package/dist/config-Bfg4yG8S.d.ts.map +1 -0
  7. package/dist/{config-D6uv73fs.js → config-Bw0kmF01.js} +6 -3
  8. package/dist/config-Bw0kmF01.js.map +1 -0
  9. package/dist/config.d.ts +1 -1
  10. package/dist/config.js +1 -1
  11. package/dist/{docs-DeIEl_Kt.js → docs--WqEHTyl.js} +2 -2
  12. package/dist/{docs-DeIEl_Kt.js.map → docs--WqEHTyl.js.map} +1 -1
  13. package/dist/{epub-CnQFJh09.js → epub-jc_2B886.js} +2 -2
  14. package/dist/{epub-CnQFJh09.js.map → epub-jc_2B886.js.map} +1 -1
  15. package/dist/features/ai.d.ts +1 -1
  16. package/dist/features/ai.js +1 -1
  17. package/dist/features/all.d.ts +1 -1
  18. package/dist/features/all.js +1 -1
  19. package/dist/features/docs/index.d.ts +2 -2
  20. package/dist/features/docs/index.js +1 -1
  21. package/dist/features/epub.d.ts +1 -1
  22. package/dist/features/epub.js +1 -1
  23. package/dist/features/feedback.d.ts +1 -1
  24. package/dist/features/feedback.js +1 -1
  25. package/dist/features/index.d.ts +1 -1
  26. package/dist/features/index.js +1 -1
  27. package/dist/features/lint.d.ts +1 -1
  28. package/dist/features/llms.d.ts +3 -3
  29. package/dist/features/llms.js +1 -1
  30. package/dist/features/mcp.d.ts +3 -3
  31. package/dist/features/mcp.js +1 -1
  32. package/dist/features/og.d.ts +1 -1
  33. package/dist/features/og.js +1 -1
  34. package/dist/features/search.d.ts +2 -2
  35. package/dist/features/search.js +1 -1
  36. package/dist/features/utils.d.ts +1 -1
  37. package/dist/features/webmcp.d.ts +1 -1
  38. package/dist/features/webmcp.js +1 -1
  39. package/dist/{features-C1kf2s8j.js → features-CXFCgOWD.js} +3 -3
  40. package/dist/{features-C1kf2s8j.js.map → features-CXFCgOWD.js.map} +1 -1
  41. package/dist/{feedback-COmWtc5C.js → feedback-Brj092vC.js} +2 -2
  42. package/dist/{feedback-COmWtc5C.js.map → feedback-Brj092vC.js.map} +1 -1
  43. package/dist/{index-CR9T40ox.d.ts → index-Cs7hw2un.d.ts} +2 -2
  44. package/dist/{index-CR9T40ox.d.ts.map → index-Cs7hw2un.d.ts.map} +1 -1
  45. package/dist/{index-BCVGQxn9.d.ts → index-DI66DoV8.d.ts} +2 -2
  46. package/dist/{index-BCVGQxn9.d.ts.map → index-DI66DoV8.d.ts.map} +1 -1
  47. package/dist/index.js +43 -36
  48. package/dist/index.js.map +1 -1
  49. package/dist/{installer-D0Obb2Md.js → installer-CzhVfs7i.js} +67 -4
  50. package/dist/installer-CzhVfs7i.js.map +1 -0
  51. package/dist/{llms-CONd8Vnn.js → llms-Br0RJXXg.js} +2 -2
  52. package/dist/{llms-CONd8Vnn.js.map → llms-Br0RJXXg.js.map} +1 -1
  53. package/dist/{mcp-BT8BLZZ-.js → mcp-Ce_W93we.js} +3 -3
  54. package/dist/{mcp-BT8BLZZ-.js.map → mcp-Ce_W93we.js.map} +1 -1
  55. package/dist/{og-DRDOyQ_t.js → og-LFlX1Bl4.js} +3 -3
  56. package/dist/{og-DRDOyQ_t.js.map → og-LFlX1Bl4.js.map} +1 -1
  57. package/dist/project/index.d.ts +1 -1
  58. package/dist/registry/installer.d.ts +1 -1
  59. package/dist/registry/installer.d.ts.map +1 -1
  60. package/dist/registry/installer.js +1 -1
  61. package/dist/schema.json +1 -1
  62. package/dist/{search-iphnSVVY.js → search-WfSdiGx-.js} +2 -2
  63. package/dist/{search-iphnSVVY.js.map → search-WfSdiGx-.js.map} +1 -1
  64. package/dist/{utils-B1Xy4l_a.d.ts → utils-CxJ7kw39.d.ts} +3 -3
  65. package/dist/{utils-B1Xy4l_a.d.ts.map → utils-CxJ7kw39.d.ts.map} +1 -1
  66. package/dist/{webmcp-C2PM_9md.js → webmcp-DyTLrsXL.js} +2 -2
  67. package/dist/{webmcp-C2PM_9md.js.map → webmcp-DyTLrsXL.js.map} +1 -1
  68. package/package.json +9 -8
  69. package/dist/all-aUHxtoXL.js +0 -27
  70. package/dist/config-CT2jj39D.d.ts.map +0 -1
  71. package/dist/config-D6uv73fs.js.map +0 -1
  72. package/dist/installer-D0Obb2Md.js.map +0 -1
@@ -1,7 +1,15 @@
1
+ import { existsSync } from "node:fs";
2
+ import path from "node:path";
1
3
  import { detectPackageManager } from "fuma-cli/detect";
2
4
  import picocolors from "picocolors";
3
5
  import { box, confirm, isCancel, log, outro, spinner } from "@clack/prompts";
4
6
  import { ComponentInstaller } from "fuma-cli/registry/installer";
7
+ //#region src/commands/shared.ts
8
+ const UIRegistries = {
9
+ "base-ui": "fumadocs/base-ui",
10
+ "radix-ui": "fumadocs/radix-ui"
11
+ };
12
+ //#endregion
5
13
  //#region src/registry/plugins/preserve.ts
6
14
  /**
7
15
  * keep references to `fumadocs-ui/layouts/*` components as original, unless the user is installing them directly.
@@ -49,6 +57,40 @@ function pluginPreserveLayouts() {
49
57
  };
50
58
  }
51
59
  //#endregion
60
+ //#region src/registry/plugins/shadcn.ts
61
+ /**
62
+ * `components/ui/*` follow the API of Shadcn UI, reuse the ones the project already has along with its `cn`.
63
+ */
64
+ function pluginReuseUI(config, cwd = process.cwd()) {
65
+ const { baseDir, aliases } = config;
66
+ const utils = aliases.utils && path.resolve(cwd, baseDir, aliases.utils);
67
+ return {
68
+ beforeInstall(comp) {
69
+ const files = comp.files.filter((file) => {
70
+ if (file.type === "lib") return !(utils && file.path === "utils/cn.ts");
71
+ if (file.type !== "ui") return true;
72
+ return !existsSync(path.resolve(cwd, baseDir, aliases.uiDir, path.basename(file.path)));
73
+ });
74
+ if (files.length === comp.files.length) return;
75
+ if (files.length === 0) return {
76
+ ...comp,
77
+ files,
78
+ dependencies: {},
79
+ devDependencies: {}
80
+ };
81
+ return {
82
+ ...comp,
83
+ files
84
+ };
85
+ },
86
+ transformImport(specifier, { filePath }) {
87
+ if (!utils || specifier !== "local:utils/cn.ts") return specifier;
88
+ const relative = path.relative(path.dirname(filePath), utils).replaceAll(path.sep, "/");
89
+ return relative.startsWith(".") ? relative : `./${relative}`;
90
+ }
91
+ };
92
+ }
93
+ //#endregion
52
94
  //#region src/utils/prompt.ts
53
95
  /**
54
96
  * `@clack/core` narrows to its own `unique symbol`, which can't exclude the wider `symbol` that
@@ -59,9 +101,30 @@ function isCancel$1(value) {
59
101
  }
60
102
  //#endregion
61
103
  //#region src/registry/installer.ts
104
+ const uiRegistries = new Set(Object.values(UIRegistries));
105
+ /** components are compiled against one UI library, fetch them from the sub-registry of the configured one */
106
+ var UILibraryConnector = class {
107
+ constructor(base, ui) {
108
+ this.base = base;
109
+ this.ui = ui;
110
+ this.id = base.id;
111
+ }
112
+ resolve(subRegistry) {
113
+ return subRegistry && uiRegistries.has(subRegistry) ? this.ui : subRegistry;
114
+ }
115
+ fetchRegistryInfo(subRegistry) {
116
+ return this.base.fetchRegistryInfo(this.resolve(subRegistry));
117
+ }
118
+ fetchComponent(name, subRegistry) {
119
+ return this.base.fetchComponent(name, this.resolve(subRegistry));
120
+ }
121
+ hasComponent(name, subRegistry) {
122
+ return this.base.hasComponent(name, this.resolve(subRegistry));
123
+ }
124
+ };
62
125
  var FumadocsComponentInstaller = class extends ComponentInstaller {
63
126
  constructor(connector, config, cwd, io) {
64
- super(connector, {
127
+ super(new UILibraryConnector(connector, UIRegistries[config.uiLibrary]), {
65
128
  cwd,
66
129
  framework: config.framework === "astro" ? "none" : config.framework,
67
130
  outDir: {
@@ -96,7 +159,7 @@ var FumadocsComponentInstaller = class extends ComponentInstaller {
96
159
  },
97
160
  ...io
98
161
  },
99
- plugins: [pluginPreserveLayouts()]
162
+ plugins: [pluginPreserveLayouts(), pluginReuseUI(config, cwd)]
100
163
  });
101
164
  this.interactive = null;
102
165
  }
@@ -136,6 +199,6 @@ var FumadocsComponentInstaller = class extends ComponentInstaller {
136
199
  }
137
200
  };
138
201
  //#endregion
139
- export { isCancel$1 as n, FumadocsComponentInstaller as t };
202
+ export { isCancel$1 as n, UIRegistries as r, FumadocsComponentInstaller as t };
140
203
 
141
- //# sourceMappingURL=installer-D0Obb2Md.js.map
204
+ //# sourceMappingURL=installer-CzhVfs7i.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"installer-CzhVfs7i.js","names":["isCancel","isCancelSymbol","isCancel"],"sources":["../src/commands/shared.ts","../src/registry/plugins/preserve.ts","../src/registry/plugins/shadcn.ts","../src/utils/prompt.ts","../src/registry/installer.ts"],"sourcesContent":["export const UIRegistries = {\n 'base-ui': 'fumadocs/base-ui',\n 'radix-ui': 'fumadocs/radix-ui',\n};\n","import type { InstallerPlugin } from 'fuma-cli/registry/installer';\n\n/**\n * keep references to `fumadocs-ui/layouts/*` components as original, unless the user is installing them directly.\n */\nexport function pluginPreserveLayouts(): InstallerPlugin {\n const layoutNames = [\n 'layouts/home',\n 'layouts/flux',\n 'layouts/glass',\n 'layouts/notebook',\n 'layouts/docs',\n 'layouts/shared',\n ];\n // original specifier -> new specifier\n const layoutComps: Record<string, string> = {\n 'local:<dir>/home/index.tsx': 'layouts/home',\n 'local:<dir>/shared/index.tsx': 'layouts/shared',\n 'local:<dir>/shared/client.tsx': 'layouts/shared',\n 'local:<dir>/notebook/index.tsx': 'layouts/notebook',\n 'local:<dir>/notebook/client.tsx': 'layouts/notebook',\n 'local:<dir>/notebook/page/index.tsx': 'layouts/notebook/page',\n 'local:<dir>/notebook/page/client.tsx': 'layouts/notebook/page',\n 'local:<dir>/docs/index.tsx': 'layouts/docs',\n 'local:<dir>/docs/client.tsx': 'layouts/docs',\n 'local:<dir>/docs/page/index.tsx': 'layouts/docs/page',\n 'local:<dir>/docs/page/client.tsx': 'layouts/docs/page',\n 'local:<dir>/flux/index.tsx': 'layouts/flux',\n 'local:<dir>/flux/page/index.tsx': 'layouts/flux/page',\n 'local:<dir>/flux/page/client.tsx': 'layouts/flux/page',\n 'local:<dir>/glass/index.tsx': 'layouts/glass',\n 'local:<dir>/glass/page/index.tsx': 'layouts/glass/page',\n };\n const layoutNameSet = new Set(layoutNames);\n\n return {\n beforeInstall(comp, { stack }) {\n const isDirectInstall = layoutNameSet.has(stack[0].name);\n if (isDirectInstall) return;\n\n return {\n ...comp,\n $subComponents: comp.$subComponents.filter((child) => !layoutNameSet.has(child.name)),\n };\n },\n transformImport(specifier, { stack }) {\n const isDirectInstall = layoutNameSet.has(stack[0].name);\n // skip if direct install or unrelated to layout component\n if (isDirectInstall || !(specifier in layoutComps)) return specifier;\n\n return `fumadocs-ui/${layoutComps[specifier]}`;\n },\n };\n}\n","import { existsSync } from 'node:fs';\nimport path from 'node:path';\nimport type { InstallerPlugin } from 'fuma-cli/registry/installer';\nimport type { LoadedConfig } from '@/config';\n\n/**\n * `components/ui/*` follow the API of Shadcn UI, reuse the ones the project already has along with its `cn`.\n */\nexport function pluginReuseUI(config: LoadedConfig, cwd = process.cwd()): InstallerPlugin {\n const { baseDir, aliases } = config;\n const utils = aliases.utils && path.resolve(cwd, baseDir, aliases.utils);\n\n return {\n beforeInstall(comp) {\n const files = comp.files.filter((file) => {\n if (file.type === 'lib') return !(utils && file.path === 'utils/cn.ts');\n if (file.type !== 'ui') return true;\n return !existsSync(path.resolve(cwd, baseDir, aliases.uiDir, path.basename(file.path)));\n });\n\n if (files.length === comp.files.length) return;\n // a fully reused component brings no dependencies\n if (files.length === 0) return { ...comp, files, dependencies: {}, devDependencies: {} };\n return { ...comp, files };\n },\n transformImport(specifier, { filePath }) {\n if (!utils || specifier !== 'local:utils/cn.ts') return specifier;\n const relative = path.relative(path.dirname(filePath), utils).replaceAll(path.sep, '/');\n return relative.startsWith('.') ? relative : `./${relative}`;\n },\n };\n}\n","import { isCancel as isCancelSymbol } from '@clack/prompts';\n\n/**\n * `@clack/core` narrows to its own `unique symbol`, which can't exclude the wider `symbol` that\n * `@clack/prompts` returns, so a prompt result never narrows without this.\n */\nexport function isCancel<T>(value: T | symbol): value is symbol {\n return isCancelSymbol(value);\n}\n","import { ComponentInstaller, type IOInterface } from 'fuma-cli/registry/installer';\nimport { pluginPreserveLayouts } from './plugins/preserve';\nimport { pluginReuseUI } from './plugins/shadcn';\nimport type { RegistryConnector } from 'fuma-cli/registry/connector';\nimport { UIRegistries } from '@/commands/shared';\nimport type { LoadedConfig } from '@/config';\nimport { box, confirm, log, outro, spinner, SpinnerResult } from '@clack/prompts';\nimport { isCancel } from '@/utils/prompt';\nimport picocolors from 'picocolors';\nimport { detectPackageManager } from 'fuma-cli/detect';\n\nconst uiRegistries = new Set(Object.values(UIRegistries));\n\n/** components are compiled against one UI library, fetch them from the sub-registry of the configured one */\nclass UILibraryConnector implements RegistryConnector {\n readonly id: string;\n\n constructor(\n private readonly base: RegistryConnector,\n private readonly ui: string,\n ) {\n this.id = base.id;\n }\n\n private resolve(subRegistry?: string) {\n return subRegistry && uiRegistries.has(subRegistry) ? this.ui : subRegistry;\n }\n\n fetchRegistryInfo(subRegistry?: string) {\n return this.base.fetchRegistryInfo(this.resolve(subRegistry));\n }\n\n fetchComponent(name: string, subRegistry?: string) {\n return this.base.fetchComponent(name, this.resolve(subRegistry));\n }\n\n hasComponent(name: string, subRegistry?: string) {\n return this.base.hasComponent(name, this.resolve(subRegistry));\n }\n}\n\nexport class FumadocsComponentInstaller extends ComponentInstaller {\n private interactive: {\n name: string;\n spin: SpinnerResult;\n } | null = null;\n\n constructor(\n connector: RegistryConnector,\n config: LoadedConfig,\n cwd?: string,\n io?: Partial<IOInterface>,\n ) {\n super(new UILibraryConnector(connector, UIRegistries[config.uiLibrary]), {\n cwd,\n framework: config.framework === 'astro' ? 'none' : config.framework,\n outDir: {\n base: config.baseDir,\n components: config.aliases.componentsDir,\n css: config.aliases.cssDir,\n layout: config.aliases.layoutDir,\n lib: config.aliases.libDir,\n ui: config.aliases.uiDir,\n },\n io: {\n onWarn: (message) => {\n this.interactive?.spin.message(message);\n },\n confirmFileOverride: async (options) => {\n if (!this.interactive) return true;\n const { name, spin } = this.interactive;\n spin.clear();\n const value = await confirm({\n message: `Do you want to override ${options.path}?`,\n initialValue: false,\n });\n if (isCancel(value)) {\n outro('Installation terminated');\n process.exit(0);\n }\n spin.start(picocolors.bold(picocolors.cyanBright(`Installing ${name}`)));\n return value;\n },\n onFileDownloaded: (options) => {\n this.interactive?.spin.message(options.path);\n },\n ...io,\n },\n plugins: [pluginPreserveLayouts(), pluginReuseUI(config, cwd)],\n });\n }\n\n async installInteractive(name: string, subRegistry?: string): Promise<void> {\n if (this.interactive) {\n throw new Error(`cannot install while installing another component`);\n }\n\n const spin = spinner();\n spin.start(picocolors.bold(picocolors.cyanBright(`Installing ${name}`)));\n\n try {\n this.interactive = { name, spin };\n const deps = await super.install(name, subRegistry).then((res) => res.deps());\n spin.stop(picocolors.bold(picocolors.greenBright(`${name} installed`)));\n\n if (deps.hasRequired()) {\n log.message();\n box([...deps.dependencies, ...deps.devDependencies].join('\\n'), 'New Dependencies');\n const pm = (await detectPackageManager())?.name ?? 'npm';\n const value = await confirm({\n message: `Do you want to install with ${pm}?`,\n });\n\n if (isCancel(value)) {\n outro('Installation terminated');\n process.exit(0);\n }\n\n if (value) {\n const spin = spinner({\n errorMessage: 'Failed to install dependencies',\n });\n spin.start('Installing dependencies');\n await deps.installRequired(pm);\n spin.stop('Dependencies installed');\n } else {\n await deps.writeRequired();\n }\n }\n } catch (e) {\n spin.error(e instanceof Error ? e.message : String(e));\n process.exit(-1);\n } finally {\n this.interactive = null;\n }\n }\n}\n"],"mappings":";;;;;;;AAAA,MAAa,eAAe;CAC1B,WAAW;CACX,YAAY;AACd;;;;;;ACEA,SAAgB,wBAAyC;CACvD,MAAM,cAAc;EAClB;EACA;EACA;EACA;EACA;EACA;CACF;CAEA,MAAM,cAAsC;EAC1C,8BAA8B;EAC9B,gCAAgC;EAChC,iCAAiC;EACjC,kCAAkC;EAClC,mCAAmC;EACnC,uCAAuC;EACvC,wCAAwC;EACxC,8BAA8B;EAC9B,+BAA+B;EAC/B,mCAAmC;EACnC,oCAAoC;EACpC,8BAA8B;EAC9B,mCAAmC;EACnC,oCAAoC;EACpC,+BAA+B;EAC/B,oCAAoC;CACtC;CACA,MAAM,gBAAgB,IAAI,IAAI,WAAW;CAEzC,OAAO;EACL,cAAc,MAAM,EAAE,SAAS;GAE7B,IADwB,cAAc,IAAI,MAAM,EAAE,CAAC,IACjC,GAAG;GAErB,OAAO;IACL,GAAG;IACH,gBAAgB,KAAK,eAAe,QAAQ,UAAU,CAAC,cAAc,IAAI,MAAM,IAAI,CAAC;GACtF;EACF;EACA,gBAAgB,WAAW,EAAE,SAAS;GAGpC,IAFwB,cAAc,IAAI,MAAM,EAAE,CAAC,IAEjC,KAAK,EAAE,aAAa,cAAc,OAAO;GAE3D,OAAO,eAAe,YAAY;EACpC;CACF;AACF;;;;;;AC7CA,SAAgB,cAAc,QAAsB,MAAM,QAAQ,IAAI,GAAoB;CACxF,MAAM,EAAE,SAAS,YAAY;CAC7B,MAAM,QAAQ,QAAQ,SAAS,KAAK,QAAQ,KAAK,SAAS,QAAQ,KAAK;CAEvE,OAAO;EACL,cAAc,MAAM;GAClB,MAAM,QAAQ,KAAK,MAAM,QAAQ,SAAS;IACxC,IAAI,KAAK,SAAS,OAAO,OAAO,EAAE,SAAS,KAAK,SAAS;IACzD,IAAI,KAAK,SAAS,MAAM,OAAO;IAC/B,OAAO,CAAC,WAAW,KAAK,QAAQ,KAAK,SAAS,QAAQ,OAAO,KAAK,SAAS,KAAK,IAAI,CAAC,CAAC;GACxF,CAAC;GAED,IAAI,MAAM,WAAW,KAAK,MAAM,QAAQ;GAExC,IAAI,MAAM,WAAW,GAAG,OAAO;IAAE,GAAG;IAAM;IAAO,cAAc,CAAC;IAAG,iBAAiB,CAAC;GAAE;GACvF,OAAO;IAAE,GAAG;IAAM;GAAM;EAC1B;EACA,gBAAgB,WAAW,EAAE,YAAY;GACvC,IAAI,CAAC,SAAS,cAAc,qBAAqB,OAAO;GACxD,MAAM,WAAW,KAAK,SAAS,KAAK,QAAQ,QAAQ,GAAG,KAAK,CAAC,CAAC,WAAW,KAAK,KAAK,GAAG;GACtF,OAAO,SAAS,WAAW,GAAG,IAAI,WAAW,KAAK;EACpD;CACF;AACF;;;;;;;ACzBA,SAAgBA,WAAY,OAAoC;CAC9D,OAAOC,SAAe,KAAK;AAC7B;;;ACGA,MAAM,eAAe,IAAI,IAAI,OAAO,OAAO,YAAY,CAAC;;AAGxD,IAAM,qBAAN,MAAsD;CAGpD,YACE,MACA,IACA;EAFiB,KAAA,OAAA;EACA,KAAA,KAAA;EAEjB,KAAK,KAAK,KAAK;CACjB;CAEA,QAAgB,aAAsB;EACpC,OAAO,eAAe,aAAa,IAAI,WAAW,IAAI,KAAK,KAAK;CAClE;CAEA,kBAAkB,aAAsB;EACtC,OAAO,KAAK,KAAK,kBAAkB,KAAK,QAAQ,WAAW,CAAC;CAC9D;CAEA,eAAe,MAAc,aAAsB;EACjD,OAAO,KAAK,KAAK,eAAe,MAAM,KAAK,QAAQ,WAAW,CAAC;CACjE;CAEA,aAAa,MAAc,aAAsB;EAC/C,OAAO,KAAK,KAAK,aAAa,MAAM,KAAK,QAAQ,WAAW,CAAC;CAC/D;AACF;AAEA,IAAa,6BAAb,cAAgD,mBAAmB;CAMjE,YACE,WACA,QACA,KACA,IACA;EACA,MAAM,IAAI,mBAAmB,WAAW,aAAa,OAAO,UAAU,GAAG;GACvE;GACA,WAAW,OAAO,cAAc,UAAU,SAAS,OAAO;GAC1D,QAAQ;IACN,MAAM,OAAO;IACb,YAAY,OAAO,QAAQ;IAC3B,KAAK,OAAO,QAAQ;IACpB,QAAQ,OAAO,QAAQ;IACvB,KAAK,OAAO,QAAQ;IACpB,IAAI,OAAO,QAAQ;GACrB;GACA,IAAI;IACF,SAAS,YAAY;KACnB,KAAK,aAAa,KAAK,QAAQ,OAAO;IACxC;IACA,qBAAqB,OAAO,YAAY;KACtC,IAAI,CAAC,KAAK,aAAa,OAAO;KAC9B,MAAM,EAAE,MAAM,SAAS,KAAK;KAC5B,KAAK,MAAM;KACX,MAAM,QAAQ,MAAM,QAAQ;MAC1B,SAAS,2BAA2B,QAAQ,KAAK;MACjD,cAAc;KAChB,CAAC;KACD,IAAIC,WAAS,KAAK,GAAG;MACnB,MAAM,yBAAyB;MAC/B,QAAQ,KAAK,CAAC;KAChB;KACA,KAAK,MAAM,WAAW,KAAK,WAAW,WAAW,cAAc,MAAM,CAAC,CAAC;KACvE,OAAO;IACT;IACA,mBAAmB,YAAY;KAC7B,KAAK,aAAa,KAAK,QAAQ,QAAQ,IAAI;IAC7C;IACA,GAAG;GACL;GACA,SAAS,CAAC,sBAAsB,GAAG,cAAc,QAAQ,GAAG,CAAC;EAC/D,CAAC;EA5CQ,KAAA,cAAA;CA6CX;CAEA,MAAM,mBAAmB,MAAc,aAAqC;EAC1E,IAAI,KAAK,aACP,MAAM,IAAI,MAAM,mDAAmD;EAGrE,MAAM,OAAO,QAAQ;EACrB,KAAK,MAAM,WAAW,KAAK,WAAW,WAAW,cAAc,MAAM,CAAC,CAAC;EAEvE,IAAI;GACF,KAAK,cAAc;IAAE;IAAM;GAAK;GAChC,MAAM,OAAO,MAAM,MAAM,QAAQ,MAAM,WAAW,CAAC,CAAC,MAAM,QAAQ,IAAI,KAAK,CAAC;GAC5E,KAAK,KAAK,WAAW,KAAK,WAAW,YAAY,GAAG,KAAK,WAAW,CAAC,CAAC;GAEtE,IAAI,KAAK,YAAY,GAAG;IACtB,IAAI,QAAQ;IACZ,IAAI,CAAC,GAAG,KAAK,cAAc,GAAG,KAAK,eAAe,CAAC,CAAC,KAAK,IAAI,GAAG,kBAAkB;IAClF,MAAM,MAAM,MAAM,qBAAqB,EAAA,EAAI,QAAQ;IACnD,MAAM,QAAQ,MAAM,QAAQ,EAC1B,SAAS,+BAA+B,GAAG,GAC7C,CAAC;IAED,IAAIA,WAAS,KAAK,GAAG;KACnB,MAAM,yBAAyB;KAC/B,QAAQ,KAAK,CAAC;IAChB;IAEA,IAAI,OAAO;KACT,MAAM,OAAO,QAAQ,EACnB,cAAc,iCAChB,CAAC;KACD,KAAK,MAAM,yBAAyB;KACpC,MAAM,KAAK,gBAAgB,EAAE;KAC7B,KAAK,KAAK,wBAAwB;IACpC,OACE,MAAM,KAAK,cAAc;GAE7B;EACF,SAAS,GAAG;GACV,KAAK,MAAM,aAAa,QAAQ,EAAE,UAAU,OAAO,CAAC,CAAC;GACrD,QAAQ,KAAK,EAAE;EACjB,UAAU;GACR,KAAK,cAAc;EACrB;CACF;AACF"}
@@ -1,5 +1,5 @@
1
1
  import { c as addNextRewrites, n as enableProcessedMarkdown, p as addImport, r as addTanstackPrerender, t as addProxyMatcher } from "./codemod-auol-p4R.js";
2
- import { a as routeModule, i as reactRouterTypes, n as findNextProxy, r as formatRoute, t as docs } from "./docs-DeIEl_Kt.js";
2
+ import { a as routeModule, i as reactRouterTypes, n as findNextProxy, r as formatRoute, t as docs } from "./docs--WqEHTyl.js";
3
3
  import { a as reactFramework, c as requiresMarkdown, d as sourceRef, n as addPageUrl, s as registerReactRouterRoutes, t as addExport, u as sharedRoute } from "./utils-DbMe7h6c.js";
4
4
  import path from "node:path";
5
5
  //#region src/features/llms.ts
@@ -225,4 +225,4 @@ async function extendNextProxy(ctx, route) {
225
225
  //#endregion
226
226
  export { markdownRewrite as a, templates as c, llmsRoutes as i, extendNextProxy as n, markdownUrl as o, llms as r, tanstackMarkdownUrl as s, docsLlms as t };
227
227
 
228
- //# sourceMappingURL=llms-CONd8Vnn.js.map
228
+ //# sourceMappingURL=llms-Br0RJXXg.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"llms-CONd8Vnn.js","names":[],"sources":["../src/features/llms.ts"],"sourcesContent":["import path from 'node:path';\nimport type { Feature, FeatureContext } from '@/features';\nimport type { I18nInfo, ReactFramework } from '@/project';\nimport {\n type FormattedRoute,\n formatRoute,\n reactRouterTypes,\n routeModule,\n type RouteDescriptor,\n} from '@/project/route';\nimport {\n addImport,\n addNextRewrites,\n addProxyMatcher,\n addTanstackPrerender,\n enableProcessedMarkdown,\n} from '@/codemod';\nimport { docs, findNextProxy } from './docs';\nimport {\n addExport,\n addPageUrl,\n reactFramework,\n type SourceRef,\n sourceRef,\n registerReactRouterRoutes,\n requiresMarkdown,\n sharedRoute,\n} from './utils';\n\n/** runtime content sources expose raw Markdown as `page.data.content`, Fumadocs MDX processes it */\nexport const docsLlms = ({ dynamic, ref }: SourceRef) => `\nexport const docsLlms = llms(${ref}, {\n renderPage: ${dynamic ? '(page)' : 'async (page)'} => \\`# \\${page.data.title} (\\${page.url})\n\n\\${${dynamic ? 'page.data.content' : \"await page.data.getText('processed')\"}}\\`,\n});`;\n\n/** the Markdown URL of a page, `/llms.mdx/docs/<slugs>/content.md` */\nexport const markdownUrl = (i18n: boolean) => `\nconst getContentUrl = createGetUrl(docsContentRoute${i18n ? ', i18n' : ''});\n\nexport function getPageMarkdownUrl(page: { slugs: string[]; locale?: string }) {\n const segments = [...page.slugs, 'content.md'];\n\n return { segments, url: getContentUrl(segments, page.locale) };\n}`;\n\n/** TanStack Start serves Markdown with a `.md` suffix under the docs route */\nexport const tanstackMarkdownUrl = (i18n: boolean) => `\nconst getDocsUrl = createGetUrl(docsRoute${i18n ? ', i18n' : ''});\n\nexport function getPageMarkdownUrl(page: { slugs: string[]; locale?: string }) {\n const segments = [...page.slugs];\n if (segments.length === 0) {\n segments.push('index.md');\n } else {\n segments[segments.length - 1] += '.md';\n }\n\n return { segments, url: getDocsUrl(segments, page.locale) };\n}\n\n/** @returns page slugs */\nexport function decodeMarkdownUrl(segments: string[]) {\n if (segments.length === 0) return [];\n\n const out = [...segments];\n out[out.length - 1] = out[out.length - 1].replace(/\\\\.md$/, '');\n if (out.length === 1 && out[0] === 'index') out.pop();\n return out;\n}`;\n\nconst imports = `import { docsLlms } from '@/lib/source';`;\nconst pageImports = ({ ref }: SourceRef) => `import { docsLlms, ${ref} } from '@/lib/source';`;\nconst markdown = `new Response(await docsLlms.page(page), {\n headers: {\n 'Content-Type': 'text/markdown',\n },\n })`;\n\ninterface Routes {\n index: FormattedRoute;\n full: FormattedRoute;\n markdown: FormattedRoute;\n}\n\ntype Template = (route: FormattedRoute, i18n: boolean, src: SourceRef) => string;\n\n/** the per-page Markdown route, the only one reading route params */\nconst markdownTemplates: Record<ReactFramework, Template> = {\n next: (route, i18n, src) => `${pageImports(src)}\nimport { notFound } from 'next/navigation';\n\nexport const revalidate = false;\n\nexport async function GET(_req: Request, { params }: RouteContext<'${route.path}'>) {\n const { slug${i18n ? ', lang' : ''} } = await params;\n // remove the appended \"content.md\", \\`/docs/index.md\\` is rewritten to the root page\n const slugs = slug?.slice(0, -1) ?? [];\n if (slugs.at(-1) === 'index') slugs.pop();\n const page = ${src.resolved}.getPage(slugs${i18n ? ', lang' : ''});\n if (!page) notFound();\n\n return ${markdown};\n}\n\nexport ${src.dynamic ? 'async ' : ''}function generateStaticParams() {\n return ${src.resolved}.generateParams().map((item) => ({\n ...item,\n slug: [...item.slug, 'content.md'],\n }));\n}\n`,\n 'react-router': (route, i18n, src) => `${reactRouterTypes(route)}\n${pageImports(src)}\n\nexport async function loader({ params }: Route.LoaderArgs) {\n const slugs = params['*'].split('/').filter((v) => v.length > 0);\n // remove the appended \"content.md\"\n slugs.pop();\n const page = ${src.resolved}.getPage(slugs${i18n ? ', params.lang' : ''});\n if (!page) return new Response('not found', { status: 404 });\n\n return ${markdown};\n}\n`,\n 'tanstack-start': (\n route,\n i18n,\n src,\n ) => `import { createFileRoute, notFound } from '@tanstack/react-router';\nimport { decodeMarkdownUrl } from '@/lib/shared';\n${pageImports(src)}\n\nexport const Route = createFileRoute('${route.path}')({\n server: {\n handlers: {\n GET: async ({ params }) => {\n const slugs = decodeMarkdownUrl(params._splat?.split('/') ?? []);\n const page = ${src.resolved}.getPage(slugs${i18n ? ', params.lang' : ''});\n if (!page) throw notFound();\n\n return ${markdown.replaceAll('\\n ', '\\n ')};\n },\n },\n },\n});\n`,\n waku: (route, i18n, src) => `${pageImports(src)}\nimport type { ApiContext } from 'waku/router';\nimport { unstable_notFound } from 'waku/router/server';\n\nexport async function GET(_: Request, { params }: ApiContext<'${route.path}'>) {\n const page = ${src.resolved}.getPage(params.slug${i18n ? ', params.lang' : ''});\n if (!page) unstable_notFound();\n\n return ${markdown};\n}\n\nexport async function getConfig() {\n return {\n render: 'static' as const,\n staticPaths: ${src.resolved}.generateParams().map((item) => ${i18n ? '[item.lang, ...item.slug]' : 'item.slug'}),\n } as const;\n}\n`,\n};\n\nexport function templates(\n framework: ReactFramework,\n routes: Routes,\n i18n: boolean,\n src: SourceRef,\n): [route: FormattedRoute, content: string][] {\n const index = (body: string) => ({ imports, body, revalidate: true, static: true });\n\n return [\n [\n routes.index,\n routeModule(framework, routes.index, index('new Response(await docsLlms.index())')),\n ],\n [\n routes.full,\n routeModule(framework, routes.full, index('new Response(await docsLlms.full())')),\n ],\n [routes.markdown, markdownTemplates[framework](routes.markdown, i18n, src)],\n ];\n}\n\nexport function llmsRoutes(\n framework: ReactFramework,\n i18n: I18nInfo | null,\n docsRoute: string,\n contentRoute: string,\n): Routes {\n const format = (route: RouteDescriptor) => formatRoute(route, framework, i18n);\n return {\n index: format({ segments: ['llms.txt'] }),\n full: format({ segments: ['llms-full.txt'] }),\n markdown: format(\n framework === 'tanstack-start'\n ? { segments: [docsRoute, '{$}.md'], locale: true }\n : {\n segments: [\n contentRoute,\n { param: 'slug', catchAll: true, optional: true, suffix: 'content.md' },\n ],\n locale: true,\n },\n ),\n };\n}\n\nexport const llms: Feature = {\n id: 'llms',\n title: 'LLM Routes',\n description: 'serve docs as Markdown for LLMs: llms.txt, llms-full.txt and per-page Markdown',\n requires: [docs],\n supports: requiresMarkdown,\n async apply(ctx) {\n const { baseDir, i18n, source, configFile } = ctx.project;\n const framework = reactFramework(ctx.project);\n const sourceFile = path.join(baseDir, 'lib/source.ts');\n const src = sourceRef(ctx.project.source.dynamic);\n // runtime content sources already give raw Markdown\n if (source.collections) {\n await ctx.source(source.collections, (file) => {\n if (!enableProcessedMarkdown(file))\n throw new Error(`cannot find \\`defineDocs()\\` in ${source.collections}`);\n });\n }\n if (await addExport(ctx, sourceFile, 'docsLlms', docsLlms(src))) {\n await ctx.source(sourceFile, (file) =>\n addImport(file, { from: 'fumadocs-core/source', named: ['llms'] }),\n );\n }\n\n const docsRoute = source.baseUrl.replace(/\\/$/, '');\n let contentRoute = `/llms.mdx${docsRoute}`;\n if (framework === 'tanstack-start') {\n await sharedRoute(ctx, 'docsRoute', source.baseUrl);\n await addPageUrl(ctx, 'getPageMarkdownUrl', tanstackMarkdownUrl(i18n !== null));\n } else {\n contentRoute = await sharedRoute(ctx, 'docsContentRoute', contentRoute);\n await addPageUrl(ctx, 'getPageMarkdownUrl', markdownUrl(i18n !== null));\n }\n const routes = llmsRoutes(framework, i18n, docsRoute, contentRoute);\n for (const [route, content] of templates(framework, routes, i18n !== null, src))\n await ctx.write(path.join(baseDir, route.file), content);\n\n if (framework === 'react-router') {\n await registerReactRouterRoutes(ctx, [routes.index, routes.full, routes.markdown]);\n } else if (framework === 'tanstack-start' && ctx.project.static && configFile) {\n // SPA mode only serves prerendered routes\n await ctx.source(configFile, (file) =>\n addTanstackPrerender(file, [routes.index.path, routes.full.path]),\n );\n } else if (framework === 'next') {\n if (i18n) await extendNextProxy(ctx, routes.markdown);\n await configureMarkdownUrl(ctx, docsRoute, routes.markdown);\n }\n if (ctx.project.static && framework !== 'next') {\n ctx.note(\n 'Per-page Markdown routes are dynamic, add the pages to your prerender config to serve them statically.',\n );\n }\n if (framework === 'react-router' || framework === 'waku') {\n ctx.note(\n 'To serve pages with a `.md` suffix, rewrite `/docs/*.md` to the `llms.mdx` route in a middleware, see https://fumadocs.dev/docs/integrations/llms#md-extension.',\n );\n }\n ctx.note(\n \"Add page actions to your docs page with `MarkdownCopyButton` and `ViewOptionsPopover` from 'fumadocs-ui/layouts/docs/page', the Markdown URL of a page is `getPageMarkdownUrl(page).url` from '@/lib/shared'.\",\n );\n },\n};\n\n/** `/docs/*.md` serves the Markdown of a page, like TanStack Start's `.md` route */\nexport const markdownRewrite = (docsRoute: string, markdown: FormattedRoute, i18n: boolean) => ({\n source: `${i18n ? '/:lang' : ''}${docsRoute}/:slug*.md`,\n destination: `${markdown.pattern}/content.md`,\n});\n\nasync function configureMarkdownUrl(\n ctx: FeatureContext,\n docsRoute: string,\n markdown: FormattedRoute,\n) {\n const { configFile, i18n } = ctx.project;\n const rewrite = markdownRewrite(docsRoute, markdown, i18n !== null);\n const edited =\n configFile !== undefined &&\n (await ctx\n .source(configFile, (file) => {\n if (file.code.includes('llms.mdx')) return;\n if (!addNextRewrites(file, [rewrite])) throw new Error('cannot add rewrites');\n })\n .catch(() => false));\n\n if (!edited) {\n ctx.note(\n `Rewrite \\`${rewrite.source}\\` to \\`${rewrite.destination}\\` in your Next.js config to serve pages with a \\`.md\\` suffix.`,\n );\n }\n}\n\n/** routes under `[lang]` need the i18n middleware to resolve the locale */\nexport async function extendNextProxy(ctx: FeatureContext, route: FormattedRoute) {\n const patterns = [route.pattern.replace('/:lang', ''), route.pattern];\n const proxy = await findNextProxy(ctx.project);\n const edited =\n proxy !== undefined &&\n (await ctx\n .source(proxy, (file) => {\n if (!addProxyMatcher(file, patterns)) throw new Error('cannot find the matcher');\n })\n .catch(() => false));\n if (!edited)\n ctx.note(\n `Add ${patterns.map((p) => `\\`${p}\\``).join(', ')} to the matcher of your i18n middleware.`,\n );\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"llms-Br0RJXXg.js","names":[],"sources":["../src/features/llms.ts"],"sourcesContent":["import path from 'node:path';\nimport type { Feature, FeatureContext } from '@/features';\nimport type { I18nInfo, ReactFramework } from '@/project';\nimport {\n type FormattedRoute,\n formatRoute,\n reactRouterTypes,\n routeModule,\n type RouteDescriptor,\n} from '@/project/route';\nimport {\n addImport,\n addNextRewrites,\n addProxyMatcher,\n addTanstackPrerender,\n enableProcessedMarkdown,\n} from '@/codemod';\nimport { docs, findNextProxy } from './docs';\nimport {\n addExport,\n addPageUrl,\n reactFramework,\n type SourceRef,\n sourceRef,\n registerReactRouterRoutes,\n requiresMarkdown,\n sharedRoute,\n} from './utils';\n\n/** runtime content sources expose raw Markdown as `page.data.content`, Fumadocs MDX processes it */\nexport const docsLlms = ({ dynamic, ref }: SourceRef) => `\nexport const docsLlms = llms(${ref}, {\n renderPage: ${dynamic ? '(page)' : 'async (page)'} => \\`# \\${page.data.title} (\\${page.url})\n\n\\${${dynamic ? 'page.data.content' : \"await page.data.getText('processed')\"}}\\`,\n});`;\n\n/** the Markdown URL of a page, `/llms.mdx/docs/<slugs>/content.md` */\nexport const markdownUrl = (i18n: boolean) => `\nconst getContentUrl = createGetUrl(docsContentRoute${i18n ? ', i18n' : ''});\n\nexport function getPageMarkdownUrl(page: { slugs: string[]; locale?: string }) {\n const segments = [...page.slugs, 'content.md'];\n\n return { segments, url: getContentUrl(segments, page.locale) };\n}`;\n\n/** TanStack Start serves Markdown with a `.md` suffix under the docs route */\nexport const tanstackMarkdownUrl = (i18n: boolean) => `\nconst getDocsUrl = createGetUrl(docsRoute${i18n ? ', i18n' : ''});\n\nexport function getPageMarkdownUrl(page: { slugs: string[]; locale?: string }) {\n const segments = [...page.slugs];\n if (segments.length === 0) {\n segments.push('index.md');\n } else {\n segments[segments.length - 1] += '.md';\n }\n\n return { segments, url: getDocsUrl(segments, page.locale) };\n}\n\n/** @returns page slugs */\nexport function decodeMarkdownUrl(segments: string[]) {\n if (segments.length === 0) return [];\n\n const out = [...segments];\n out[out.length - 1] = out[out.length - 1].replace(/\\\\.md$/, '');\n if (out.length === 1 && out[0] === 'index') out.pop();\n return out;\n}`;\n\nconst imports = `import { docsLlms } from '@/lib/source';`;\nconst pageImports = ({ ref }: SourceRef) => `import { docsLlms, ${ref} } from '@/lib/source';`;\nconst markdown = `new Response(await docsLlms.page(page), {\n headers: {\n 'Content-Type': 'text/markdown',\n },\n })`;\n\ninterface Routes {\n index: FormattedRoute;\n full: FormattedRoute;\n markdown: FormattedRoute;\n}\n\ntype Template = (route: FormattedRoute, i18n: boolean, src: SourceRef) => string;\n\n/** the per-page Markdown route, the only one reading route params */\nconst markdownTemplates: Record<ReactFramework, Template> = {\n next: (route, i18n, src) => `${pageImports(src)}\nimport { notFound } from 'next/navigation';\n\nexport const revalidate = false;\n\nexport async function GET(_req: Request, { params }: RouteContext<'${route.path}'>) {\n const { slug${i18n ? ', lang' : ''} } = await params;\n // remove the appended \"content.md\", \\`/docs/index.md\\` is rewritten to the root page\n const slugs = slug?.slice(0, -1) ?? [];\n if (slugs.at(-1) === 'index') slugs.pop();\n const page = ${src.resolved}.getPage(slugs${i18n ? ', lang' : ''});\n if (!page) notFound();\n\n return ${markdown};\n}\n\nexport ${src.dynamic ? 'async ' : ''}function generateStaticParams() {\n return ${src.resolved}.generateParams().map((item) => ({\n ...item,\n slug: [...item.slug, 'content.md'],\n }));\n}\n`,\n 'react-router': (route, i18n, src) => `${reactRouterTypes(route)}\n${pageImports(src)}\n\nexport async function loader({ params }: Route.LoaderArgs) {\n const slugs = params['*'].split('/').filter((v) => v.length > 0);\n // remove the appended \"content.md\"\n slugs.pop();\n const page = ${src.resolved}.getPage(slugs${i18n ? ', params.lang' : ''});\n if (!page) return new Response('not found', { status: 404 });\n\n return ${markdown};\n}\n`,\n 'tanstack-start': (\n route,\n i18n,\n src,\n ) => `import { createFileRoute, notFound } from '@tanstack/react-router';\nimport { decodeMarkdownUrl } from '@/lib/shared';\n${pageImports(src)}\n\nexport const Route = createFileRoute('${route.path}')({\n server: {\n handlers: {\n GET: async ({ params }) => {\n const slugs = decodeMarkdownUrl(params._splat?.split('/') ?? []);\n const page = ${src.resolved}.getPage(slugs${i18n ? ', params.lang' : ''});\n if (!page) throw notFound();\n\n return ${markdown.replaceAll('\\n ', '\\n ')};\n },\n },\n },\n});\n`,\n waku: (route, i18n, src) => `${pageImports(src)}\nimport type { ApiContext } from 'waku/router';\nimport { unstable_notFound } from 'waku/router/server';\n\nexport async function GET(_: Request, { params }: ApiContext<'${route.path}'>) {\n const page = ${src.resolved}.getPage(params.slug${i18n ? ', params.lang' : ''});\n if (!page) unstable_notFound();\n\n return ${markdown};\n}\n\nexport async function getConfig() {\n return {\n render: 'static' as const,\n staticPaths: ${src.resolved}.generateParams().map((item) => ${i18n ? '[item.lang, ...item.slug]' : 'item.slug'}),\n } as const;\n}\n`,\n};\n\nexport function templates(\n framework: ReactFramework,\n routes: Routes,\n i18n: boolean,\n src: SourceRef,\n): [route: FormattedRoute, content: string][] {\n const index = (body: string) => ({ imports, body, revalidate: true, static: true });\n\n return [\n [\n routes.index,\n routeModule(framework, routes.index, index('new Response(await docsLlms.index())')),\n ],\n [\n routes.full,\n routeModule(framework, routes.full, index('new Response(await docsLlms.full())')),\n ],\n [routes.markdown, markdownTemplates[framework](routes.markdown, i18n, src)],\n ];\n}\n\nexport function llmsRoutes(\n framework: ReactFramework,\n i18n: I18nInfo | null,\n docsRoute: string,\n contentRoute: string,\n): Routes {\n const format = (route: RouteDescriptor) => formatRoute(route, framework, i18n);\n return {\n index: format({ segments: ['llms.txt'] }),\n full: format({ segments: ['llms-full.txt'] }),\n markdown: format(\n framework === 'tanstack-start'\n ? { segments: [docsRoute, '{$}.md'], locale: true }\n : {\n segments: [\n contentRoute,\n { param: 'slug', catchAll: true, optional: true, suffix: 'content.md' },\n ],\n locale: true,\n },\n ),\n };\n}\n\nexport const llms: Feature = {\n id: 'llms',\n title: 'LLM Routes',\n description: 'serve docs as Markdown for LLMs: llms.txt, llms-full.txt and per-page Markdown',\n requires: [docs],\n supports: requiresMarkdown,\n async apply(ctx) {\n const { baseDir, i18n, source, configFile } = ctx.project;\n const framework = reactFramework(ctx.project);\n const sourceFile = path.join(baseDir, 'lib/source.ts');\n const src = sourceRef(ctx.project.source.dynamic);\n // runtime content sources already give raw Markdown\n if (source.collections) {\n await ctx.source(source.collections, (file) => {\n if (!enableProcessedMarkdown(file))\n throw new Error(`cannot find \\`defineDocs()\\` in ${source.collections}`);\n });\n }\n if (await addExport(ctx, sourceFile, 'docsLlms', docsLlms(src))) {\n await ctx.source(sourceFile, (file) =>\n addImport(file, { from: 'fumadocs-core/source', named: ['llms'] }),\n );\n }\n\n const docsRoute = source.baseUrl.replace(/\\/$/, '');\n let contentRoute = `/llms.mdx${docsRoute}`;\n if (framework === 'tanstack-start') {\n await sharedRoute(ctx, 'docsRoute', source.baseUrl);\n await addPageUrl(ctx, 'getPageMarkdownUrl', tanstackMarkdownUrl(i18n !== null));\n } else {\n contentRoute = await sharedRoute(ctx, 'docsContentRoute', contentRoute);\n await addPageUrl(ctx, 'getPageMarkdownUrl', markdownUrl(i18n !== null));\n }\n const routes = llmsRoutes(framework, i18n, docsRoute, contentRoute);\n for (const [route, content] of templates(framework, routes, i18n !== null, src))\n await ctx.write(path.join(baseDir, route.file), content);\n\n if (framework === 'react-router') {\n await registerReactRouterRoutes(ctx, [routes.index, routes.full, routes.markdown]);\n } else if (framework === 'tanstack-start' && ctx.project.static && configFile) {\n // SPA mode only serves prerendered routes\n await ctx.source(configFile, (file) =>\n addTanstackPrerender(file, [routes.index.path, routes.full.path]),\n );\n } else if (framework === 'next') {\n if (i18n) await extendNextProxy(ctx, routes.markdown);\n await configureMarkdownUrl(ctx, docsRoute, routes.markdown);\n }\n if (ctx.project.static && framework !== 'next') {\n ctx.note(\n 'Per-page Markdown routes are dynamic, add the pages to your prerender config to serve them statically.',\n );\n }\n if (framework === 'react-router' || framework === 'waku') {\n ctx.note(\n 'To serve pages with a `.md` suffix, rewrite `/docs/*.md` to the `llms.mdx` route in a middleware, see https://fumadocs.dev/docs/integrations/llms#md-extension.',\n );\n }\n ctx.note(\n \"Add page actions to your docs page with `MarkdownCopyButton` and `ViewOptionsPopover` from 'fumadocs-ui/layouts/docs/page', the Markdown URL of a page is `getPageMarkdownUrl(page).url` from '@/lib/shared'.\",\n );\n },\n};\n\n/** `/docs/*.md` serves the Markdown of a page, like TanStack Start's `.md` route */\nexport const markdownRewrite = (docsRoute: string, markdown: FormattedRoute, i18n: boolean) => ({\n source: `${i18n ? '/:lang' : ''}${docsRoute}/:slug*.md`,\n destination: `${markdown.pattern}/content.md`,\n});\n\nasync function configureMarkdownUrl(\n ctx: FeatureContext,\n docsRoute: string,\n markdown: FormattedRoute,\n) {\n const { configFile, i18n } = ctx.project;\n const rewrite = markdownRewrite(docsRoute, markdown, i18n !== null);\n const edited =\n configFile !== undefined &&\n (await ctx\n .source(configFile, (file) => {\n if (file.code.includes('llms.mdx')) return;\n if (!addNextRewrites(file, [rewrite])) throw new Error('cannot add rewrites');\n })\n .catch(() => false));\n\n if (!edited) {\n ctx.note(\n `Rewrite \\`${rewrite.source}\\` to \\`${rewrite.destination}\\` in your Next.js config to serve pages with a \\`.md\\` suffix.`,\n );\n }\n}\n\n/** routes under `[lang]` need the i18n middleware to resolve the locale */\nexport async function extendNextProxy(ctx: FeatureContext, route: FormattedRoute) {\n const patterns = [route.pattern.replace('/:lang', ''), route.pattern];\n const proxy = await findNextProxy(ctx.project);\n const edited =\n proxy !== undefined &&\n (await ctx\n .source(proxy, (file) => {\n if (!addProxyMatcher(file, patterns)) throw new Error('cannot find the matcher');\n })\n .catch(() => false));\n if (!edited)\n ctx.note(\n `Add ${patterns.map((p) => `\\`${p}\\``).join(', ')} to the matcher of your i18n middleware.`,\n );\n}\n"],"mappings":"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"}
@@ -1,7 +1,7 @@
1
1
  import { i as addReactRouterPrerenderArray } from "./codemod-auol-p4R.js";
2
- import { a as routeModule, r as formatRoute } from "./docs-DeIEl_Kt.js";
2
+ import { a as routeModule, r as formatRoute } from "./docs--WqEHTyl.js";
3
3
  import { a as reactFramework, c as requiresMarkdown, d as sourceRef, s as registerReactRouterRoutes } from "./utils-DbMe7h6c.js";
4
- import { r as llms } from "./llms-CONd8Vnn.js";
4
+ import { r as llms } from "./llms-Br0RJXXg.js";
5
5
  import path from "node:path";
6
6
  //#region src/features/mcp.ts
7
7
  const server = ({ ref }) => `import { createMcpHandler, McpServer } from '@modelcontextprotocol/server';
@@ -64,4 +64,4 @@ const mcp = {
64
64
  //#endregion
65
65
  export { mcpRoute as n, templates as r, mcp as t };
66
66
 
67
- //# sourceMappingURL=mcp-BT8BLZZ-.js.map
67
+ //# sourceMappingURL=mcp-Ce_W93we.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"mcp-BT8BLZZ-.js","names":[],"sources":["../src/features/mcp.ts"],"sourcesContent":["import path from 'node:path';\nimport type { Feature } from '@/features';\nimport type { ReactFramework } from '@/project';\nimport { type FormattedRoute, formatRoute, routeModule } from '@/project/route';\nimport { addReactRouterPrerenderArray } from '@/codemod';\nimport { llms } from './llms';\nimport {\n reactFramework,\n registerReactRouterRoutes,\n requiresMarkdown,\n type SourceRef,\n sourceRef,\n} from './utils';\n\nconst server = ({\n ref,\n}: SourceRef) => `import { createMcpHandler, McpServer } from '@modelcontextprotocol/server';\nimport { registerSearchTool, registerSourceTools } from 'fumadocs-core/mcp';\nimport { createFromSource } from 'fumadocs-core/search/server';\nimport { docsLlms, ${ref} } from '@/lib/source';\n\nconst handler = createMcpHandler(() => {\n const mcp = new McpServer({\n name: 'docs',\n version: '1.0.0',\n });\n\n registerSourceTools(mcp, ${ref}, docsLlms);\n registerSearchTool(mcp, createFromSource(${ref}));\n\n return mcp;\n});\n`;\n\nexport const templates = (framework: ReactFramework, route: FormattedRoute, src: SourceRef) =>\n routeModule(framework, route, {\n imports: server(src),\n body: 'handler.fetch(request)',\n request: true,\n methods: ['GET', 'POST', 'DELETE'],\n });\n\nexport const mcpRoute = (framework: ReactFramework) =>\n formatRoute({ segments: ['api/mcp'] }, framework, null);\n\nexport const mcp: Feature = {\n id: 'mcp',\n title: 'MCP Server',\n description: 'a MCP server for AI agents to search and read your docs, at /api/mcp',\n requires: [llms],\n supports: (project) =>\n project.static ? 'MCP requires a server at runtime' : requiresMarkdown(project),\n async apply(ctx) {\n const { baseDir } = ctx.project;\n const framework = reactFramework(ctx.project);\n ctx.addDependencies({ '@modelcontextprotocol/server': null, zod: null });\n\n const route = mcpRoute(framework);\n await ctx.write(\n path.join(baseDir, route.file),\n templates(framework, route, sourceRef(ctx.project.source.dynamic)),\n );\n\n if (framework === 'react-router') {\n await registerReactRouterRoutes(ctx, [route]);\n await ctx.source('react-router.config.ts', (file) => {\n addReactRouterPrerenderArray(file, 'excluded', [`/${route.path}`]);\n });\n }\n\n ctx.note(`Connect AI agents to your docs with the MCP server:\n{\n \"mcpServers\": {\n \"docs\": { \"url\": \"https://your-site.com/api/mcp\" }\n }\n}`);\n },\n};\n"],"mappings":";;;;;;AAcA,MAAM,UAAU,EACd,UACe;;;qBAGI,IAAI;;;;;;;;6BAQI,IAAI;6CACY,IAAI;;;;;AAMjD,MAAa,aAAa,WAA2B,OAAuB,QAC1E,YAAY,WAAW,OAAO;CAC5B,SAAS,OAAO,GAAG;CACnB,MAAM;CACN,SAAS;CACT,SAAS;EAAC;EAAO;EAAQ;CAAQ;AACnC,CAAC;AAEH,MAAa,YAAY,cACvB,YAAY,EAAE,UAAU,CAAC,SAAS,EAAE,GAAG,WAAW,IAAI;AAExD,MAAa,MAAe;CAC1B,IAAI;CACJ,OAAO;CACP,aAAa;CACb,UAAU,CAAC,IAAI;CACf,WAAW,YACT,QAAQ,SAAS,qCAAqC,iBAAiB,OAAO;CAChF,MAAM,MAAM,KAAK;EACf,MAAM,EAAE,YAAY,IAAI;EACxB,MAAM,YAAY,eAAe,IAAI,OAAO;EAC5C,IAAI,gBAAgB;GAAE,gCAAgC;GAAM,KAAK;EAAK,CAAC;EAEvE,MAAM,QAAQ,SAAS,SAAS;EAChC,MAAM,IAAI,MACR,KAAK,KAAK,SAAS,MAAM,IAAI,GAC7B,UAAU,WAAW,OAAO,UAAU,IAAI,QAAQ,OAAO,OAAO,CAAC,CACnE;EAEA,IAAI,cAAc,gBAAgB;GAChC,MAAM,0BAA0B,KAAK,CAAC,KAAK,CAAC;GAC5C,MAAM,IAAI,OAAO,2BAA2B,SAAS;IACnD,6BAA6B,MAAM,YAAY,CAAC,IAAI,MAAM,MAAM,CAAC;GACnE,CAAC;EACH;EAEA,IAAI,KAAK;;;;;EAKX;CACA;AACF"}
1
+ {"version":3,"file":"mcp-Ce_W93we.js","names":[],"sources":["../src/features/mcp.ts"],"sourcesContent":["import path from 'node:path';\nimport type { Feature } from '@/features';\nimport type { ReactFramework } from '@/project';\nimport { type FormattedRoute, formatRoute, routeModule } from '@/project/route';\nimport { addReactRouterPrerenderArray } from '@/codemod';\nimport { llms } from './llms';\nimport {\n reactFramework,\n registerReactRouterRoutes,\n requiresMarkdown,\n type SourceRef,\n sourceRef,\n} from './utils';\n\nconst server = ({\n ref,\n}: SourceRef) => `import { createMcpHandler, McpServer } from '@modelcontextprotocol/server';\nimport { registerSearchTool, registerSourceTools } from 'fumadocs-core/mcp';\nimport { createFromSource } from 'fumadocs-core/search/server';\nimport { docsLlms, ${ref} } from '@/lib/source';\n\nconst handler = createMcpHandler(() => {\n const mcp = new McpServer({\n name: 'docs',\n version: '1.0.0',\n });\n\n registerSourceTools(mcp, ${ref}, docsLlms);\n registerSearchTool(mcp, createFromSource(${ref}));\n\n return mcp;\n});\n`;\n\nexport const templates = (framework: ReactFramework, route: FormattedRoute, src: SourceRef) =>\n routeModule(framework, route, {\n imports: server(src),\n body: 'handler.fetch(request)',\n request: true,\n methods: ['GET', 'POST', 'DELETE'],\n });\n\nexport const mcpRoute = (framework: ReactFramework) =>\n formatRoute({ segments: ['api/mcp'] }, framework, null);\n\nexport const mcp: Feature = {\n id: 'mcp',\n title: 'MCP Server',\n description: 'a MCP server for AI agents to search and read your docs, at /api/mcp',\n requires: [llms],\n supports: (project) =>\n project.static ? 'MCP requires a server at runtime' : requiresMarkdown(project),\n async apply(ctx) {\n const { baseDir } = ctx.project;\n const framework = reactFramework(ctx.project);\n ctx.addDependencies({ '@modelcontextprotocol/server': null, zod: null });\n\n const route = mcpRoute(framework);\n await ctx.write(\n path.join(baseDir, route.file),\n templates(framework, route, sourceRef(ctx.project.source.dynamic)),\n );\n\n if (framework === 'react-router') {\n await registerReactRouterRoutes(ctx, [route]);\n await ctx.source('react-router.config.ts', (file) => {\n addReactRouterPrerenderArray(file, 'excluded', [`/${route.path}`]);\n });\n }\n\n ctx.note(`Connect AI agents to your docs with the MCP server:\n{\n \"mcpServers\": {\n \"docs\": { \"url\": \"https://your-site.com/api/mcp\" }\n }\n}`);\n },\n};\n"],"mappings":";;;;;;AAcA,MAAM,UAAU,EACd,UACe;;;qBAGI,IAAI;;;;;;;;6BAQI,IAAI;6CACY,IAAI;;;;;AAMjD,MAAa,aAAa,WAA2B,OAAuB,QAC1E,YAAY,WAAW,OAAO;CAC5B,SAAS,OAAO,GAAG;CACnB,MAAM;CACN,SAAS;CACT,SAAS;EAAC;EAAO;EAAQ;CAAQ;AACnC,CAAC;AAEH,MAAa,YAAY,cACvB,YAAY,EAAE,UAAU,CAAC,SAAS,EAAE,GAAG,WAAW,IAAI;AAExD,MAAa,MAAe;CAC1B,IAAI;CACJ,OAAO;CACP,aAAa;CACb,UAAU,CAAC,IAAI;CACf,WAAW,YACT,QAAQ,SAAS,qCAAqC,iBAAiB,OAAO;CAChF,MAAM,MAAM,KAAK;EACf,MAAM,EAAE,YAAY,IAAI;EACxB,MAAM,YAAY,eAAe,IAAI,OAAO;EAC5C,IAAI,gBAAgB;GAAE,gCAAgC;GAAM,KAAK;EAAK,CAAC;EAEvE,MAAM,QAAQ,SAAS,SAAS;EAChC,MAAM,IAAI,MACR,KAAK,KAAK,SAAS,MAAM,IAAI,GAC7B,UAAU,WAAW,OAAO,UAAU,IAAI,QAAQ,OAAO,OAAO,CAAC,CACnE;EAEA,IAAI,cAAc,gBAAgB;GAChC,MAAM,0BAA0B,KAAK,CAAC,KAAK,CAAC;GAC5C,MAAM,IAAI,OAAO,2BAA2B,SAAS;IACnD,6BAA6B,MAAM,YAAY,CAAC,IAAI,MAAM,MAAM,CAAC;GACnE,CAAC;EACH;EAEA,IAAI,KAAK;;;;;EAKX;CACA;AACF"}
@@ -1,7 +1,7 @@
1
1
  import { S as getProperty, f as addElements, u as getConfigObject } from "./codemod-auol-p4R.js";
2
- import { i as reactRouterTypes, r as formatRoute, t as docs } from "./docs-DeIEl_Kt.js";
2
+ import { i as reactRouterTypes, r as formatRoute, t as docs } from "./docs--WqEHTyl.js";
3
3
  import { a as reactFramework, d as sourceRef, n as addPageUrl, s as registerReactRouterRoutes, u as sharedRoute } from "./utils-DbMe7h6c.js";
4
- import { n as extendNextProxy } from "./llms-CONd8Vnn.js";
4
+ import { n as extendNextProxy } from "./llms-Br0RJXXg.js";
5
5
  import path from "node:path";
6
6
  //#region src/features/og.ts
7
7
  /** the image URL of a page, `/og/docs/<slugs>/image.png` */
@@ -139,4 +139,4 @@ const og = {
139
139
  //#endregion
140
140
  export { og as t };
141
141
 
142
- //# sourceMappingURL=og-DRDOyQ_t.js.map
142
+ //# sourceMappingURL=og-LFlX1Bl4.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"og-DRDOyQ_t.js","names":[],"sources":["../src/features/og.ts"],"sourcesContent":["import path from 'node:path';\nimport type { Feature } from '@/features';\nimport type { ReactFramework } from '@/project';\nimport { type FormattedRoute, formatRoute, reactRouterTypes } from '@/project/route';\nimport { addElements, getConfigObject, getProperty } from '@/codemod';\nimport { docs } from './docs';\nimport { extendNextProxy } from './llms';\nimport {\n addPageUrl,\n reactFramework,\n registerReactRouterRoutes,\n sharedRoute,\n type SourceRef,\n sourceRef,\n} from './utils';\n\ntype Engine = 'takumi' | 'next-og';\n\n/** the image URL of a page, `/og/docs/<slugs>/image.png` */\nconst imageUrl = (ext: string, i18n: boolean) => `\nconst getImageUrl = createGetUrl(docsImageRoute${i18n ? ', i18n' : ''});\n\nexport function getPageImageUrl(page: { slugs: string[]; locale?: string }) {\n const segments = [...page.slugs, 'image.${ext}'];\n\n return { segments, url: getImageUrl(segments, page.locale) };\n}`;\n\nconst imports = (engine: Engine) =>\n `import { generateOGImage } from 'fumadocs-ui/og${engine === 'takumi' ? '/takumi' : ''}';`;\n\nconst image = (engine: Engine) => `generateOGImage({\n title: page.data.title,\n description: page.data.description,\n site: 'My App',${engine === 'takumi' ? \"\\n format: 'webp',\" : ''}\n })`;\n\nconst ext = (engine: Engine) => (engine === 'takumi' ? 'webp' : 'png');\n\ntype Template = (route: FormattedRoute, engine: Engine, i18n: boolean, src: SourceRef) => string;\n\nconst templates: Record<ReactFramework, Template> = {\n next: (route, engine, i18n, src) => `import { ${src.ref} } from '@/lib/source';\nimport { notFound } from 'next/navigation';\n${imports(engine)}\n\nexport const revalidate = false;\n\nexport async function GET(_req: Request, { params }: RouteContext<'${route.path}'>) {\n const { slug${i18n ? ', lang' : ''} } = await params;\n const page = ${src.resolved}.getPage(slug.slice(0, -1)${i18n ? ', lang' : ''});\n if (!page) notFound();\n\n return ${image(engine)};\n}\n\nexport ${src.dynamic ? 'async ' : ''}function generateStaticParams() {\n return ${src.resolved}.generateParams().map((item) => ({\n ...item,\n slug: [...item.slug, 'image.${ext(engine)}'],\n }));\n}\n`,\n 'react-router': (route, engine, i18n, src) => `${reactRouterTypes(route)}\nimport { ${src.ref} } from '@/lib/source';\n${imports(engine)}\n\nexport async function loader({ params }: Route.LoaderArgs) {\n const slugs = params['*'].split('/').filter((v) => v.length > 0);\n const page = ${src.resolved}.getPage(slugs.slice(0, -1)${i18n ? ', params.lang' : ''});\n if (!page) throw new Response(undefined, { status: 404 });\n\n return ${image(engine)};\n}\n`,\n 'tanstack-start': (\n route,\n engine,\n i18n,\n src,\n ) => `import { createFileRoute } from '@tanstack/react-router';\nimport { ${src.ref} } from '@/lib/source';\n${imports(engine)}\n\nexport const Route = createFileRoute('${route.path}')({\n server: {\n handlers: {\n GET: async ({ params }) => {\n const slugs = params._splat?.split('/') ?? [];\n const page = ${src.resolved}.getPage(slugs.slice(0, -1)${i18n ? ', params.lang' : ''});\n if (!page) return new Response(undefined, { status: 404 });\n\n return ${image(engine).replaceAll('\\n ', '\\n ')};\n },\n },\n },\n});\n`,\n waku: (route, engine, i18n, src) => `import { ${src.ref} } from '@/lib/source';\nimport type { ApiContext } from 'waku/router';\n${imports(engine)}\n\nexport async function GET(_: Request, { params }: ApiContext<'${route.path}'>) {\n const page = ${src.resolved}.getPage(params.slug${i18n ? ', params.lang' : ''});\n if (!page) return new Response(undefined, { status: 404 });\n\n return ${image(engine)};\n}\n\nexport async function getConfig() {\n return {\n render: 'static' as const,\n staticPaths: ${src.resolved}.generateParams().map((item) => ${i18n ? '[item.lang, ...item.slug]' : 'item.slug'}),\n } as const;\n}\n`,\n};\n\nexport const og: Feature<{ engine: Engine }> = {\n id: 'og',\n title: 'OG Image',\n description: 'generate Open Graph images for docs pages',\n requires: [docs],\n options: {\n engine: {\n message: 'Choose an image engine',\n choices: [\n { value: 'takumi', label: 'Takumi', hint: 'outputs WebP, framework-agnostic' },\n { value: 'next-og', label: 'next/og', hint: 'Next.js built-in solution' },\n ],\n },\n },\n async apply(ctx, { engine }) {\n const { baseDir, configFile, i18n, source } = ctx.project;\n const framework = reactFramework(ctx.project);\n if (engine === 'next-og' && framework !== 'next')\n throw new Error('next/og is only available on Next.js');\n if (engine === 'takumi') ctx.addDependencies({ 'takumi-js': null });\n\n const shared = path.join(baseDir, 'lib/shared.ts');\n const imageRoute = await sharedRoute(\n ctx,\n 'docsImageRoute',\n `/og${source.baseUrl.replace(/\\/$/, '')}`,\n );\n const added = await addPageUrl(ctx, 'getPageImageUrl', imageUrl(ext(engine), i18n !== null));\n if (!added && engine === 'takumi') {\n await ctx.source(shared, (file) => {\n file.s.replaceAll('image.png', 'image.webp');\n });\n }\n\n const route = formatRoute(\n {\n segments: [imageRoute, { param: 'slug', catchAll: true, suffix: `image.${ext(engine)}` }],\n locale: true,\n },\n framework,\n i18n,\n 'tsx',\n );\n await ctx.write(\n path.join(baseDir, route.file),\n templates[framework](route, engine, i18n !== null, sourceRef(ctx.project.source.dynamic)),\n );\n\n if (framework === 'react-router') await registerReactRouterRoutes(ctx, [route]);\n if (framework === 'next' && engine === 'takumi' && configFile) {\n await ctx.source(configFile, (file) => {\n const config = getConfigObject(file);\n if (config && !getProperty(config, 'serverExternalPackages'))\n addElements(file, config, [\"serverExternalPackages: ['@takumi-rs/core']\"]);\n });\n }\n if (framework === 'next' && i18n) await extendNextProxy(ctx, route);\n\n ctx.note(\n \"The image URL of a page is `getPageImageUrl(page).url` from '@/lib/shared', reference it in the page metadata (e.g. `og:image`).\",\n );\n },\n};\n"],"mappings":"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"}
1
+ {"version":3,"file":"og-LFlX1Bl4.js","names":[],"sources":["../src/features/og.ts"],"sourcesContent":["import path from 'node:path';\nimport type { Feature } from '@/features';\nimport type { ReactFramework } from '@/project';\nimport { type FormattedRoute, formatRoute, reactRouterTypes } from '@/project/route';\nimport { addElements, getConfigObject, getProperty } from '@/codemod';\nimport { docs } from './docs';\nimport { extendNextProxy } from './llms';\nimport {\n addPageUrl,\n reactFramework,\n registerReactRouterRoutes,\n sharedRoute,\n type SourceRef,\n sourceRef,\n} from './utils';\n\ntype Engine = 'takumi' | 'next-og';\n\n/** the image URL of a page, `/og/docs/<slugs>/image.png` */\nconst imageUrl = (ext: string, i18n: boolean) => `\nconst getImageUrl = createGetUrl(docsImageRoute${i18n ? ', i18n' : ''});\n\nexport function getPageImageUrl(page: { slugs: string[]; locale?: string }) {\n const segments = [...page.slugs, 'image.${ext}'];\n\n return { segments, url: getImageUrl(segments, page.locale) };\n}`;\n\nconst imports = (engine: Engine) =>\n `import { generateOGImage } from 'fumadocs-ui/og${engine === 'takumi' ? '/takumi' : ''}';`;\n\nconst image = (engine: Engine) => `generateOGImage({\n title: page.data.title,\n description: page.data.description,\n site: 'My App',${engine === 'takumi' ? \"\\n format: 'webp',\" : ''}\n })`;\n\nconst ext = (engine: Engine) => (engine === 'takumi' ? 'webp' : 'png');\n\ntype Template = (route: FormattedRoute, engine: Engine, i18n: boolean, src: SourceRef) => string;\n\nconst templates: Record<ReactFramework, Template> = {\n next: (route, engine, i18n, src) => `import { ${src.ref} } from '@/lib/source';\nimport { notFound } from 'next/navigation';\n${imports(engine)}\n\nexport const revalidate = false;\n\nexport async function GET(_req: Request, { params }: RouteContext<'${route.path}'>) {\n const { slug${i18n ? ', lang' : ''} } = await params;\n const page = ${src.resolved}.getPage(slug.slice(0, -1)${i18n ? ', lang' : ''});\n if (!page) notFound();\n\n return ${image(engine)};\n}\n\nexport ${src.dynamic ? 'async ' : ''}function generateStaticParams() {\n return ${src.resolved}.generateParams().map((item) => ({\n ...item,\n slug: [...item.slug, 'image.${ext(engine)}'],\n }));\n}\n`,\n 'react-router': (route, engine, i18n, src) => `${reactRouterTypes(route)}\nimport { ${src.ref} } from '@/lib/source';\n${imports(engine)}\n\nexport async function loader({ params }: Route.LoaderArgs) {\n const slugs = params['*'].split('/').filter((v) => v.length > 0);\n const page = ${src.resolved}.getPage(slugs.slice(0, -1)${i18n ? ', params.lang' : ''});\n if (!page) throw new Response(undefined, { status: 404 });\n\n return ${image(engine)};\n}\n`,\n 'tanstack-start': (\n route,\n engine,\n i18n,\n src,\n ) => `import { createFileRoute } from '@tanstack/react-router';\nimport { ${src.ref} } from '@/lib/source';\n${imports(engine)}\n\nexport const Route = createFileRoute('${route.path}')({\n server: {\n handlers: {\n GET: async ({ params }) => {\n const slugs = params._splat?.split('/') ?? [];\n const page = ${src.resolved}.getPage(slugs.slice(0, -1)${i18n ? ', params.lang' : ''});\n if (!page) return new Response(undefined, { status: 404 });\n\n return ${image(engine).replaceAll('\\n ', '\\n ')};\n },\n },\n },\n});\n`,\n waku: (route, engine, i18n, src) => `import { ${src.ref} } from '@/lib/source';\nimport type { ApiContext } from 'waku/router';\n${imports(engine)}\n\nexport async function GET(_: Request, { params }: ApiContext<'${route.path}'>) {\n const page = ${src.resolved}.getPage(params.slug${i18n ? ', params.lang' : ''});\n if (!page) return new Response(undefined, { status: 404 });\n\n return ${image(engine)};\n}\n\nexport async function getConfig() {\n return {\n render: 'static' as const,\n staticPaths: ${src.resolved}.generateParams().map((item) => ${i18n ? '[item.lang, ...item.slug]' : 'item.slug'}),\n } as const;\n}\n`,\n};\n\nexport const og: Feature<{ engine: Engine }> = {\n id: 'og',\n title: 'OG Image',\n description: 'generate Open Graph images for docs pages',\n requires: [docs],\n options: {\n engine: {\n message: 'Choose an image engine',\n choices: [\n { value: 'takumi', label: 'Takumi', hint: 'outputs WebP, framework-agnostic' },\n { value: 'next-og', label: 'next/og', hint: 'Next.js built-in solution' },\n ],\n },\n },\n async apply(ctx, { engine }) {\n const { baseDir, configFile, i18n, source } = ctx.project;\n const framework = reactFramework(ctx.project);\n if (engine === 'next-og' && framework !== 'next')\n throw new Error('next/og is only available on Next.js');\n if (engine === 'takumi') ctx.addDependencies({ 'takumi-js': null });\n\n const shared = path.join(baseDir, 'lib/shared.ts');\n const imageRoute = await sharedRoute(\n ctx,\n 'docsImageRoute',\n `/og${source.baseUrl.replace(/\\/$/, '')}`,\n );\n const added = await addPageUrl(ctx, 'getPageImageUrl', imageUrl(ext(engine), i18n !== null));\n if (!added && engine === 'takumi') {\n await ctx.source(shared, (file) => {\n file.s.replaceAll('image.png', 'image.webp');\n });\n }\n\n const route = formatRoute(\n {\n segments: [imageRoute, { param: 'slug', catchAll: true, suffix: `image.${ext(engine)}` }],\n locale: true,\n },\n framework,\n i18n,\n 'tsx',\n );\n await ctx.write(\n path.join(baseDir, route.file),\n templates[framework](route, engine, i18n !== null, sourceRef(ctx.project.source.dynamic)),\n );\n\n if (framework === 'react-router') await registerReactRouterRoutes(ctx, [route]);\n if (framework === 'next' && engine === 'takumi' && configFile) {\n await ctx.source(configFile, (file) => {\n const config = getConfigObject(file);\n if (config && !getProperty(config, 'serverExternalPackages'))\n addElements(file, config, [\"serverExternalPackages: ['@takumi-rs/core']\"]);\n });\n }\n if (framework === 'next' && i18n) await extendNextProxy(ctx, route);\n\n ctx.note(\n \"The image URL of a page is `getPageImageUrl(page).url` from '@/lib/shared', reference it in the page metadata (e.g. `og:image`).\",\n );\n },\n};\n"],"mappings":"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"}
@@ -1,2 +1,2 @@
1
- import { a as Project, c as detectFramework, d as loadProject, i as PackageManager, l as frameworks, n as FrameworkInfo, o as ReactFramework, r as I18nInfo, s as SourceInfo, t as Framework, u as isSupportedFramework } from "../index-BCVGQxn9.js";
1
+ import { a as Project, c as detectFramework, d as loadProject, i as PackageManager, l as frameworks, n as FrameworkInfo, o as ReactFramework, r as I18nInfo, s as SourceInfo, t as Framework, u as isSupportedFramework } from "../index-DI66DoV8.js";
2
2
  export { Framework, FrameworkInfo, I18nInfo, PackageManager, Project, ReactFramework, SourceInfo, detectFramework, frameworks, isSupportedFramework, loadProject };
@@ -1,4 +1,4 @@
1
- import { r as LoadedConfig } from "../config-CT2jj39D.js";
1
+ import { r as LoadedConfig } from "../config-Bfg4yG8S.js";
2
2
  import { ComponentInstaller, IOInterface } from "fuma-cli/registry/installer";
3
3
  import { RegistryConnector } from "fuma-cli/registry/connector";
4
4
  //#region src/registry/installer.d.ts
@@ -1 +1 @@
1
- {"version":3,"file":"installer.d.ts","names":[],"sources":["../../src/registry/installer.ts"],"mappings":";;;;qBASa,mCAAmC;UACtC;EAKR,YACE,WAAW,mBACX,QAAQ,cACR,cACA,KAAK,QAAQ;EAyCT,mBAAmB,cAAc,uBAAuB"}
1
+ {"version":3,"file":"installer.d.ts","names":[],"sources":["../../src/registry/installer.ts"],"mappings":";;;;qBAyCa,mCAAmC;UACtC;EAKR,YACE,WAAW,mBACX,QAAQ,cACR,cACA,KAAK,QAAQ;EAyCT,mBAAmB,cAAc,uBAAuB"}
@@ -1,2 +1,2 @@
1
- import { t as FumadocsComponentInstaller } from "../installer-D0Obb2Md.js";
1
+ import { t as FumadocsComponentInstaller } from "../installer-CzhVfs7i.js";
2
2
  export { FumadocsComponentInstaller };
package/dist/schema.json CHANGED
@@ -1 +1 @@
1
- {"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","properties":{"$schema":{"default":"node_modules/@fumadocs/cli/dist/schema.json","type":"string"},"aliases":{"default":{"uiDir":"./components/ui","componentsDir":"./components","layoutDir":"./layouts","cssDir":"./styles","libDir":"./lib"},"type":"object","properties":{"uiDir":{"default":"./components/ui","type":"string"},"componentsDir":{"default":"./components","type":"string"},"layoutDir":{"default":"./layouts","type":"string"},"cssDir":{"default":"./styles","type":"string"},"libDir":{"default":"./lib","type":"string"}}},"baseDir":{"default":"src","type":"string"},"uiLibrary":{"default":"base-ui","type":"string","enum":["radix-ui","base-ui"]},"framework":{"default":"next","type":"string","enum":["next","astro","waku","react-router","tanstack-start"]},"commands":{"default":{},"type":"object","properties":{"format":{"type":"string"}}}}}
1
+ {"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","properties":{"$schema":{"default":"node_modules/@fumadocs/cli/dist/schema.json","type":"string"},"aliases":{"default":{"uiDir":"./components/ui","componentsDir":"./components","layoutDir":"./layouts","cssDir":"./styles","libDir":"./lib"},"type":"object","properties":{"uiDir":{"default":"./components/ui","type":"string"},"componentsDir":{"default":"./components","type":"string"},"layoutDir":{"default":"./layouts","type":"string"},"cssDir":{"default":"./styles","type":"string"},"libDir":{"default":"./lib","type":"string"},"utils":{"type":"string"}}},"baseDir":{"default":"src","type":"string"},"uiLibrary":{"default":"base-ui","type":"string","enum":["radix-ui","base-ui"]},"framework":{"default":"next","type":"string","enum":["next","astro","waku","react-router","tanstack-start"]},"commands":{"default":{},"type":"object","properties":{"format":{"type":"string"}}}}}
@@ -1,5 +1,5 @@
1
1
  import { b as findJsxElement, m as addJsxAttribute, o as filterReactRouterPrerenderArray, p as addImport, r as addTanstackPrerender, s as filterReactRouterRoute } from "./codemod-auol-p4R.js";
2
- import { a as routeModule, r as formatRoute, t as docs } from "./docs-DeIEl_Kt.js";
2
+ import { a as routeModule, r as formatRoute, t as docs } from "./docs--WqEHTyl.js";
3
3
  import { a as reactFramework, d as sourceRef, i as posix, l as scripts, r as findSource, s as registerReactRouterRoutes } from "./utils-DbMe7h6c.js";
4
4
  import path from "node:path";
5
5
  //#region src/features/search.ts
@@ -378,4 +378,4 @@ async function removeDefaultSearch(ctx, file) {
378
378
  //#endregion
379
379
  export { search as t };
380
380
 
381
- //# sourceMappingURL=search-iphnSVVY.js.map
381
+ //# sourceMappingURL=search-WfSdiGx-.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"search-iphnSVVY.js","names":[],"sources":["../src/features/search.ts"],"sourcesContent":["import path from 'node:path';\nimport type { Feature, FeatureContext } from '@/features';\nimport type { ReactFramework } from '@/project';\nimport { type FormattedRoute, formatRoute, routeModule } from '@/project/route';\nimport {\n addImport,\n addJsxAttribute,\n addTanstackPrerender,\n filterReactRouterPrerenderArray,\n filterReactRouterRoute,\n findJsxElement,\n} from '@/codemod';\nimport { docs } from './docs';\nimport {\n findSource,\n posix,\n reactFramework,\n registerReactRouterRoutes,\n scripts,\n type SourceRef,\n sourceRef,\n} from './utils';\n\n/** how client code reads public env variables */\ninterface Env {\n prefix: string;\n read: string;\n}\n\nconst publicEnv = (next: boolean): Env =>\n next\n ? { prefix: 'NEXT_PUBLIC_', read: 'process.env.NEXT_PUBLIC_' }\n : { prefix: 'VITE_', read: 'import.meta.env.VITE_' };\n\nconst dialogImports = `import {\n SearchDialog,\n SearchDialogClose,\n SearchDialogContent,\n SearchDialogFooter,\n SearchDialogHeader,\n SearchDialogIcon,\n SearchDialogInput,\n SearchDialogList,\n SearchDialogOverlay,\n type SharedProps,\n} from 'fumadocs-ui/components/dialog/search';\nimport { useI18n } from 'fumadocs-ui/contexts/i18n';`;\n\nconst dialogBody = (footer: string, url: string) => ` return (\n <SearchDialog search={search} onSearchChange={setSearch} isLoading={query.isLoading} {...props}>\n <SearchDialogOverlay />\n <SearchDialogContent>\n <SearchDialogHeader>\n <SearchDialogIcon />\n <SearchDialogInput />\n <SearchDialogClose />\n </SearchDialogHeader>\n <SearchDialogList items={query.data !== 'empty' ? query.data : null} />\n <SearchDialogFooter>\n <a\n href=\"${url}\"\n rel=\"noreferrer noopener\"\n className=\"ms-auto text-xs text-fd-muted-foreground\"\n >\n Search powered by ${footer}\n </a>\n </SearchDialogFooter>\n </SearchDialogContent>\n </SearchDialog>\n );\n}\n`;\n\n/** `structuredData` is a function on async collections (React Router & TanStack Start) */\nconst typesenseIndexes = (\n async: boolean,\n { ref, resolved }: SourceRef,\n) => `import { ${ref} } from '@/lib/source';\nimport type { DocumentRecord } from 'typesense-fumadocs-adapter';\n\nexport async function exportSearchIndexes() {\n const results: DocumentRecord[] = [];\n for (const page of ${resolved}.getPages()) {\n results.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 results;\n}\n`;\n\nconst syncScript = (imports: string, body: string) => `import * as fs from 'node:fs/promises';\n${imports}\n\nasync function main(filePath: string) {\n const content = await fs.readFile(filePath);\n const records = JSON.parse(content.toString()) as DocumentRecord[];\n${body}\n console.log(\\`search updated: \\${records.length} records\\`);\n}\n\n// the path of pre-rendered \\`static.json\\`\nconst filePath = process.argv[2];\nif (!filePath) throw new Error('missing the path of static.json');\nvoid main(filePath);\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?: (src: SourceRef) => {\n imports: string;\n /** expression producing the documents */\n documents: string;\n /** generated `lib/export-search-indexes.ts`, for providers without a `toDocuments()` */\n file?: (async: boolean) => string;\n };\n sync?: (env: Env, dir: string) => string;\n /** server-side search, replaces the default search route */\n searchRoute?: Record<ReactFramework, string>;\n}\n\nconst providers = {\n 'orama-cloud': {\n label: 'Orama Cloud',\n hint: 'signup needed',\n dependencies: { '@orama/core': null },\n publicEnv: ['ORAMA_DATASOURCE_ID', 'ORAMA_PROJECT_ID', 'ORAMA_API_KEY'],\n privateEnv: ['ORAMA_PRIVATE_API_KEY'],\n static: true,\n dialog: (env) => `'use client';\n${dialogImports}\nimport { useDocsSearch } from 'fumadocs-core/search/client';\nimport { OramaCloud } from '@orama/core';\n\nconst client = new OramaCloud({\n projectId: ${env.read}ORAMA_PROJECT_ID!,\n apiKey: ${env.read}ORAMA_API_KEY!,\n});\n\nexport default function CustomSearchDialog(props: SharedProps) {\n const { locale } = useI18n();\n const { search, setSearch, query } = useDocsSearch({\n type: 'orama-cloud',\n client,\n locale,\n });\n\n${dialogBody('Orama', 'https://orama.com')}`,\n exportIndexes: ({ ref }) => ({\n imports: `import { ${ref} } from '@/lib/source';\nimport { toDocuments } from 'fumadocs-core/search/orama-cloud';`,\n documents: `toDocuments(${ref})`,\n }),\n sync: (env) =>\n syncScript(\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\n await sync(orama, {\n index: process.env.${env.prefix}ORAMA_DATASOURCE_ID!,\n documents: records,\n });\n`,\n ),\n },\n algolia: {\n label: 'Algolia',\n hint: 'signup needed',\n dependencies: { algoliasearch: null },\n publicEnv: ['ALGOLIA_APP_ID', 'ALGOLIA_SEARCH_KEY'],\n privateEnv: ['ALGOLIA_WRITE_KEY'],\n static: true,\n dialog: (env) => `'use client';\n${dialogImports}\nimport { useDocsSearch } from 'fumadocs-core/search/client';\nimport { algoliaClient } from 'fumadocs-core/search/client/algolia';\nimport { liteClient } from 'algoliasearch/lite';\n\nconst algolia = liteClient(${env.read}ALGOLIA_APP_ID!, ${env.read}ALGOLIA_SEARCH_KEY!);\n\nexport default function CustomSearchDialog(props: SharedProps) {\n const { locale } = useI18n();\n const { search, setSearch, query } = useDocsSearch({\n client: algoliaClient({\n client: algolia,\n indexName: 'document',\n locale,\n }),\n });\n\n${dialogBody('Algolia', 'https://algolia.com')}`,\n exportIndexes: ({ ref }) => ({\n imports: `import { ${ref} } from '@/lib/source';\nimport { toDocuments } from 'fumadocs-core/search/algolia';`,\n documents: `toDocuments(${ref})`,\n }),\n sync: (env) =>\n syncScript(\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\n await sync(client, {\n indexName: 'document',\n documents: records,\n });\n`,\n ),\n },\n typesense: {\n label: 'Typesense',\n hint: 'self-hosted or cloud, community adapter',\n dependencies: { typesense: null, 'typesense-fumadocs-adapter': null },\n publicEnv: ['TYPESENSE_URL', 'TYPESENSE_SEARCH_KEY'],\n privateEnv: ['TYPESENSE_API_KEY'],\n static: true,\n dialog: (env) => `'use client';\n${dialogImports}\nimport { Client } from 'typesense';\nimport { useTypesenseSearch } from 'typesense-fumadocs-adapter/client';\n\nconst client = new Client({\n nodes: [{ url: ${env.read}TYPESENSE_URL! }],\n apiKey: ${env.read}TYPESENSE_SEARCH_KEY!,\n});\n\nexport default function CustomSearchDialog(props: SharedProps) {\n const { locale } = useI18n();\n const { search, setSearch, query } = useTypesenseSearch({\n typesenseCollectionName: 'docs',\n locale,\n client,\n });\n\n${dialogBody('Typesense', 'https://typesense.org')}`,\n exportIndexes: (src) => ({\n imports: \"import { exportSearchIndexes } from '@/lib/export-search-indexes';\",\n documents: 'exportSearchIndexes()',\n file: (async) => typesenseIndexes(async, src),\n }),\n sync: (env) =>\n syncScript(\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\n await sync(client, {\n typesenseCollectionName: 'docs',\n documents: records,\n });\n`,\n ),\n },\n mixedbread: {\n label: 'Mixedbread',\n hint: 'AI search, signup needed',\n dependencies: { '@mixedbread/sdk': null },\n publicEnv: [],\n privateEnv: ['MIXEDBREAD_API_KEY', 'MIXEDBREAD_STORE_ID'],\n static: false,\n dialog: () => `'use client';\n${dialogImports}\nimport { useDocsSearch } from 'fumadocs-core/search/client';\nimport { fetchClient } from 'fumadocs-core/search/client/fetch';\n\nexport default function CustomSearchDialog(props: SharedProps) {\n const { locale } = useI18n();\n const { search, setSearch, query } = useDocsSearch({\n client: fetchClient({\n api: '/api/search',\n locale,\n }),\n });\n\n${dialogBody('Mixedbread', 'https://mixedbread.com')}`,\n sync: (_env, dir) => `import { spawnSync } from 'node:child_process';\n\n// sync the content with Mixedbread CLI\nconst result = spawnSync(\n 'npx',\n ['--yes', '@mixedbread/cli', 'vs', 'sync', process.env.MIXEDBREAD_STORE_ID!, '${dir}', '--ci'],\n { stdio: 'inherit' },\n);\n\nprocess.exit(result.status ?? 1);\n`,\n searchRoute: (() => {\n const server = `import { createMixedbreadSearchAPI } from 'fumadocs-core/search/mixedbread';\nimport Mixedbread from '@mixedbread/sdk';\n\nconst server = createMixedbreadSearchAPI({\n client: new Mixedbread({\n apiKey: process.env.MIXEDBREAD_API_KEY,\n }),\n storeIdentifier: process.env.MIXEDBREAD_STORE_ID!,\n});\n`;\n return {\n next: `${server}\nexport const { GET } = server;\n`,\n 'react-router': `import type { Route } from './+types/search';\n${server}\nexport async function loader({ request }: Route.LoaderArgs) {\n return server.GET(request);\n}\n`,\n 'tanstack-start': `import { createFileRoute } from '@tanstack/react-router';\n${server}\nexport const Route = createFileRoute('/api/search')({\n server: {\n handlers: {\n GET: async ({ request }) => server.GET(request),\n },\n },\n});\n`,\n waku: `${server}\nexport const { GET } = server;\n`,\n };\n })(),\n },\n} satisfies Record<string, Provider>;\n\nexport type SearchProvider = keyof typeof providers;\n\nconst staticRoute = (\n framework: ReactFramework,\n route: FormattedRoute,\n imports: string,\n documents: string,\n) =>\n routeModule(framework, route, {\n imports,\n body: `Response.json(await ${documents})`,\n revalidate: true,\n static: true,\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 const env = publicEnv(framework === 'next');\n\n ctx.addDependencies(provider.dependencies);\n await ctx.write(path.join(baseDir, 'components/search.tsx'), provider.dialog(env));\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 { imports, documents, file } = provider.exportIndexes(\n sourceRef(project.source.dynamic),\n );\n if (file)\n await ctx.write(\n path.join(baseDir, 'lib/export-search-indexes.ts'),\n file(project.source.async),\n );\n\n const route = formatRoute({ segments: ['static.json'] }, framework, null);\n await ctx.write(\n path.join(baseDir, route.file),\n staticRoute(framework, route, imports, documents),\n );\n\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 // path of the pre-rendered `static.json`, passed to the sync script\n const output = {\n next: '.next/server/app/static.json.body',\n 'react-router': 'build/client/static.json',\n 'tanstack-start': project.static ? 'dist/client/static.json' : '.output/public/static.json',\n waku: 'dist/public/static.json',\n }[framework];\n await ctx.write('scripts/sync-content.ts', provider.sync(env, project.source.dir));\n await ctx.packageJson((data) => {\n const build = data.scripts?.build ?? '';\n if (build.includes('sync-content')) return;\n const sync = `node --env-file-if-exists=.env.local scripts/sync-content.ts${provider.exportIndexes ? ` ${output}` : ''}`;\n scripts(data, { build: build ? `${build} && ${sync}` : sync });\n });\n }\n\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
+ {"version":3,"file":"search-WfSdiGx-.js","names":[],"sources":["../src/features/search.ts"],"sourcesContent":["import path from 'node:path';\nimport type { Feature, FeatureContext } from '@/features';\nimport type { ReactFramework } from '@/project';\nimport { type FormattedRoute, formatRoute, routeModule } from '@/project/route';\nimport {\n addImport,\n addJsxAttribute,\n addTanstackPrerender,\n filterReactRouterPrerenderArray,\n filterReactRouterRoute,\n findJsxElement,\n} from '@/codemod';\nimport { docs } from './docs';\nimport {\n findSource,\n posix,\n reactFramework,\n registerReactRouterRoutes,\n scripts,\n type SourceRef,\n sourceRef,\n} from './utils';\n\n/** how client code reads public env variables */\ninterface Env {\n prefix: string;\n read: string;\n}\n\nconst publicEnv = (next: boolean): Env =>\n next\n ? { prefix: 'NEXT_PUBLIC_', read: 'process.env.NEXT_PUBLIC_' }\n : { prefix: 'VITE_', read: 'import.meta.env.VITE_' };\n\nconst dialogImports = `import {\n SearchDialog,\n SearchDialogClose,\n SearchDialogContent,\n SearchDialogFooter,\n SearchDialogHeader,\n SearchDialogIcon,\n SearchDialogInput,\n SearchDialogList,\n SearchDialogOverlay,\n type SharedProps,\n} from 'fumadocs-ui/components/dialog/search';\nimport { useI18n } from 'fumadocs-ui/contexts/i18n';`;\n\nconst dialogBody = (footer: string, url: string) => ` return (\n <SearchDialog search={search} onSearchChange={setSearch} isLoading={query.isLoading} {...props}>\n <SearchDialogOverlay />\n <SearchDialogContent>\n <SearchDialogHeader>\n <SearchDialogIcon />\n <SearchDialogInput />\n <SearchDialogClose />\n </SearchDialogHeader>\n <SearchDialogList items={query.data !== 'empty' ? query.data : null} />\n <SearchDialogFooter>\n <a\n href=\"${url}\"\n rel=\"noreferrer noopener\"\n className=\"ms-auto text-xs text-fd-muted-foreground\"\n >\n Search powered by ${footer}\n </a>\n </SearchDialogFooter>\n </SearchDialogContent>\n </SearchDialog>\n );\n}\n`;\n\n/** `structuredData` is a function on async collections (React Router & TanStack Start) */\nconst typesenseIndexes = (\n async: boolean,\n { ref, resolved }: SourceRef,\n) => `import { ${ref} } from '@/lib/source';\nimport type { DocumentRecord } from 'typesense-fumadocs-adapter';\n\nexport async function exportSearchIndexes() {\n const results: DocumentRecord[] = [];\n for (const page of ${resolved}.getPages()) {\n results.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 results;\n}\n`;\n\nconst syncScript = (imports: string, body: string) => `import * as fs from 'node:fs/promises';\n${imports}\n\nasync function main(filePath: string) {\n const content = await fs.readFile(filePath);\n const records = JSON.parse(content.toString()) as DocumentRecord[];\n${body}\n console.log(\\`search updated: \\${records.length} records\\`);\n}\n\n// the path of pre-rendered \\`static.json\\`\nconst filePath = process.argv[2];\nif (!filePath) throw new Error('missing the path of static.json');\nvoid main(filePath);\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?: (src: SourceRef) => {\n imports: string;\n /** expression producing the documents */\n documents: string;\n /** generated `lib/export-search-indexes.ts`, for providers without a `toDocuments()` */\n file?: (async: boolean) => string;\n };\n sync?: (env: Env, dir: string) => string;\n /** server-side search, replaces the default search route */\n searchRoute?: Record<ReactFramework, string>;\n}\n\nconst providers = {\n 'orama-cloud': {\n label: 'Orama Cloud',\n hint: 'signup needed',\n dependencies: { '@orama/core': null },\n publicEnv: ['ORAMA_DATASOURCE_ID', 'ORAMA_PROJECT_ID', 'ORAMA_API_KEY'],\n privateEnv: ['ORAMA_PRIVATE_API_KEY'],\n static: true,\n dialog: (env) => `'use client';\n${dialogImports}\nimport { useDocsSearch } from 'fumadocs-core/search/client';\nimport { OramaCloud } from '@orama/core';\n\nconst client = new OramaCloud({\n projectId: ${env.read}ORAMA_PROJECT_ID!,\n apiKey: ${env.read}ORAMA_API_KEY!,\n});\n\nexport default function CustomSearchDialog(props: SharedProps) {\n const { locale } = useI18n();\n const { search, setSearch, query } = useDocsSearch({\n type: 'orama-cloud',\n client,\n locale,\n });\n\n${dialogBody('Orama', 'https://orama.com')}`,\n exportIndexes: ({ ref }) => ({\n imports: `import { ${ref} } from '@/lib/source';\nimport { toDocuments } from 'fumadocs-core/search/orama-cloud';`,\n documents: `toDocuments(${ref})`,\n }),\n sync: (env) =>\n syncScript(\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\n await sync(orama, {\n index: process.env.${env.prefix}ORAMA_DATASOURCE_ID!,\n documents: records,\n });\n`,\n ),\n },\n algolia: {\n label: 'Algolia',\n hint: 'signup needed',\n dependencies: { algoliasearch: null },\n publicEnv: ['ALGOLIA_APP_ID', 'ALGOLIA_SEARCH_KEY'],\n privateEnv: ['ALGOLIA_WRITE_KEY'],\n static: true,\n dialog: (env) => `'use client';\n${dialogImports}\nimport { useDocsSearch } from 'fumadocs-core/search/client';\nimport { algoliaClient } from 'fumadocs-core/search/client/algolia';\nimport { liteClient } from 'algoliasearch/lite';\n\nconst algolia = liteClient(${env.read}ALGOLIA_APP_ID!, ${env.read}ALGOLIA_SEARCH_KEY!);\n\nexport default function CustomSearchDialog(props: SharedProps) {\n const { locale } = useI18n();\n const { search, setSearch, query } = useDocsSearch({\n client: algoliaClient({\n client: algolia,\n indexName: 'document',\n locale,\n }),\n });\n\n${dialogBody('Algolia', 'https://algolia.com')}`,\n exportIndexes: ({ ref }) => ({\n imports: `import { ${ref} } from '@/lib/source';\nimport { toDocuments } from 'fumadocs-core/search/algolia';`,\n documents: `toDocuments(${ref})`,\n }),\n sync: (env) =>\n syncScript(\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\n await sync(client, {\n indexName: 'document',\n documents: records,\n });\n`,\n ),\n },\n typesense: {\n label: 'Typesense',\n hint: 'self-hosted or cloud, community adapter',\n dependencies: { typesense: null, 'typesense-fumadocs-adapter': null },\n publicEnv: ['TYPESENSE_URL', 'TYPESENSE_SEARCH_KEY'],\n privateEnv: ['TYPESENSE_API_KEY'],\n static: true,\n dialog: (env) => `'use client';\n${dialogImports}\nimport { Client } from 'typesense';\nimport { useTypesenseSearch } from 'typesense-fumadocs-adapter/client';\n\nconst client = new Client({\n nodes: [{ url: ${env.read}TYPESENSE_URL! }],\n apiKey: ${env.read}TYPESENSE_SEARCH_KEY!,\n});\n\nexport default function CustomSearchDialog(props: SharedProps) {\n const { locale } = useI18n();\n const { search, setSearch, query } = useTypesenseSearch({\n typesenseCollectionName: 'docs',\n locale,\n client,\n });\n\n${dialogBody('Typesense', 'https://typesense.org')}`,\n exportIndexes: (src) => ({\n imports: \"import { exportSearchIndexes } from '@/lib/export-search-indexes';\",\n documents: 'exportSearchIndexes()',\n file: (async) => typesenseIndexes(async, src),\n }),\n sync: (env) =>\n syncScript(\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\n await sync(client, {\n typesenseCollectionName: 'docs',\n documents: records,\n });\n`,\n ),\n },\n mixedbread: {\n label: 'Mixedbread',\n hint: 'AI search, signup needed',\n dependencies: { '@mixedbread/sdk': null },\n publicEnv: [],\n privateEnv: ['MIXEDBREAD_API_KEY', 'MIXEDBREAD_STORE_ID'],\n static: false,\n dialog: () => `'use client';\n${dialogImports}\nimport { useDocsSearch } from 'fumadocs-core/search/client';\nimport { fetchClient } from 'fumadocs-core/search/client/fetch';\n\nexport default function CustomSearchDialog(props: SharedProps) {\n const { locale } = useI18n();\n const { search, setSearch, query } = useDocsSearch({\n client: fetchClient({\n api: '/api/search',\n locale,\n }),\n });\n\n${dialogBody('Mixedbread', 'https://mixedbread.com')}`,\n sync: (_env, dir) => `import { spawnSync } from 'node:child_process';\n\n// sync the content with Mixedbread CLI\nconst result = spawnSync(\n 'npx',\n ['--yes', '@mixedbread/cli', 'vs', 'sync', process.env.MIXEDBREAD_STORE_ID!, '${dir}', '--ci'],\n { stdio: 'inherit' },\n);\n\nprocess.exit(result.status ?? 1);\n`,\n searchRoute: (() => {\n const server = `import { createMixedbreadSearchAPI } from 'fumadocs-core/search/mixedbread';\nimport Mixedbread from '@mixedbread/sdk';\n\nconst server = createMixedbreadSearchAPI({\n client: new Mixedbread({\n apiKey: process.env.MIXEDBREAD_API_KEY,\n }),\n storeIdentifier: process.env.MIXEDBREAD_STORE_ID!,\n});\n`;\n return {\n next: `${server}\nexport const { GET } = server;\n`,\n 'react-router': `import type { Route } from './+types/search';\n${server}\nexport async function loader({ request }: Route.LoaderArgs) {\n return server.GET(request);\n}\n`,\n 'tanstack-start': `import { createFileRoute } from '@tanstack/react-router';\n${server}\nexport const Route = createFileRoute('/api/search')({\n server: {\n handlers: {\n GET: async ({ request }) => server.GET(request),\n },\n },\n});\n`,\n waku: `${server}\nexport const { GET } = server;\n`,\n };\n })(),\n },\n} satisfies Record<string, Provider>;\n\nexport type SearchProvider = keyof typeof providers;\n\nconst staticRoute = (\n framework: ReactFramework,\n route: FormattedRoute,\n imports: string,\n documents: string,\n) =>\n routeModule(framework, route, {\n imports,\n body: `Response.json(await ${documents})`,\n revalidate: true,\n static: true,\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 const env = publicEnv(framework === 'next');\n\n ctx.addDependencies(provider.dependencies);\n await ctx.write(path.join(baseDir, 'components/search.tsx'), provider.dialog(env));\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 { imports, documents, file } = provider.exportIndexes(\n sourceRef(project.source.dynamic),\n );\n if (file)\n await ctx.write(\n path.join(baseDir, 'lib/export-search-indexes.ts'),\n file(project.source.async),\n );\n\n const route = formatRoute({ segments: ['static.json'] }, framework, null);\n await ctx.write(\n path.join(baseDir, route.file),\n staticRoute(framework, route, imports, documents),\n );\n\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 // path of the pre-rendered `static.json`, passed to the sync script\n const output = {\n next: '.next/server/app/static.json.body',\n 'react-router': 'build/client/static.json',\n 'tanstack-start': project.static ? 'dist/client/static.json' : '.output/public/static.json',\n waku: 'dist/public/static.json',\n }[framework];\n await ctx.write('scripts/sync-content.ts', provider.sync(env, project.source.dir));\n await ctx.packageJson((data) => {\n const build = data.scripts?.build ?? '';\n if (build.includes('sync-content')) return;\n const sync = `node --env-file-if-exists=.env.local scripts/sync-content.ts${provider.exportIndexes ? ` ${output}` : ''}`;\n scripts(data, { build: build ? `${build} && ${sync}` : sync });\n });\n }\n\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,5 +1,5 @@
1
- import { a as Project, o as ReactFramework } from "./index-BCVGQxn9.js";
2
- import { i as FeatureContext, s as PackageJson } from "./index-CR9T40ox.js";
1
+ import { a as Project, o as ReactFramework } from "./index-DI66DoV8.js";
2
+ import { i as FeatureContext, s as PackageJson } from "./index-Cs7hw2un.js";
3
3
  //#region src/project/route.d.ts
4
4
  interface FormattedRoute {
5
5
  /** route file, relative to the base dir */
@@ -40,4 +40,4 @@ interface SourceRef {
40
40
  declare function sourceRef(dynamic: boolean): SourceRef;
41
41
  //#endregion
42
42
  export { posix as a, registerReactRouterRoutes as c, sharedRoute as d, sourceRef as f, findSource as i, requiresMarkdown as l, addExport as n, reactFramework as o, FormattedRoute as p, addPageUrl as r, reactOnly as s, SourceRef as t, scripts as u };
43
- //# sourceMappingURL=utils-B1Xy4l_a.d.ts.map
43
+ //# sourceMappingURL=utils-CxJ7kw39.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"utils-B1Xy4l_a.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
+ {"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,6 +1,6 @@
1
1
  import { b as findJsxElement, h as appendJsxChildren, p as addImport } from "./codemod-auol-p4R.js";
2
2
  import { a as reactFramework, c as requiresMarkdown, r as findSource } from "./utils-DbMe7h6c.js";
3
- import { r as llms } from "./llms-CONd8Vnn.js";
3
+ import { r as llms } from "./llms-Br0RJXXg.js";
4
4
  import path from "node:path";
5
5
  //#region src/features/webmcp.ts
6
6
  const component = ({ static: isStatic, i18n, tanstack }) => `'use client';
@@ -132,4 +132,4 @@ const webmcp = {
132
132
  //#endregion
133
133
  export { webmcp as n, component as t };
134
134
 
135
- //# sourceMappingURL=webmcp-C2PM_9md.js.map
135
+ //# sourceMappingURL=webmcp-DyTLrsXL.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"webmcp-C2PM_9md.js","names":[],"sources":["../src/features/webmcp.ts"],"sourcesContent":["import path from 'node:path';\nimport type { Feature } from '@/features';\nimport { addImport, appendJsxChildren, findJsxElement } from '@/codemod';\nimport { llms } from './llms';\nimport { findSource, reactFramework, requiresMarkdown } from './utils';\n\ninterface Input {\n static: boolean;\n i18n: boolean;\n /** the Markdown route of TanStack Start is `/docs/*.md` */\n tanstack: boolean;\n}\n\nexport const component = ({ static: isStatic, i18n, tanstack }: Input) => `'use client';\nimport { useEffect } from 'react';\nimport { useRouter } from 'fumadocs-core/framework';\nimport { ${isStatic ? 'staticClient' : 'fetchClient'} } from 'fumadocs-core/search/client/${isStatic ? 'orama-static' : 'fetch'}';\n${i18n ? \"import { useI18n } from 'fumadocs-ui/contexts/i18n';\\n\" : ''}import { ${tanstack ? 'docsRoute' : 'docsContentRoute, docsRoute'} } from '@/lib/shared';\n\ninterface ModelContextTool<Input> {\n name: string;\n description: string;\n inputSchema?: object;\n execute: (input: Input) => Promise<unknown>;\n}\n\ndeclare global {\n interface Document {\n /** WebMCP, see https://webmachinelearning.github.io/webmcp/ */\n modelContext?: {\n registerTool: <Input>(\n tool: ModelContextTool<Input>,\n options?: { signal?: AbortSignal },\n ) => Promise<void>;\n };\n }\n}\n\n/** the Markdown URL of a page, e.g. ${tanstack ? '`/docs/page` -> `/docs/page.md`' : '`/docs/page` -> `/llms.mdx/docs/page/content.md`'} */\nfunction markdownUrl(url: string) {\n${\n tanstack\n ? ' return url.endsWith(docsRoute) ? `${url}/index.md` : `${url}.md`;'\n : ` const start = url.indexOf(docsRoute);\n const slugs = url.slice(start + docsRoute.length).replace(/^\\\\//, '');\n return [url.slice(0, start) + docsContentRoute, slugs, 'content.md'].filter(Boolean).join('/');`\n}\n}\n\n/** expose the docs to AI agents in the browser */\nexport function WebMCP() {\n const router = useRouter();\n${i18n ? ' const { locale } = useI18n();\\n' : ''}\n useEffect(() => {\n const context = document.modelContext;\n if (!context) return;\n const controller = new AbortController();\n const options = { signal: controller.signal };\n const client = ${isStatic ? 'staticClient' : 'fetchClient'}(${i18n ? '{ locale }' : ''});\n\n void context.registerTool(\n {\n name: 'search_docs',\n description: 'Search the docs, returns matching pages and headings with their URLs',\n inputSchema: {\n type: 'object',\n properties: { query: { type: 'string' } },\n required: ['query'],\n },\n async execute({ query }: { query: string }) {\n return client.search(query);\n },\n },\n options,\n );\n void context.registerTool(\n {\n name: 'read_page',\n description: 'Read a docs page as Markdown by its URL',\n inputSchema: {\n type: 'object',\n properties: { url: { type: 'string' } },\n required: ['url'],\n },\n async execute({ url }: { url: string }) {\n const res = await fetch(markdownUrl(url));\n if (!res.ok) throw new Error(\\`page not found: \\${url}\\`);\n return res.text();\n },\n },\n options,\n );\n void context.registerTool(\n {\n name: 'open_page',\n description: 'Navigate to a docs page by its URL',\n inputSchema: {\n type: 'object',\n properties: { url: { type: 'string' } },\n required: ['url'],\n },\n async execute({ url }: { url: string }) {\n await router.push(url);\n return \\`opened \\${url}\\`;\n },\n },\n options,\n );\n\n return () => controller.abort();\n }, [router${i18n ? ', locale' : ''}]);\n\n return null;\n}\n`;\n\nexport const webmcp: Feature = {\n id: 'webmcp',\n title: 'WebMCP',\n description:\n 'expose tools to AI agents in the browser (experimental): search, read and open docs pages',\n requires: [llms],\n supports: requiresMarkdown,\n async apply(ctx) {\n const { cwd, baseDir, i18n } = ctx.project;\n const framework = reactFramework(ctx.project);\n await ctx.write(\n path.join(baseDir, 'components/webmcp.tsx'),\n component({\n static: ctx.project.static,\n i18n: i18n !== null,\n tanstack: framework === 'tanstack-start',\n }),\n );\n\n const provider = await findSource(cwd, baseDir, '<RootProvider');\n const edited =\n provider !== undefined &&\n (await ctx.source(provider, (file) => {\n if (file.code.includes('<WebMCP')) return;\n const element = findJsxElement(file, 'RootProvider');\n if (!element) return;\n appendJsxChildren(file, element, '<WebMCP />');\n addImport(file, { from: '@/components/webmcp', named: ['WebMCP'] });\n }));\n if (!edited)\n ctx.note('Render `<WebMCP />` from `@/components/webmcp` inside `<RootProvider />`.');\n\n ctx.note(\n 'WebMCP is experimental, test it with the `#enable-webmcp-testing` flag of Chrome. The `search_docs` tool uses the search API route, see https://fumadocs.dev/docs/integrations/llms#webmcp.',\n );\n },\n};\n"],"mappings":";;;;;AAaA,MAAa,aAAa,EAAE,QAAQ,UAAU,MAAM,eAAsB;;;WAG/D,WAAW,iBAAiB,cAAc,uCAAuC,WAAW,iBAAiB,QAAQ;EAC9H,OAAO,2DAA2D,GAAG,WAAW,WAAW,cAAc,8BAA8B;;;;;;;;;;;;;;;;;;;;;uCAqBlG,WAAW,oCAAoC,mDAAmD;;EAGvI,WACI,wEACA;;mGAGL;;;;;;EAMC,OAAO,sCAAsC,GAAG;;;;;;qBAM7B,WAAW,iBAAiB,cAAc,GAAG,OAAO,eAAe,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;cAoD7E,OAAO,aAAa,GAAG;;;;;AAMrC,MAAa,SAAkB;CAC7B,IAAI;CACJ,OAAO;CACP,aACE;CACF,UAAU,CAAC,IAAI;CACf,UAAU;CACV,MAAM,MAAM,KAAK;EACf,MAAM,EAAE,KAAK,SAAS,SAAS,IAAI;EACnC,MAAM,YAAY,eAAe,IAAI,OAAO;EAC5C,MAAM,IAAI,MACR,KAAK,KAAK,SAAS,uBAAuB,GAC1C,UAAU;GACR,QAAQ,IAAI,QAAQ;GACpB,MAAM,SAAS;GACf,UAAU,cAAc;EAC1B,CAAC,CACH;EAEA,MAAM,WAAW,MAAM,WAAW,KAAK,SAAS,eAAe;EAU/D,IAAI,EARF,aAAa,KAAA,KACZ,MAAM,IAAI,OAAO,WAAW,SAAS;GACpC,IAAI,KAAK,KAAK,SAAS,SAAS,GAAG;GACnC,MAAM,UAAU,eAAe,MAAM,cAAc;GACnD,IAAI,CAAC,SAAS;GACd,kBAAkB,MAAM,SAAS,YAAY;GAC7C,UAAU,MAAM;IAAE,MAAM;IAAuB,OAAO,CAAC,QAAQ;GAAE,CAAC;EACpE,CAAC,IAED,IAAI,KAAK,2EAA2E;EAEtF,IAAI,KACF,6LACF;CACF;AACF"}
1
+ {"version":3,"file":"webmcp-DyTLrsXL.js","names":[],"sources":["../src/features/webmcp.ts"],"sourcesContent":["import path from 'node:path';\nimport type { Feature } from '@/features';\nimport { addImport, appendJsxChildren, findJsxElement } from '@/codemod';\nimport { llms } from './llms';\nimport { findSource, reactFramework, requiresMarkdown } from './utils';\n\ninterface Input {\n static: boolean;\n i18n: boolean;\n /** the Markdown route of TanStack Start is `/docs/*.md` */\n tanstack: boolean;\n}\n\nexport const component = ({ static: isStatic, i18n, tanstack }: Input) => `'use client';\nimport { useEffect } from 'react';\nimport { useRouter } from 'fumadocs-core/framework';\nimport { ${isStatic ? 'staticClient' : 'fetchClient'} } from 'fumadocs-core/search/client/${isStatic ? 'orama-static' : 'fetch'}';\n${i18n ? \"import { useI18n } from 'fumadocs-ui/contexts/i18n';\\n\" : ''}import { ${tanstack ? 'docsRoute' : 'docsContentRoute, docsRoute'} } from '@/lib/shared';\n\ninterface ModelContextTool<Input> {\n name: string;\n description: string;\n inputSchema?: object;\n execute: (input: Input) => Promise<unknown>;\n}\n\ndeclare global {\n interface Document {\n /** WebMCP, see https://webmachinelearning.github.io/webmcp/ */\n modelContext?: {\n registerTool: <Input>(\n tool: ModelContextTool<Input>,\n options?: { signal?: AbortSignal },\n ) => Promise<void>;\n };\n }\n}\n\n/** the Markdown URL of a page, e.g. ${tanstack ? '`/docs/page` -> `/docs/page.md`' : '`/docs/page` -> `/llms.mdx/docs/page/content.md`'} */\nfunction markdownUrl(url: string) {\n${\n tanstack\n ? ' return url.endsWith(docsRoute) ? `${url}/index.md` : `${url}.md`;'\n : ` const start = url.indexOf(docsRoute);\n const slugs = url.slice(start + docsRoute.length).replace(/^\\\\//, '');\n return [url.slice(0, start) + docsContentRoute, slugs, 'content.md'].filter(Boolean).join('/');`\n}\n}\n\n/** expose the docs to AI agents in the browser */\nexport function WebMCP() {\n const router = useRouter();\n${i18n ? ' const { locale } = useI18n();\\n' : ''}\n useEffect(() => {\n const context = document.modelContext;\n if (!context) return;\n const controller = new AbortController();\n const options = { signal: controller.signal };\n const client = ${isStatic ? 'staticClient' : 'fetchClient'}(${i18n ? '{ locale }' : ''});\n\n void context.registerTool(\n {\n name: 'search_docs',\n description: 'Search the docs, returns matching pages and headings with their URLs',\n inputSchema: {\n type: 'object',\n properties: { query: { type: 'string' } },\n required: ['query'],\n },\n async execute({ query }: { query: string }) {\n return client.search(query);\n },\n },\n options,\n );\n void context.registerTool(\n {\n name: 'read_page',\n description: 'Read a docs page as Markdown by its URL',\n inputSchema: {\n type: 'object',\n properties: { url: { type: 'string' } },\n required: ['url'],\n },\n async execute({ url }: { url: string }) {\n const res = await fetch(markdownUrl(url));\n if (!res.ok) throw new Error(\\`page not found: \\${url}\\`);\n return res.text();\n },\n },\n options,\n );\n void context.registerTool(\n {\n name: 'open_page',\n description: 'Navigate to a docs page by its URL',\n inputSchema: {\n type: 'object',\n properties: { url: { type: 'string' } },\n required: ['url'],\n },\n async execute({ url }: { url: string }) {\n await router.push(url);\n return \\`opened \\${url}\\`;\n },\n },\n options,\n );\n\n return () => controller.abort();\n }, [router${i18n ? ', locale' : ''}]);\n\n return null;\n}\n`;\n\nexport const webmcp: Feature = {\n id: 'webmcp',\n title: 'WebMCP',\n description:\n 'expose tools to AI agents in the browser (experimental): search, read and open docs pages',\n requires: [llms],\n supports: requiresMarkdown,\n async apply(ctx) {\n const { cwd, baseDir, i18n } = ctx.project;\n const framework = reactFramework(ctx.project);\n await ctx.write(\n path.join(baseDir, 'components/webmcp.tsx'),\n component({\n static: ctx.project.static,\n i18n: i18n !== null,\n tanstack: framework === 'tanstack-start',\n }),\n );\n\n const provider = await findSource(cwd, baseDir, '<RootProvider');\n const edited =\n provider !== undefined &&\n (await ctx.source(provider, (file) => {\n if (file.code.includes('<WebMCP')) return;\n const element = findJsxElement(file, 'RootProvider');\n if (!element) return;\n appendJsxChildren(file, element, '<WebMCP />');\n addImport(file, { from: '@/components/webmcp', named: ['WebMCP'] });\n }));\n if (!edited)\n ctx.note('Render `<WebMCP />` from `@/components/webmcp` inside `<RootProvider />`.');\n\n ctx.note(\n 'WebMCP is experimental, test it with the `#enable-webmcp-testing` flag of Chrome. The `search_docs` tool uses the search API route, see https://fumadocs.dev/docs/integrations/llms#webmcp.',\n );\n },\n};\n"],"mappings":";;;;;AAaA,MAAa,aAAa,EAAE,QAAQ,UAAU,MAAM,eAAsB;;;WAG/D,WAAW,iBAAiB,cAAc,uCAAuC,WAAW,iBAAiB,QAAQ;EAC9H,OAAO,2DAA2D,GAAG,WAAW,WAAW,cAAc,8BAA8B;;;;;;;;;;;;;;;;;;;;;uCAqBlG,WAAW,oCAAoC,mDAAmD;;EAGvI,WACI,wEACA;;mGAGL;;;;;;EAMC,OAAO,sCAAsC,GAAG;;;;;;qBAM7B,WAAW,iBAAiB,cAAc,GAAG,OAAO,eAAe,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;cAoD7E,OAAO,aAAa,GAAG;;;;;AAMrC,MAAa,SAAkB;CAC7B,IAAI;CACJ,OAAO;CACP,aACE;CACF,UAAU,CAAC,IAAI;CACf,UAAU;CACV,MAAM,MAAM,KAAK;EACf,MAAM,EAAE,KAAK,SAAS,SAAS,IAAI;EACnC,MAAM,YAAY,eAAe,IAAI,OAAO;EAC5C,MAAM,IAAI,MACR,KAAK,KAAK,SAAS,uBAAuB,GAC1C,UAAU;GACR,QAAQ,IAAI,QAAQ;GACpB,MAAM,SAAS;GACf,UAAU,cAAc;EAC1B,CAAC,CACH;EAEA,MAAM,WAAW,MAAM,WAAW,KAAK,SAAS,eAAe;EAU/D,IAAI,EARF,aAAa,KAAA,KACZ,MAAM,IAAI,OAAO,WAAW,SAAS;GACpC,IAAI,KAAK,KAAK,SAAS,SAAS,GAAG;GACnC,MAAM,UAAU,eAAe,MAAM,cAAc;GACnD,IAAI,CAAC,SAAS;GACd,kBAAkB,MAAM,SAAS,YAAY;GAC7C,UAAU,MAAM;IAAE,MAAM;IAAuB,OAAO,CAAC,QAAQ;GAAE,CAAC;EACpE,CAAC,IAED,IAAI,KAAK,2EAA2E;EAEtF,IAAI,KACF,6LACF;CACF;AACF"}