@fumadocs/cli 1.5.0 → 1.6.1

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 (99) hide show
  1. package/dist/ai-C19ZOP53.js +100 -0
  2. package/dist/ai-C19ZOP53.js.map +1 -0
  3. package/dist/all-BBf7I8hG.js +27 -0
  4. package/dist/all-BBf7I8hG.js.map +1 -0
  5. package/dist/codemod/index.d.ts +2 -0
  6. package/dist/codemod/index.js +2 -0
  7. package/dist/codemod-auol-p4R.js +404 -0
  8. package/dist/codemod-auol-p4R.js.map +1 -0
  9. package/dist/config-Bfg4yG8S.d.ts +54 -0
  10. package/dist/config-Bfg4yG8S.d.ts.map +1 -0
  11. package/dist/config-Bw0kmF01.js +97 -0
  12. package/dist/config-Bw0kmF01.js.map +1 -0
  13. package/dist/config.d.ts +2 -52
  14. package/dist/config.js +1 -72
  15. package/dist/docs--WqEHTyl.js +738 -0
  16. package/dist/docs--WqEHTyl.js.map +1 -0
  17. package/dist/epub-jc_2B886.js +52 -0
  18. package/dist/epub-jc_2B886.js.map +1 -0
  19. package/dist/features/ai.d.ts +25 -0
  20. package/dist/features/ai.d.ts.map +1 -0
  21. package/dist/features/ai.js +2 -0
  22. package/dist/features/all.d.ts +5 -0
  23. package/dist/features/all.d.ts.map +1 -0
  24. package/dist/features/all.js +2 -0
  25. package/dist/features/docs/index.d.ts +10 -0
  26. package/dist/features/docs/index.d.ts.map +1 -0
  27. package/dist/features/docs/index.js +2 -0
  28. package/dist/features/epub.d.ts +5 -0
  29. package/dist/features/epub.d.ts.map +1 -0
  30. package/dist/features/epub.js +2 -0
  31. package/dist/features/feedback.d.ts +5 -0
  32. package/dist/features/feedback.d.ts.map +1 -0
  33. package/dist/features/feedback.js +2 -0
  34. package/dist/features/index.d.ts +2 -0
  35. package/dist/features/index.js +2 -0
  36. package/dist/features/lint.d.ts +7 -0
  37. package/dist/features/lint.d.ts.map +1 -0
  38. package/dist/features/lint.js +2 -0
  39. package/dist/features/llms.d.ts +27 -0
  40. package/dist/features/llms.d.ts.map +1 -0
  41. package/dist/features/llms.js +2 -0
  42. package/dist/features/mcp.d.ts +9 -0
  43. package/dist/features/mcp.d.ts.map +1 -0
  44. package/dist/features/mcp.js +2 -0
  45. package/dist/features/og.d.ts +8 -0
  46. package/dist/features/og.d.ts.map +1 -0
  47. package/dist/features/og.js +2 -0
  48. package/dist/features/search.d.ts +84 -0
  49. package/dist/features/search.d.ts.map +1 -0
  50. package/dist/features/search.js +2 -0
  51. package/dist/features/utils.d.ts +2 -0
  52. package/dist/features/utils.js +2 -0
  53. package/dist/features/webmcp.d.ts +12 -0
  54. package/dist/features/webmcp.d.ts.map +1 -0
  55. package/dist/features/webmcp.js +2 -0
  56. package/dist/features-CXFCgOWD.js +132 -0
  57. package/dist/features-CXFCgOWD.js.map +1 -0
  58. package/dist/feedback-Brj092vC.js +37 -0
  59. package/dist/feedback-Brj092vC.js.map +1 -0
  60. package/dist/fs-D6J9dOEx.js +14 -0
  61. package/dist/fs-D6J9dOEx.js.map +1 -0
  62. package/dist/index-Ck0EPdgu.d.ts +126 -0
  63. package/dist/index-Ck0EPdgu.d.ts.map +1 -0
  64. package/dist/index-Cs7hw2un.d.ts +76 -0
  65. package/dist/index-Cs7hw2un.d.ts.map +1 -0
  66. package/dist/index-DI66DoV8.d.ts +64 -0
  67. package/dist/index-DI66DoV8.d.ts.map +1 -0
  68. package/dist/index.js +155 -139
  69. package/dist/index.js.map +1 -1
  70. package/dist/{installer-Ci3Hf9it.js → installer-CzhVfs7i.js} +81 -8
  71. package/dist/installer-CzhVfs7i.js.map +1 -0
  72. package/dist/lint-zkXUULgO.js +137 -0
  73. package/dist/lint-zkXUULgO.js.map +1 -0
  74. package/dist/llms-Br0RJXXg.js +228 -0
  75. package/dist/llms-Br0RJXXg.js.map +1 -0
  76. package/dist/mcp-Ce_W93we.js +67 -0
  77. package/dist/mcp-Ce_W93we.js.map +1 -0
  78. package/dist/og-LFlX1Bl4.js +142 -0
  79. package/dist/og-LFlX1Bl4.js.map +1 -0
  80. package/dist/project/index.d.ts +2 -0
  81. package/dist/project/index.js +2 -0
  82. package/dist/project-DwRVTm4p.js +110 -0
  83. package/dist/project-DwRVTm4p.js.map +1 -0
  84. package/dist/registry/installer.d.ts +4 -5
  85. package/dist/registry/installer.d.ts.map +1 -1
  86. package/dist/registry/installer.js +1 -1
  87. package/dist/schema.json +1 -1
  88. package/dist/search-WfSdiGx-.js +381 -0
  89. package/dist/search-WfSdiGx-.js.map +1 -0
  90. package/dist/utils-CxJ7kw39.d.ts +43 -0
  91. package/dist/utils-CxJ7kw39.d.ts.map +1 -0
  92. package/dist/utils-DbMe7h6c.js +98 -0
  93. package/dist/utils-DbMe7h6c.js.map +1 -0
  94. package/dist/webmcp-DyTLrsXL.js +135 -0
  95. package/dist/webmcp-DyTLrsXL.js.map +1 -0
  96. package/package.json +28 -7
  97. package/dist/config.d.ts.map +0 -1
  98. package/dist/config.js.map +0 -1
  99. package/dist/installer-Ci3Hf9it.js.map +0 -1
@@ -0,0 +1,738 @@
1
+ import { b as findJsxElement, d as wrapNextConfig, l as addVitePlugin, m as addJsxAttribute, p as addImport } from "./codemod-auol-p4R.js";
2
+ import { t as exists } from "./fs-D6J9dOEx.js";
3
+ import { a as reactFramework, o as reactOnly, s as registerReactRouterRoutes } from "./utils-DbMe7h6c.js";
4
+ import fs from "node:fs/promises";
5
+ import path from "node:path";
6
+ //#region src/project/route.ts
7
+ const conventions = {
8
+ next: {
9
+ locale: () => "[lang]",
10
+ param: (name, catchAll, optional) => catchAll ? optional ? `[[...${name}]]` : `[...${name}]` : `[${name}]`,
11
+ file: (segments, ext) => `app/${segments.join("/")}/route.${ext}`,
12
+ path: (segments) => `/${segments.join("/")}`
13
+ },
14
+ "react-router": {
15
+ locale: (optional) => optional ? ":lang?" : ":lang",
16
+ param: (name, catchAll) => catchAll ? "*" : `:${name}`,
17
+ file: (segments, ext) => `routes/${segments.filter((segment) => !/^[:*]/.test(segment)).join(".")}.${ext}`,
18
+ path: (segments) => segments.join("/")
19
+ },
20
+ "tanstack-start": {
21
+ locale: (optional) => optional ? "{-$lang}" : "$lang",
22
+ param: (name, catchAll) => catchAll ? "$" : `$${name}`,
23
+ file: (segments, ext) => `routes/${segments.join("/").replaceAll(".", "[.]")}.${ext}`,
24
+ path: (segments) => `/${segments.join("/")}`
25
+ },
26
+ waku: {
27
+ locale: () => "[lang]",
28
+ param: (name, catchAll, _optional, suffix) => catchAll ? `[...${name}]${suffix ? `/${suffix}` : ""}` : `[${name}]`,
29
+ file: (segments, ext) => `pages/_api/${segments.join("/")}.${ext}`,
30
+ path: (segments) => `/${segments.join("/")}`
31
+ }
32
+ };
33
+ /** the locale segment of route files, with a trailing slash */
34
+ function localeSegment(framework, i18n) {
35
+ return i18n ? `${conventions[framework].locale(i18n.optionalLocale)}/` : "";
36
+ }
37
+ function formatRoute(route, framework, i18n, ext = "ts") {
38
+ const convention = conventions[framework];
39
+ const segments = [];
40
+ const patterns = [];
41
+ if (route.locale && i18n) {
42
+ segments.push(convention.locale(i18n.optionalLocale));
43
+ patterns.push(":lang");
44
+ }
45
+ for (const segment of route.segments) if (typeof segment === "string") for (const part of segment.split("/")) {
46
+ if (part.length === 0) continue;
47
+ segments.push(part);
48
+ patterns.push(part);
49
+ }
50
+ else {
51
+ const { param, catchAll = false, optional = false, suffix } = segment;
52
+ segments.push(convention.param(param, catchAll, optional, suffix));
53
+ patterns.push(catchAll ? `:${param}*` : `:${param}`);
54
+ }
55
+ return {
56
+ file: convention.file(segments, ext),
57
+ path: convention.path(segments),
58
+ pattern: `/${patterns.join("/")}`
59
+ };
60
+ }
61
+ /** the route module of a handler that only reads the request, per framework */
62
+ function routeModule(framework, route, mod) {
63
+ const { imports, body, request, methods = ["GET"], revalidate, static: isStatic } = mod;
64
+ const arg = request ? "request: Request" : "";
65
+ const head = `${imports.trim()}\n\n`;
66
+ if (framework === "react-router") {
67
+ const out = [];
68
+ out.push(handler("loader", "Route.LoaderArgs"));
69
+ if (methods.length > 1) out.push(handler("action", "Route.ActionArgs"));
70
+ return `${request ? `${reactRouterTypes(route)}\n` : ""}${head}${out.join("\n")}`;
71
+ }
72
+ if (framework === "tanstack-start") {
73
+ const out = [];
74
+ for (const method of methods) out.push(` ${method}: async (${request ? "{ request }" : ""}) => ${body},`);
75
+ return `import { createFileRoute } from '@tanstack/react-router';
76
+ ${head}export const Route = createFileRoute('${route.path}')({
77
+ server: {
78
+ handlers: {
79
+ ${out.join("\n")}
80
+ },
81
+ },
82
+ });
83
+ `;
84
+ }
85
+ const out = [];
86
+ if (framework === "next" && revalidate) out.push("export const revalidate = false;\n");
87
+ for (const method of methods) out.push(`export async function ${method}(${arg}) {
88
+ return ${body};
89
+ }
90
+ `);
91
+ if (framework === "waku" && isStatic) out.push(`export async function getConfig() {
92
+ return {
93
+ render: 'static' as const,
94
+ } as const;
95
+ }
96
+ `);
97
+ return `${head}${out.join("\n")}`;
98
+ function handler(name, type) {
99
+ return `export async function ${name}(${request ? `{ request }: ${type}` : ""}) {
100
+ return ${body};
101
+ }
102
+ `;
103
+ }
104
+ }
105
+ /** import of the generated types of a React Router route module */
106
+ const reactRouterTypes = (route) => `import type { Route } from './+types/${route.file.split("/").pop().replace(/\.tsx?$/, "")}';`;
107
+ //#endregion
108
+ //#region src/features/docs/templates.ts
109
+ const wrapper = (children) => `<div className="flex flex-col min-h-screen">${children}</div>`;
110
+ const i18nConfig = (optionalLocale) => `import { defineI18n } from 'fumadocs-core/i18n';
111
+
112
+ export const i18n = defineI18n({
113
+ defaultLanguage: 'en',
114
+ languages: ['en', 'cn'],${optionalLocale ? "\n hideLocale: 'default-locale'," : ""}
115
+ });
116
+ `;
117
+ const sharedConstants = `export const docsRoute = '/docs';
118
+ `;
119
+ const source = (async, i18n) => `import { loader } from 'fumadocs-core/source';
120
+ import { defineDocs } from 'fumadocs-mdx/macro';
121
+ import { docsRoute } from './shared';
122
+ ${i18n ? "import { i18n } from '@/lib/i18n';\n" : ""}
123
+ export const docs = defineDocs({
124
+ dir: 'content/docs',${async ? "\n docs: {\n async: true,\n }," : ""}
125
+ });
126
+
127
+ export const source = loader({
128
+ baseUrl: docsRoute,
129
+ source: docs.toFumadocsSource(),${i18n ? "\n i18n," : ""}
130
+ });
131
+ `;
132
+ const layoutShared = (i18n) => i18n ? `import type { BaseLayoutProps } from 'fumadocs-ui/layouts/shared';
133
+ import { uiTranslations } from 'fumadocs-ui/i18n';
134
+ import { i18n } from '@/lib/i18n';
135
+
136
+ export const translations = i18n.translations().extend(uiTranslations());
137
+
138
+ export function baseOptions(locale: string = i18n.defaultLanguage): BaseLayoutProps {
139
+ return {
140
+ nav: {
141
+ title: 'My App',
142
+ url: \`/\${locale}\`,
143
+ },
144
+ };
145
+ }
146
+ ` : `import type { BaseLayoutProps } from 'fumadocs-ui/layouts/shared';
147
+
148
+ export function baseOptions(): BaseLayoutProps {
149
+ return {
150
+ nav: {
151
+ title: 'My App',
152
+ },
153
+ };
154
+ }
155
+ `;
156
+ const mdx = `import defaultMdxComponents from 'fumadocs-ui/mdx';
157
+ import type { MDXComponents } from 'mdx/types';
158
+
159
+ export function getMDXComponents(components?: MDXComponents) {
160
+ return {
161
+ ...defaultMdxComponents,
162
+ ...components,
163
+ } satisfies MDXComponents;
164
+ }
165
+
166
+ export const useMDXComponents = getMDXComponents;
167
+
168
+ declare global {
169
+ type MDXProvidedComponents = ReturnType<typeof getMDXComponents>;
170
+ }
171
+ `;
172
+ const search = (i18n) => `'use client';
173
+ import {
174
+ SearchDialog,
175
+ SearchDialogClose,
176
+ SearchDialogContent,
177
+ SearchDialogHeader,
178
+ SearchDialogIcon,
179
+ SearchDialogInput,
180
+ SearchDialogList,
181
+ SearchDialogOverlay,
182
+ type SharedProps,
183
+ } from 'fumadocs-ui/components/dialog/search';
184
+ import { useDocsSearch } from 'fumadocs-core/search/client';
185
+ import { staticClient } from 'fumadocs-core/search/client/orama-static';
186
+ ${i18n ? "import { useI18n } from 'fumadocs-ui/contexts/i18n';\n" : ""}
187
+ export default function DefaultSearchDialog(props: SharedProps) {
188
+ ${i18n ? " const { locale } = useI18n();\n" : ""} const { search, setSearch, query } = useDocsSearch({
189
+ client: staticClient(${i18n ? "{ locale }" : ""}),
190
+ });
191
+
192
+ return (
193
+ <SearchDialog search={search} onSearchChange={setSearch} isLoading={query.isLoading} {...props}>
194
+ <SearchDialogOverlay />
195
+ <SearchDialogContent>
196
+ <SearchDialogHeader>
197
+ <SearchDialogIcon />
198
+ <SearchDialogInput />
199
+ <SearchDialogClose />
200
+ </SearchDialogHeader>
201
+ <SearchDialogList items={query.data !== 'empty' ? query.data : null} />
202
+ </SearchDialogContent>
203
+ </SearchDialog>
204
+ );
205
+ }
206
+ `;
207
+ function shared(input, async) {
208
+ const i18n = input.i18n !== null;
209
+ return {
210
+ "lib/shared.ts": sharedConstants,
211
+ "lib/source.ts": source(async, i18n),
212
+ "lib/layout.shared.tsx": layoutShared(i18n),
213
+ "components/mdx.tsx": mdx,
214
+ ...input.static ? { "components/search.tsx": search(i18n) } : {},
215
+ ...input.i18n ? { "lib/i18n.ts": i18nConfig(input.i18n.optionalLocale) } : {}
216
+ };
217
+ }
218
+ const searchImport = (input) => input.static ? `import SearchDialog from '@/components/search';\n` : "";
219
+ /** imports of the root provider, with the i18n provider */
220
+ const providerImports = (input) => `import { RootProvider } from '${input.provider}';
221
+ ${searchImport(input)}${input.i18n ? `import { i18nProvider } from 'fumadocs-ui/i18n';
222
+ import { translations } from '@/lib/layout.shared';
223
+ import { i18n } from '@/lib/i18n';
224
+ ` : ""}`;
225
+ const providerProps = (input) => `${input.i18n ? " i18n={i18nProvider(translations, lang)}" : ""}${input.providerProps}`;
226
+ /** the docs page for React Router & TanStack Start, where MDX content is loaded lazily on client */
227
+ const clientContent = `function Content({ path }: { path: string }) {
228
+ const page = docs.getPage(path);
229
+ if (!page) throw new Error(\`unknown page: \${path}\`);
230
+
231
+ const { toc } = use(page.load());
232
+ const MDX = page.body;
233
+
234
+ return (
235
+ <DocsPage toc={toc}>
236
+ <DocsTitle>{page.title}</DocsTitle>
237
+ <DocsDescription>{page.description}</DocsDescription>
238
+ <DocsBody>
239
+ <MDX components={useMDXComponents()} />
240
+ </DocsBody>
241
+ </DocsPage>
242
+ );
243
+ }`;
244
+ const searchRoute = {
245
+ next: (input) => `import { source } from '@/lib/source';
246
+ import { createFromSource } from 'fumadocs-core/search/server';
247
+ ${input.static ? "\nexport const revalidate = false;\n" : ""}
248
+ export const { ${input.static ? "staticGET: GET" : "GET"} } = createFromSource(source);
249
+ `,
250
+ waku: `import { createFromSource } from 'fumadocs-core/search/server';
251
+ import { source } from '@/lib/source';
252
+
253
+ export const { GET } = createFromSource(source);
254
+ `
255
+ };
256
+ const templates = {
257
+ next: (input) => {
258
+ const i18n = input.i18n !== null;
259
+ const dir = i18n ? "app/(docs)/[lang]" : "app/(docs)";
260
+ const route = i18n ? "/[lang]" : "";
261
+ const lang = i18n ? ", params.lang" : "";
262
+ return {
263
+ ...shared(input, false),
264
+ [`${dir}/layout.tsx`]: input.static ? `'use client';
265
+ ${providerImports(input)}${i18n ? "import { use } from 'react';\n" : ""}
266
+ export default function Layout({ ${i18n ? "params, " : ""}children }: LayoutProps<'${route || "/"}'>) {
267
+ ${i18n ? " const { lang } = use(params);\n\n" : ""} return (
268
+ <RootProvider${providerProps(input)}>
269
+ ${wrapper("{children}")}
270
+ </RootProvider>
271
+ );
272
+ }
273
+ ` : `${providerImports(input)}
274
+ export default ${i18n ? "async " : ""}function Layout({ ${i18n ? "params, " : ""}children }: LayoutProps<'${route || "/"}'>) {
275
+ ${i18n ? " const { lang } = await params;\n\n" : ""} return (
276
+ <RootProvider${providerProps(input)}>
277
+ ${wrapper("{children}")}
278
+ </RootProvider>
279
+ );
280
+ }
281
+ `,
282
+ [`${dir}/docs/layout.tsx`]: `import { source } from '@/lib/source';
283
+ import { DocsLayout } from 'fumadocs-ui/layouts/docs';
284
+ import { baseOptions } from '@/lib/layout.shared';
285
+
286
+ export default ${i18n ? "async " : ""}function Layout({ ${i18n ? "params, " : ""}children }: LayoutProps<'${route}/docs'>) {
287
+ ${i18n ? " const { lang } = await params;\n\n" : ""} return (
288
+ <DocsLayout tree={source.getPageTree(${i18n ? "lang" : ""})} {...baseOptions(${i18n ? "lang" : ""})}>
289
+ {children}
290
+ </DocsLayout>
291
+ );
292
+ }
293
+ `,
294
+ [`${dir}/docs/[[...slug]]/page.tsx`]: `import { source } from '@/lib/source';
295
+ import { DocsBody, DocsDescription, DocsPage, DocsTitle } from 'fumadocs-ui/layouts/docs/page';
296
+ import { notFound } from 'next/navigation';
297
+ import { getMDXComponents } from '@/components/mdx';
298
+ import type { Metadata } from 'next';
299
+ import { createRelativeLink } from 'fumadocs-ui/mdx';
300
+
301
+ export default async function Page(props: PageProps<'${route}/docs/[[...slug]]'>) {
302
+ const params = await props.params;
303
+ const page = source.getPage(params.slug${lang});
304
+ if (!page) notFound();
305
+
306
+ const MDX = page.data.body;
307
+
308
+ return (
309
+ <DocsPage toc={page.data.toc} full={page.data.full}>
310
+ <DocsTitle>{page.data.title}</DocsTitle>
311
+ <DocsDescription>{page.data.description}</DocsDescription>
312
+ <DocsBody>
313
+ <MDX
314
+ components={getMDXComponents({
315
+ // this allows you to link to other pages with relative file paths
316
+ a: createRelativeLink(source, page),
317
+ })}
318
+ />
319
+ </DocsBody>
320
+ </DocsPage>
321
+ );
322
+ }
323
+
324
+ export async function generateStaticParams() {
325
+ return source.generateParams();
326
+ }
327
+
328
+ export async function generateMetadata(props: PageProps<'${route}/docs/[[...slug]]'>): Promise<Metadata> {
329
+ const params = await props.params;
330
+ const page = source.getPage(params.slug${lang});
331
+ if (!page) notFound();
332
+
333
+ return {
334
+ title: page.data.title,
335
+ description: page.data.description,
336
+ };
337
+ }
338
+ `,
339
+ "app/api/search/route.ts": searchRoute.next(input)
340
+ };
341
+ },
342
+ "react-router": (input) => {
343
+ const i18n = input.i18n !== null;
344
+ return {
345
+ ...shared(input, true),
346
+ "routes/docs/layout.tsx": `import { Outlet${i18n ? ", useParams" : ""} } from 'react-router';
347
+ ${providerImports(input)}
348
+ export default function Layout() {
349
+ ${i18n ? " const { lang = i18n.defaultLanguage } = useParams();\n\n" : ""} return (
350
+ <RootProvider${providerProps(input)}>
351
+ ${wrapper("\n <Outlet />\n ")}
352
+ </RootProvider>
353
+ );
354
+ }
355
+ `,
356
+ "routes/docs/page.tsx": `import type { Route } from './+types/page';
357
+ import { DocsLayout } from 'fumadocs-ui/layouts/docs';
358
+ import { DocsBody, DocsDescription, DocsPage, DocsTitle } from 'fumadocs-ui/layouts/docs/page';
359
+ import { docs, source } from '@/lib/source';
360
+ import { baseOptions } from '@/lib/layout.shared';
361
+ import { useFumadocsLoader } from 'fumadocs-core/source/client';
362
+ import { useMDXComponents } from '@/components/mdx';
363
+ import { use } from 'react';
364
+
365
+ export async function loader({ params }: Route.LoaderArgs) {
366
+ const slugs = params['*'].split('/').filter((v) => v.length > 0);
367
+ const page = source.getPage(slugs${i18n ? ", params.lang" : ""});
368
+ if (!page) throw new Response('Not found', { status: 404 });
369
+
370
+ return {
371
+ path: page.path,
372
+ url: page.url,
373
+ pageTree: await source.serializePageTree(source.getPageTree(${i18n ? "params.lang" : ""})),
374
+ };
375
+ }
376
+
377
+ ${clientContent.replace("<DocsPage toc={toc}>", "<DocsPage toc={toc}>\n <title>{page.title}</title>\n <meta name=\"description\" content={page.description} />")}
378
+
379
+ export default function Page({ loaderData${i18n ? ", params" : ""} }: Route.ComponentProps) {
380
+ const { path, pageTree } = useFumadocsLoader(loaderData);
381
+
382
+ return (
383
+ <DocsLayout {...baseOptions(${i18n ? "params.lang" : ""})} tree={pageTree}>
384
+ <Content path={path} />
385
+ </DocsLayout>
386
+ );
387
+ }
388
+ `,
389
+ "routes/docs/search.ts": `import type { Route } from './+types/search';
390
+ import { createFromSource } from 'fumadocs-core/search/server';
391
+ import { source } from '@/lib/source';
392
+
393
+ const server = createFromSource(source);
394
+
395
+ export async function loader(${input.static ? "" : "{ request }: Route.LoaderArgs"}) {
396
+ return server.${input.static ? "staticGET()" : "GET(request)"};
397
+ }
398
+ `
399
+ };
400
+ },
401
+ "tanstack-start": (input) => {
402
+ const seg = localeSegment("tanstack-start", input.i18n);
403
+ const i18n = input.i18n !== null;
404
+ return {
405
+ ...shared(input, true),
406
+ "routes/_docs.tsx": `import { createFileRoute, Outlet${i18n ? ", useParams" : ""} } from '@tanstack/react-router';
407
+ ${providerImports(input)}
408
+ export const Route = createFileRoute('/_docs')({
409
+ component: Layout,
410
+ });
411
+
412
+ function Layout() {
413
+ ${i18n ? " const { lang = i18n.defaultLanguage } = useParams({ strict: false });\n\n" : ""} return (
414
+ <RootProvider${providerProps(input)}>
415
+ ${wrapper("\n <Outlet />\n ")}
416
+ </RootProvider>
417
+ );
418
+ }
419
+ `,
420
+ [`routes/_docs/${seg}docs/$.tsx`]: `import { createFileRoute, notFound } from '@tanstack/react-router';
421
+ import { DocsLayout } from 'fumadocs-ui/layouts/docs';
422
+ import { createServerFn } from '@tanstack/react-start';
423
+ import { docs, source } from '@/lib/source';
424
+ import { DocsBody, DocsDescription, DocsPage, DocsTitle } from 'fumadocs-ui/layouts/docs/page';
425
+ import { baseOptions } from '@/lib/layout.shared';
426
+ import { useFumadocsLoader } from 'fumadocs-core/source/client';
427
+ import { Suspense, use } from 'react';
428
+ import { useMDXComponents } from '@/components/mdx';
429
+
430
+ export const Route = createFileRoute('/_docs/${seg}docs/$')({
431
+ component: Page,
432
+ loader: async ({ params }) => {
433
+ const data = await serverLoader({
434
+ data: {
435
+ slugs: params._splat?.split('/') ?? [],${i18n ? "\n lang: params.lang," : ""}
436
+ },
437
+ });
438
+ await docs.getPage(data.path)?.preload();
439
+ return data;
440
+ },
441
+ });
442
+
443
+ const serverLoader = createServerFn({
444
+ method: 'GET',
445
+ })
446
+ .validator((params: { slugs: string[]${i18n ? "; lang?: string" : ""} }) => params)
447
+ .handler(async ({ data: { slugs${i18n ? ", lang" : ""} } }) => {
448
+ const page = source.getPage(slugs${i18n ? ", lang" : ""});
449
+ if (!page) throw notFound();
450
+
451
+ return {
452
+ path: page.path,
453
+ pageTree: await source.serializePageTree(source.getPageTree(${i18n ? "lang" : ""})),
454
+ };
455
+ });
456
+
457
+ ${clientContent}
458
+
459
+ function Page() {
460
+ ${i18n ? " const { lang } = Route.useParams();\n" : ""} const data = useFumadocsLoader(Route.useLoaderData());
461
+
462
+ return (
463
+ <DocsLayout {...baseOptions(${i18n ? "lang" : ""})} tree={data.pageTree}>
464
+ <Suspense>
465
+ <Content path={data.path} />
466
+ </Suspense>
467
+ </DocsLayout>
468
+ );
469
+ }
470
+ `,
471
+ "routes/api/search.ts": `import { createFileRoute } from '@tanstack/react-router';
472
+ import { source } from '@/lib/source';
473
+ import { createFromSource } from 'fumadocs-core/search/server';
474
+
475
+ const server = createFromSource(source);
476
+
477
+ export const Route = createFileRoute('/api/search')({
478
+ server: {
479
+ handlers: {
480
+ GET: ${input.static ? "() => server.staticGET()" : "async ({ request }) => server.GET(request)"},
481
+ },
482
+ },
483
+ });
484
+ `
485
+ };
486
+ },
487
+ waku: (input) => {
488
+ const i18n = input.i18n !== null;
489
+ const page = `import { source } from '@/lib/source';
490
+ import type { PageProps } from 'waku/router';
491
+ import { unstable_notFound } from 'waku/router/server';
492
+ import { createRelativeLink } from 'fumadocs-ui/mdx';
493
+ import { DocsBody, DocsDescription, DocsPage, DocsTitle } from 'fumadocs-ui/layouts/docs/page';
494
+ import { getMDXComponents } from '@/components/mdx';
495
+ ${i18n ? "import { DocsLayout } from 'fumadocs-ui/layouts/docs';\nimport { baseOptions } from '@/lib/layout.shared';\n" : ""}
496
+ export default function Page({ slug${i18n ? ", lang" : ""} }: PageProps<'${i18n ? "/[lang]" : ""}/docs/[...slug]'>) {
497
+ const page = source.getPage(slug${i18n ? ", lang" : ""});
498
+ if (!page) unstable_notFound();
499
+
500
+ const MDX = page.data.body;
501
+ const content = (
502
+ <DocsPage toc={page.data.toc}>
503
+ <DocsTitle>{page.data.title}</DocsTitle>
504
+ <DocsDescription>{page.data.description}</DocsDescription>
505
+ <DocsBody>
506
+ <MDX
507
+ components={getMDXComponents({
508
+ // this allows you to link to other pages with relative file paths
509
+ a: createRelativeLink(source, page),
510
+ })}
511
+ />
512
+ </DocsBody>
513
+ </DocsPage>
514
+ );
515
+ ${i18n ? `
516
+ // layouts don't receive the locale, render the docs layout here
517
+ return (
518
+ <DocsLayout {...baseOptions(lang)} tree={source.getPageTree(lang)}>
519
+ {content}
520
+ </DocsLayout>
521
+ );` : `
522
+ return content;`}
523
+ }
524
+
525
+ export async function getConfig() {
526
+ return {
527
+ render: 'static',
528
+ staticPaths: source.generateParams().map((item) => ${i18n ? "[item.lang, ...item.slug]" : "item.slug"}),
529
+ } as const;
530
+ }
531
+ `;
532
+ return {
533
+ ...shared(input, false),
534
+ "pages/(docs)/_layout.tsx": i18n ? `import type { ReactNode } from 'react';
535
+ import { Provider } from '@/components/provider';
536
+
537
+ export default function Layout({ children }: { children: ReactNode }) {
538
+ return (
539
+ <Provider>
540
+ ${wrapper("{children}")}
541
+ </Provider>
542
+ );
543
+ }
544
+ ` : `import type { ReactNode } from 'react';
545
+ ${providerImports(input)}
546
+ export default function Layout({ children }: { children: ReactNode }) {
547
+ return (
548
+ <RootProvider${providerProps(input)}>
549
+ ${wrapper("{children}")}
550
+ </RootProvider>
551
+ );
552
+ }
553
+ `,
554
+ ...i18n ? {
555
+ "components/provider.tsx": `'use client';
556
+ import type { ReactNode } from 'react';
557
+ import { useRouter } from 'waku/router/client';
558
+ ${providerImports(input)}
559
+ export function Provider({ children }: { children: ReactNode }) {
560
+ const router = useRouter();
561
+ const lang = router.path.split('/')[1] || i18n.defaultLanguage;
562
+
563
+ return <RootProvider${providerProps(input)}>{children}</RootProvider>;
564
+ }
565
+ `,
566
+ "pages/(docs)/[lang]/docs/[...slug].tsx": page
567
+ } : {
568
+ "pages/(docs)/docs/_layout.tsx": `import type { ReactNode } from 'react';
569
+ import { DocsLayout } from 'fumadocs-ui/layouts/docs';
570
+ import { source } from '@/lib/source';
571
+ import { baseOptions } from '@/lib/layout.shared';
572
+
573
+ export default function Layout({ children }: { children: ReactNode }) {
574
+ return (
575
+ <DocsLayout {...baseOptions()} tree={source.getPageTree()}>
576
+ {children}
577
+ </DocsLayout>
578
+ );
579
+ }
580
+ `,
581
+ "pages/(docs)/docs/[...slug].tsx": page
582
+ },
583
+ "pages/_api/api/search.ts": searchRoute.waku
584
+ };
585
+ }
586
+ };
587
+ const nextProxy = `import { createI18nMiddleware } from 'fumadocs-core/i18n/middleware';
588
+ import { i18n } from '@/lib/i18n';
589
+
590
+ export default createI18nMiddleware(i18n);
591
+
592
+ export const config = {
593
+ // only the docs routes, add the ones of other features here
594
+ matcher: ['/docs/:path*', '/:lang/docs/:path*'],
595
+ };
596
+ `;
597
+ const sampleContent = `---
598
+ title: Hello World
599
+ description: Your first document
600
+ ---
601
+
602
+ Welcome to the docs! You can edit this page in \`content/docs/index.mdx\`.
603
+
604
+ ## Heading
605
+
606
+ <Cards>
607
+ <Card title="Learn more about Fumadocs" href="https://fumadocs.dev" />
608
+ </Cards>
609
+ `;
610
+ const sampleContentCn = `---
611
+ title: 你好,世界
612
+ description: 你的第一份文檔
613
+ ---
614
+
615
+ 歡迎來到文檔!你可以在 \`content/docs/index.cn.mdx\` 編輯這個頁面。
616
+ `;
617
+ //#endregion
618
+ //#region src/features/docs/index.ts
619
+ const cssImports = (preset) => [`@import 'fumadocs-ui/css/${preset}.css';`, `@import 'fumadocs-ui/css/preset.css';`];
620
+ const docs = {
621
+ id: "docs",
622
+ title: "Docs",
623
+ description: "set up Fumadocs on your app, in a dedicated route group",
624
+ supports: reactOnly,
625
+ options: { i18n: {
626
+ message: "Enable internationalization?",
627
+ initialValue: false
628
+ } },
629
+ async detect(project) {
630
+ return exists(path.join(project.cwd, project.baseDir, "lib/source.ts"));
631
+ },
632
+ async apply(ctx, options) {
633
+ const { project } = ctx;
634
+ const { baseDir, packageJson, config } = project;
635
+ const framework = reactFramework(project);
636
+ const i18n = project.i18n ?? (options.i18n ? { optionalLocale: framework !== "waku" } : null);
637
+ const providerProps = [];
638
+ if (project.static) providerProps.push("search={{ SearchDialog }}");
639
+ if ("next-themes" in (packageJson.dependencies ?? {})) providerProps.push("theme={{ enabled: false }}");
640
+ ctx.addDependencies({
641
+ "fumadocs-core": null,
642
+ "fumadocs-mdx": null,
643
+ "fumadocs-ui": config.uiLibrary === "base-ui" ? "npm:@fumadocs/base-ui" : null
644
+ });
645
+ ctx.addDependencies({ "@types/mdx": null }, true);
646
+ const files = templates[framework]({
647
+ static: project.static,
648
+ i18n,
649
+ provider: project.info.provider,
650
+ providerProps: providerProps.map((prop) => ` ${prop}`).join("")
651
+ });
652
+ for (const [file, content] of Object.entries(files)) await ctx.write(path.posix.join(baseDir, file), content);
653
+ await ctx.write("content/docs/index.mdx", sampleContent);
654
+ if (i18n) await ctx.write("content/docs/index.cn.mdx", sampleContentCn);
655
+ await ctx.append(".gitignore", [".source"]);
656
+ if (i18n && framework === "next") await configureProxy(ctx);
657
+ await configureBundler(ctx);
658
+ await configureCss(ctx);
659
+ await suppressHydrationWarning(ctx);
660
+ if (framework === "react-router") await configureRoutes(ctx, i18n);
661
+ if (!(await fs.readFile(path.join(project.cwd, "tsconfig.json"), "utf-8").catch(() => "")).includes("\"@/*\"")) ctx.note(`Generated files import from the \`@/*\` alias, add it to \`tsconfig.json\`:\n "paths": { "@/*": ["./${baseDir ? `${baseDir}/` : ""}*"] }`);
662
+ if (project.static && framework === "tanstack-start") ctx.note("In SPA mode, prerender the docs pages and search index, see https://fumadocs.dev/docs/manual-installation/tanstack-start.");
663
+ if (i18n && framework === "react-router") ctx.note("When prerendering, list the docs pages with `source.getPages().map((page) => page.url)` instead of globbing content files, see https://fumadocs.dev/docs/internationalization/react-router.");
664
+ if (i18n) ctx.note(`Languages are configured in \`lib/i18n.ts\`, translated content uses a locale suffix like \`index.cn.mdx\`.\nUI translations: https://fumadocs.dev/docs/internationalization#translations`);
665
+ ctx.note(`Start the dev server and open ${i18n && !i18n.optionalLocale ? "/en/docs" : "/docs"} to see your docs.`);
666
+ }
667
+ };
668
+ /** the Next.js proxy (middleware) file of the project, relative to cwd */
669
+ async function findNextProxy({ cwd, baseDir }) {
670
+ for (const file of ["proxy.ts", "middleware.ts"]) for (const dir of /* @__PURE__ */ new Set([baseDir, ""])) {
671
+ const target = path.join(dir, file);
672
+ if (await exists(path.join(cwd, target))) return target;
673
+ }
674
+ }
675
+ /** the i18n middleware redirects & rewrites docs URLs without locale */
676
+ async function configureProxy(ctx) {
677
+ const existing = await findNextProxy(ctx.project);
678
+ if (!existing) {
679
+ await ctx.write(path.join(ctx.project.baseDir, "proxy.ts"), nextProxy);
680
+ return;
681
+ }
682
+ ctx.note(`A proxy already exists at ${existing}, handle the docs locale there. Fumadocs provides \`createI18nMiddleware\` from 'fumadocs-core/i18n/middleware', see https://fumadocs.dev/docs/internationalization/next.`);
683
+ }
684
+ async function configureBundler(ctx) {
685
+ const { framework, configFile } = ctx.project;
686
+ if (!(configFile !== void 0 && await ctx.source(configFile, (file) => {
687
+ if (!(framework === "next" ? wrapNextConfig(file) : addVitePlugin(file, {
688
+ name: "fumadocsMdx",
689
+ from: "fumadocs-mdx/vite"
690
+ }, framework === "waku" ? ["vite", "plugins"] : ["plugins"]))) throw new Error(`cannot update ${configFile}`);
691
+ }).catch(() => false))) ctx.note(framework === "next" ? "Wrap your Next.js config with `createMDX()` from 'fumadocs-mdx/next', see https://fumadocs.dev/docs/mdx/next." : "Add `fumadocsMdx()` from 'fumadocs-mdx/vite' to the Vite plugins of your config file.");
692
+ }
693
+ /** resolve the global CSS file imported by the root file */
694
+ async function findCssEntry({ cwd, baseDir, info }) {
695
+ const rootFile = path.join(baseDir, info.rootFile);
696
+ const content = await fs.readFile(path.join(cwd, rootFile), "utf-8").catch(() => "");
697
+ const match = /import\s+(?:\w+\s+from\s+)?['"]([^'"]+\.css)(?:\?url)?['"]/.exec(content);
698
+ if (!match) return;
699
+ const specifier = match[1];
700
+ if (specifier.startsWith("@/")) return path.join(baseDir, specifier.slice(2));
701
+ if (specifier.startsWith(".")) return path.join(path.dirname(rootFile), specifier);
702
+ }
703
+ async function configureCss(ctx) {
704
+ const { cwd } = ctx.project;
705
+ const preset = await exists(path.join(cwd, "components.json")) ? "shadcn" : "neutral";
706
+ const entry = await findCssEntry(ctx.project);
707
+ if (entry && await exists(path.join(cwd, entry))) {
708
+ await ctx.append(entry, cssImports(preset));
709
+ return;
710
+ }
711
+ ctx.note(`Add the styles to your global CSS file (after \`@import 'tailwindcss'\`):\n ${cssImports(preset).join("\n ")}`);
712
+ }
713
+ /** `next-themes` sets the theme class on `<html>`, React warns about the mismatch without this attribute */
714
+ async function suppressHydrationWarning(ctx) {
715
+ const { baseDir, info } = ctx.project;
716
+ if (!await ctx.source(path.join(baseDir, info.rootFile), (file) => {
717
+ const html = findJsxElement(file, "html")?.openingElement;
718
+ if (!html || file.code.slice(html.start, html.end).includes("suppressHydrationWarning")) return;
719
+ addJsxAttribute(file, html, "suppressHydrationWarning");
720
+ })) ctx.note("Add `suppressHydrationWarning` to the `<html>` element of your root layout.");
721
+ }
722
+ async function configureRoutes(ctx, i18n) {
723
+ if (!await registerReactRouterRoutes(ctx, [`layout('routes/docs/layout.tsx', [route('${localeSegment("react-router", i18n)}docs/*', 'routes/docs/page.tsx')])`, {
724
+ path: "api/search",
725
+ file: "routes/docs/search.ts",
726
+ pattern: "/api/search"
727
+ }])) return;
728
+ await ctx.source(path.join(ctx.project.baseDir, "routes.ts"), (file) => {
729
+ addImport(file, {
730
+ from: "@react-router/dev/routes",
731
+ named: ["layout", "route"]
732
+ });
733
+ });
734
+ }
735
+ //#endregion
736
+ export { routeModule as a, reactRouterTypes as i, findNextProxy as n, formatRoute as r, docs as t };
737
+
738
+ //# sourceMappingURL=docs--WqEHTyl.js.map