@fumadocs/cli 1.4.1 → 1.6.0

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 (100) hide show
  1. package/dist/ai-BVuNjYXU.js +100 -0
  2. package/dist/ai-BVuNjYXU.js.map +1 -0
  3. package/dist/all-aUHxtoXL.js +27 -0
  4. package/dist/all-aUHxtoXL.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-CT2jj39D.d.ts +52 -0
  10. package/dist/config-CT2jj39D.d.ts.map +1 -0
  11. package/dist/config-D6uv73fs.js +94 -0
  12. package/dist/config-D6uv73fs.js.map +1 -0
  13. package/dist/config.d.ts +2 -52
  14. package/dist/config.js +1 -72
  15. package/dist/docs-DeIEl_Kt.js +738 -0
  16. package/dist/docs-DeIEl_Kt.js.map +1 -0
  17. package/dist/epub-CnQFJh09.js +52 -0
  18. package/dist/epub-CnQFJh09.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-C1kf2s8j.js +132 -0
  57. package/dist/features-C1kf2s8j.js.map +1 -0
  58. package/dist/feedback-COmWtc5C.js +37 -0
  59. package/dist/feedback-COmWtc5C.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-BCVGQxn9.d.ts +64 -0
  63. package/dist/index-BCVGQxn9.d.ts.map +1 -0
  64. package/dist/index-CR9T40ox.d.ts +76 -0
  65. package/dist/index-CR9T40ox.d.ts.map +1 -0
  66. package/dist/index-Ck0EPdgu.d.ts +126 -0
  67. package/dist/index-Ck0EPdgu.d.ts.map +1 -0
  68. package/dist/index.d.ts +1 -1
  69. package/dist/index.js +198 -124
  70. package/dist/index.js.map +1 -1
  71. package/dist/{installer-Chjj_R2z.js → installer-D0Obb2Md.js} +21 -8
  72. package/dist/installer-D0Obb2Md.js.map +1 -0
  73. package/dist/lint-zkXUULgO.js +137 -0
  74. package/dist/lint-zkXUULgO.js.map +1 -0
  75. package/dist/llms-CONd8Vnn.js +228 -0
  76. package/dist/llms-CONd8Vnn.js.map +1 -0
  77. package/dist/mcp-BT8BLZZ-.js +67 -0
  78. package/dist/mcp-BT8BLZZ-.js.map +1 -0
  79. package/dist/og-DRDOyQ_t.js +142 -0
  80. package/dist/og-DRDOyQ_t.js.map +1 -0
  81. package/dist/project/index.d.ts +2 -0
  82. package/dist/project/index.js +2 -0
  83. package/dist/project-DwRVTm4p.js +110 -0
  84. package/dist/project-DwRVTm4p.js.map +1 -0
  85. package/dist/registry/installer.d.ts +4 -5
  86. package/dist/registry/installer.d.ts.map +1 -1
  87. package/dist/registry/installer.js +1 -1
  88. package/dist/schema.json +1 -1
  89. package/dist/search-iphnSVVY.js +381 -0
  90. package/dist/search-iphnSVVY.js.map +1 -0
  91. package/dist/utils-B1Xy4l_a.d.ts +43 -0
  92. package/dist/utils-B1Xy4l_a.d.ts.map +1 -0
  93. package/dist/utils-DbMe7h6c.js +98 -0
  94. package/dist/utils-DbMe7h6c.js.map +1 -0
  95. package/dist/webmcp-C2PM_9md.js +135 -0
  96. package/dist/webmcp-C2PM_9md.js.map +1 -0
  97. package/package.json +27 -7
  98. package/dist/config.d.ts.map +0 -1
  99. package/dist/config.js.map +0 -1
  100. package/dist/installer-Chjj_R2z.js.map +0 -1
@@ -1,12 +1,11 @@
1
- import { LoadedConfig } from "../config.js";
2
- import { ComponentInstaller } from "fuma-cli/registry/installer";
1
+ import { r as LoadedConfig } from "../config-CT2jj39D.js";
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
5
- declare class FumadocsComponentInstaller extends ComponentInstaller {
5
+ export declare class FumadocsComponentInstaller extends ComponentInstaller {
6
6
  private interactive;
7
- constructor(connector: RegistryConnector, config: LoadedConfig, cwd?: string);
7
+ constructor(connector: RegistryConnector, config: LoadedConfig, cwd?: string, io?: Partial<IOInterface>);
8
8
  installInteractive(name: string, subRegistry?: string): Promise<void>;
9
9
  }
10
10
  //#endregion
11
- export { FumadocsComponentInstaller };
12
11
  //# sourceMappingURL=installer.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"installer.d.ts","names":[],"sources":["../../src/registry/installer.ts"],"mappings":";;;;cAQa,mCAAmC;UACtC;cAKI,WAAW,mBAAmB,QAAQ,cAAc;EAuC1D,mBAAmB,cAAc,uBAAuB"}
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,2 +1,2 @@
1
- import { t as FumadocsComponentInstaller } from "../installer-Chjj_R2z.js";
1
+ import { t as FumadocsComponentInstaller } from "../installer-D0Obb2Md.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/ui","type":"string"},"layoutDir":{"default":"./layouts","type":"string"},"cssDir":{"default":"./components","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"}}},"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"}}}}}
@@ -0,0 +1,381 @@
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";
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
+ import path from "node:path";
5
+ //#region src/features/search.ts
6
+ const publicEnv = (next) => next ? {
7
+ prefix: "NEXT_PUBLIC_",
8
+ read: "process.env.NEXT_PUBLIC_"
9
+ } : {
10
+ prefix: "VITE_",
11
+ read: "import.meta.env.VITE_"
12
+ };
13
+ const dialogImports = `import {
14
+ SearchDialog,
15
+ SearchDialogClose,
16
+ SearchDialogContent,
17
+ SearchDialogFooter,
18
+ SearchDialogHeader,
19
+ SearchDialogIcon,
20
+ SearchDialogInput,
21
+ SearchDialogList,
22
+ SearchDialogOverlay,
23
+ type SharedProps,
24
+ } from 'fumadocs-ui/components/dialog/search';
25
+ import { useI18n } from 'fumadocs-ui/contexts/i18n';`;
26
+ const dialogBody = (footer, url) => ` return (
27
+ <SearchDialog search={search} onSearchChange={setSearch} isLoading={query.isLoading} {...props}>
28
+ <SearchDialogOverlay />
29
+ <SearchDialogContent>
30
+ <SearchDialogHeader>
31
+ <SearchDialogIcon />
32
+ <SearchDialogInput />
33
+ <SearchDialogClose />
34
+ </SearchDialogHeader>
35
+ <SearchDialogList items={query.data !== 'empty' ? query.data : null} />
36
+ <SearchDialogFooter>
37
+ <a
38
+ href="${url}"
39
+ rel="noreferrer noopener"
40
+ className="ms-auto text-xs text-fd-muted-foreground"
41
+ >
42
+ Search powered by ${footer}
43
+ </a>
44
+ </SearchDialogFooter>
45
+ </SearchDialogContent>
46
+ </SearchDialog>
47
+ );
48
+ }
49
+ `;
50
+ /** `structuredData` is a function on async collections (React Router & TanStack Start) */
51
+ const typesenseIndexes = (async, { ref, resolved }) => `import { ${ref} } from '@/lib/source';
52
+ import type { DocumentRecord } from 'typesense-fumadocs-adapter';
53
+
54
+ export async function exportSearchIndexes() {
55
+ const results: DocumentRecord[] = [];
56
+ for (const page of ${resolved}.getPages()) {
57
+ results.push({
58
+ _id: page.url,
59
+ structured: ${async ? "await page.data.structuredData()" : "page.data.structuredData"},
60
+ url: page.url,
61
+ title: page.data.title,
62
+ description: page.data.description,
63
+ locale: page.locale,
64
+ });
65
+ }
66
+ return results;
67
+ }
68
+ `;
69
+ const syncScript = (imports, body) => `import * as fs from 'node:fs/promises';
70
+ ${imports}
71
+
72
+ async function main(filePath: string) {
73
+ const content = await fs.readFile(filePath);
74
+ const records = JSON.parse(content.toString()) as DocumentRecord[];
75
+ ${body}
76
+ console.log(\`search updated: \${records.length} records\`);
77
+ }
78
+
79
+ // the path of pre-rendered \`static.json\`
80
+ const filePath = process.argv[2];
81
+ if (!filePath) throw new Error('missing the path of static.json');
82
+ void main(filePath);
83
+ `;
84
+ const providers = {
85
+ "orama-cloud": {
86
+ label: "Orama Cloud",
87
+ hint: "signup needed",
88
+ dependencies: { "@orama/core": null },
89
+ publicEnv: [
90
+ "ORAMA_DATASOURCE_ID",
91
+ "ORAMA_PROJECT_ID",
92
+ "ORAMA_API_KEY"
93
+ ],
94
+ privateEnv: ["ORAMA_PRIVATE_API_KEY"],
95
+ static: true,
96
+ dialog: (env) => `'use client';
97
+ ${dialogImports}
98
+ import { useDocsSearch } from 'fumadocs-core/search/client';
99
+ import { OramaCloud } from '@orama/core';
100
+
101
+ const client = new OramaCloud({
102
+ projectId: ${env.read}ORAMA_PROJECT_ID!,
103
+ apiKey: ${env.read}ORAMA_API_KEY!,
104
+ });
105
+
106
+ export default function CustomSearchDialog(props: SharedProps) {
107
+ const { locale } = useI18n();
108
+ const { search, setSearch, query } = useDocsSearch({
109
+ type: 'orama-cloud',
110
+ client,
111
+ locale,
112
+ });
113
+
114
+ ${dialogBody("Orama", "https://orama.com")}`,
115
+ exportIndexes: ({ ref }) => ({
116
+ imports: `import { ${ref} } from '@/lib/source';
117
+ import { toDocuments } from 'fumadocs-core/search/orama-cloud';`,
118
+ documents: `toDocuments(${ref})`
119
+ }),
120
+ sync: (env) => syncScript(`import { type OramaDocument as DocumentRecord, sync } from 'fumadocs-core/search/orama-cloud';
121
+ import { OramaCloud } from '@orama/core';`, ` const orama = new OramaCloud({
122
+ projectId: process.env.${env.prefix}ORAMA_PROJECT_ID!,
123
+ apiKey: process.env.ORAMA_PRIVATE_API_KEY!,
124
+ });
125
+
126
+ await sync(orama, {
127
+ index: process.env.${env.prefix}ORAMA_DATASOURCE_ID!,
128
+ documents: records,
129
+ });
130
+ `)
131
+ },
132
+ algolia: {
133
+ label: "Algolia",
134
+ hint: "signup needed",
135
+ dependencies: { algoliasearch: null },
136
+ publicEnv: ["ALGOLIA_APP_ID", "ALGOLIA_SEARCH_KEY"],
137
+ privateEnv: ["ALGOLIA_WRITE_KEY"],
138
+ static: true,
139
+ dialog: (env) => `'use client';
140
+ ${dialogImports}
141
+ import { useDocsSearch } from 'fumadocs-core/search/client';
142
+ import { algoliaClient } from 'fumadocs-core/search/client/algolia';
143
+ import { liteClient } from 'algoliasearch/lite';
144
+
145
+ const algolia = liteClient(${env.read}ALGOLIA_APP_ID!, ${env.read}ALGOLIA_SEARCH_KEY!);
146
+
147
+ export default function CustomSearchDialog(props: SharedProps) {
148
+ const { locale } = useI18n();
149
+ const { search, setSearch, query } = useDocsSearch({
150
+ client: algoliaClient({
151
+ client: algolia,
152
+ indexName: 'document',
153
+ locale,
154
+ }),
155
+ });
156
+
157
+ ${dialogBody("Algolia", "https://algolia.com")}`,
158
+ exportIndexes: ({ ref }) => ({
159
+ imports: `import { ${ref} } from '@/lib/source';
160
+ import { toDocuments } from 'fumadocs-core/search/algolia';`,
161
+ documents: `toDocuments(${ref})`
162
+ }),
163
+ sync: (env) => syncScript(`import { type DocumentRecord, sync } from 'fumadocs-core/search/algolia';
164
+ import { algoliasearch } from 'algoliasearch';`, ` const client = algoliasearch(
165
+ process.env.${env.prefix}ALGOLIA_APP_ID!,
166
+ process.env.ALGOLIA_WRITE_KEY!,
167
+ );
168
+
169
+ await sync(client, {
170
+ indexName: 'document',
171
+ documents: records,
172
+ });
173
+ `)
174
+ },
175
+ typesense: {
176
+ label: "Typesense",
177
+ hint: "self-hosted or cloud, community adapter",
178
+ dependencies: {
179
+ typesense: null,
180
+ "typesense-fumadocs-adapter": null
181
+ },
182
+ publicEnv: ["TYPESENSE_URL", "TYPESENSE_SEARCH_KEY"],
183
+ privateEnv: ["TYPESENSE_API_KEY"],
184
+ static: true,
185
+ dialog: (env) => `'use client';
186
+ ${dialogImports}
187
+ import { Client } from 'typesense';
188
+ import { useTypesenseSearch } from 'typesense-fumadocs-adapter/client';
189
+
190
+ const client = new Client({
191
+ nodes: [{ url: ${env.read}TYPESENSE_URL! }],
192
+ apiKey: ${env.read}TYPESENSE_SEARCH_KEY!,
193
+ });
194
+
195
+ export default function CustomSearchDialog(props: SharedProps) {
196
+ const { locale } = useI18n();
197
+ const { search, setSearch, query } = useTypesenseSearch({
198
+ typesenseCollectionName: 'docs',
199
+ locale,
200
+ client,
201
+ });
202
+
203
+ ${dialogBody("Typesense", "https://typesense.org")}`,
204
+ exportIndexes: (src) => ({
205
+ imports: "import { exportSearchIndexes } from '@/lib/export-search-indexes';",
206
+ documents: "exportSearchIndexes()",
207
+ file: (async) => typesenseIndexes(async, src)
208
+ }),
209
+ sync: (env) => syncScript(`import { type DocumentRecord, sync } from 'typesense-fumadocs-adapter';
210
+ import { Client } from 'typesense';`, ` const client = new Client({
211
+ nodes: [{ url: process.env.${env.prefix}TYPESENSE_URL! }],
212
+ apiKey: process.env.TYPESENSE_API_KEY!,
213
+ connectionTimeoutSeconds: 60 * 15,
214
+ });
215
+
216
+ await sync(client, {
217
+ typesenseCollectionName: 'docs',
218
+ documents: records,
219
+ });
220
+ `)
221
+ },
222
+ mixedbread: {
223
+ label: "Mixedbread",
224
+ hint: "AI search, signup needed",
225
+ dependencies: { "@mixedbread/sdk": null },
226
+ publicEnv: [],
227
+ privateEnv: ["MIXEDBREAD_API_KEY", "MIXEDBREAD_STORE_ID"],
228
+ static: false,
229
+ dialog: () => `'use client';
230
+ ${dialogImports}
231
+ import { useDocsSearch } from 'fumadocs-core/search/client';
232
+ import { fetchClient } from 'fumadocs-core/search/client/fetch';
233
+
234
+ export default function CustomSearchDialog(props: SharedProps) {
235
+ const { locale } = useI18n();
236
+ const { search, setSearch, query } = useDocsSearch({
237
+ client: fetchClient({
238
+ api: '/api/search',
239
+ locale,
240
+ }),
241
+ });
242
+
243
+ ${dialogBody("Mixedbread", "https://mixedbread.com")}`,
244
+ sync: (_env, dir) => `import { spawnSync } from 'node:child_process';
245
+
246
+ // sync the content with Mixedbread CLI
247
+ const result = spawnSync(
248
+ 'npx',
249
+ ['--yes', '@mixedbread/cli', 'vs', 'sync', process.env.MIXEDBREAD_STORE_ID!, '${dir}', '--ci'],
250
+ { stdio: 'inherit' },
251
+ );
252
+
253
+ process.exit(result.status ?? 1);
254
+ `,
255
+ searchRoute: (() => {
256
+ const server = `import { createMixedbreadSearchAPI } from 'fumadocs-core/search/mixedbread';
257
+ import Mixedbread from '@mixedbread/sdk';
258
+
259
+ const server = createMixedbreadSearchAPI({
260
+ client: new Mixedbread({
261
+ apiKey: process.env.MIXEDBREAD_API_KEY,
262
+ }),
263
+ storeIdentifier: process.env.MIXEDBREAD_STORE_ID!,
264
+ });
265
+ `;
266
+ return {
267
+ next: `${server}
268
+ export const { GET } = server;
269
+ `,
270
+ "react-router": `import type { Route } from './+types/search';
271
+ ${server}
272
+ export async function loader({ request }: Route.LoaderArgs) {
273
+ return server.GET(request);
274
+ }
275
+ `,
276
+ "tanstack-start": `import { createFileRoute } from '@tanstack/react-router';
277
+ ${server}
278
+ export const Route = createFileRoute('/api/search')({
279
+ server: {
280
+ handlers: {
281
+ GET: async ({ request }) => server.GET(request),
282
+ },
283
+ },
284
+ });
285
+ `,
286
+ waku: `${server}
287
+ export const { GET } = server;
288
+ `
289
+ };
290
+ })()
291
+ }
292
+ };
293
+ const staticRoute = (framework, route, imports, documents) => routeModule(framework, route, {
294
+ imports,
295
+ body: `Response.json(await ${documents})`,
296
+ revalidate: true,
297
+ static: true
298
+ });
299
+ const search = {
300
+ id: "search",
301
+ title: "Search",
302
+ description: "replace the default search with a 3rd party search solution",
303
+ requires: [docs],
304
+ supports: (project) => project.source.loader || project.source.dynamic || "`lib/source.ts` must export a `source` loader",
305
+ options: { provider: {
306
+ message: "Choose a search provider",
307
+ choices: Object.entries(providers).map(([value, { label, hint }]) => ({
308
+ value,
309
+ label,
310
+ hint
311
+ }))
312
+ } },
313
+ async apply(ctx, { provider: name }) {
314
+ const { project } = ctx;
315
+ const { baseDir } = project;
316
+ const framework = reactFramework(project);
317
+ const provider = providers[name];
318
+ if (project.static && !provider.static) throw new Error(`${provider.label} requires a server at runtime`);
319
+ const env = publicEnv(framework === "next");
320
+ ctx.addDependencies(provider.dependencies);
321
+ await ctx.write(path.join(baseDir, "components/search.tsx"), provider.dialog(env));
322
+ const searchRoute = await findSource(project.cwd, path.join(baseDir, project.info.routesDir), "createFromSource(", (file) => /(^|[\\/])search(\.ts|[\\/]route\.ts)$/.test(file));
323
+ if (provider.searchRoute) {
324
+ if (searchRoute) await ctx.write(searchRoute, provider.searchRoute[framework]);
325
+ else ctx.note("Create the search route with `createMixedbreadSearchAPI`, see https://fumadocs.dev/docs/headless/search/mixedbread.");
326
+ } else if (searchRoute) await removeDefaultSearch(ctx, searchRoute);
327
+ if (provider.exportIndexes) {
328
+ const { imports, documents, file } = provider.exportIndexes(sourceRef(project.source.dynamic));
329
+ if (file) await ctx.write(path.join(baseDir, "lib/export-search-indexes.ts"), file(project.source.async));
330
+ const route = formatRoute({ segments: ["static.json"] }, framework, null);
331
+ await ctx.write(path.join(baseDir, route.file), staticRoute(framework, route, imports, documents));
332
+ if (framework === "react-router") await registerReactRouterRoutes(ctx, [route]);
333
+ else if (framework === "tanstack-start" && project.configFile) await ctx.source(project.configFile, (file) => addTanstackPrerender(file, [route.path]));
334
+ }
335
+ const rootProvider = await findSource(project.cwd, baseDir, "<RootProvider");
336
+ if (!(rootProvider !== void 0 && await ctx.source(rootProvider, (file) => {
337
+ const element = findJsxElement(file, "RootProvider")?.openingElement;
338
+ if (!element || file.code.slice(element.start, element.end).includes("search=")) return;
339
+ addJsxAttribute(file, element, "search={{ SearchDialog }}");
340
+ addImport(file, {
341
+ from: "@/components/search",
342
+ default: "SearchDialog"
343
+ });
344
+ }))) ctx.note("Pass `search={{ SearchDialog }}` from `@/components/search` to `<RootProvider />`.");
345
+ if (provider.sync) {
346
+ const output = {
347
+ next: ".next/server/app/static.json.body",
348
+ "react-router": "build/client/static.json",
349
+ "tanstack-start": project.static ? "dist/client/static.json" : ".output/public/static.json",
350
+ waku: "dist/public/static.json"
351
+ }[framework];
352
+ await ctx.write("scripts/sync-content.ts", provider.sync(env, project.source.dir));
353
+ await ctx.packageJson((data) => {
354
+ const build = data.scripts?.build ?? "";
355
+ if (build.includes("sync-content")) return;
356
+ const sync = `node --env-file-if-exists=.env.local scripts/sync-content.ts${provider.exportIndexes ? ` ${output}` : ""}`;
357
+ scripts(data, { build: build ? `${build} && ${sync}` : sync });
358
+ });
359
+ }
360
+ for (const key of provider.publicEnv) ctx.env(env.prefix + key, "");
361
+ for (const key of provider.privateEnv) ctx.env(key, "");
362
+ ctx.note(`Fill the ${provider.label} keys in \`.env.local\`, the search index is synced after each build.\nSee https://fumadocs.dev/docs/headless/search/${name}.`);
363
+ }
364
+ };
365
+ /** remove the default search route, which conflicts with the search provider */
366
+ async function removeDefaultSearch(ctx, file) {
367
+ const { baseDir, framework } = ctx.project;
368
+ await ctx.remove(file);
369
+ if (framework !== "react-router") return;
370
+ const entry = posix(path.relative(baseDir, file));
371
+ await ctx.source(path.join(baseDir, "routes.ts"), (source) => {
372
+ filterReactRouterRoute(source, (item) => item.entry !== entry);
373
+ });
374
+ await ctx.source("react-router.config.ts", (source) => {
375
+ filterReactRouterPrerenderArray(source, "excluded", (item) => item !== "/api/search");
376
+ });
377
+ }
378
+ //#endregion
379
+ export { search as t };
380
+
381
+ //# sourceMappingURL=search-iphnSVVY.js.map
@@ -0,0 +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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"}
@@ -0,0 +1,43 @@
1
+ import { a as Project, o as ReactFramework } from "./index-BCVGQxn9.js";
2
+ import { i as FeatureContext, s as PackageJson } from "./index-CR9T40ox.js";
3
+ //#region src/project/route.d.ts
4
+ interface FormattedRoute {
5
+ /** route file, relative to the base dir */
6
+ file: string;
7
+ /** route string of the framework, e.g. for `RouteContext<'...'>`, `createFileRoute('...')`, `route('...')` or `ApiContext<'...'>` */
8
+ path: string;
9
+ /** path-to-regexp pattern, for the Next.js proxy matcher and rewrites */
10
+ pattern: string;
11
+ }
12
+ //#endregion
13
+ //#region src/features/utils.d.ts
14
+ /** the shallowest `.ts`/`.tsx` file under `dir` whose content includes `needle`, relative to `cwd` */
15
+ declare function findSource(cwd: string, dir: string, needle: string, match?: (file: string) => boolean): Promise<string | undefined>;
16
+ declare function scripts(json: PackageJson, scripts: Record<string, string>): void;
17
+ declare const reactOnly: (project: Project) => "Astro is not supported by this feature" | true;
18
+ declare function reactFramework(project: Project): ReactFramework;
19
+ /** POSIX path for generated code and route configs */
20
+ declare const posix: (file: string) => string;
21
+ /** add an export to a source file, unless a declaration with the same name exists */
22
+ declare function addExport(ctx: FeatureContext, file: string, name: string, code: string): Promise<boolean>;
23
+ /** add a page URL helper to `lib/shared.ts`, where the route constants live */
24
+ declare function addPageUrl(ctx: FeatureContext, name: string, code: string): Promise<boolean>;
25
+ /** the value of a route constant in `lib/shared.ts`, defined with `value` when missing */
26
+ declare function sharedRoute(ctx: FeatureContext, name: string, value: string): Promise<string>;
27
+ /** add routes to `routes.ts`, or tell the user when the config isn't an array (e.g. file-based routes) */
28
+ declare function registerReactRouterRoutes(ctx: FeatureContext, routes: (FormattedRoute | string)[]): Promise<boolean>;
29
+ /** features reading page Markdown need Fumadocs MDX collections, or a runtime source exposing `page.data.content` */
30
+ declare const requiresMarkdown: (project: Project) => "Astro is not supported by this feature" | "requires Fumadocs MDX or a runtime content source" | true;
31
+ /** how generated code refers to the loader of `lib/source.ts` */
32
+ interface SourceRef {
33
+ /** resolved on demand, `lib/source.ts` exports `getSource()` instead of `source` */
34
+ dynamic: boolean;
35
+ /** passed to Fumadocs APIs, which accept a loader or a function resolving one */
36
+ ref: string;
37
+ /** reads the loader inside an async function */
38
+ resolved: string;
39
+ }
40
+ declare function sourceRef(dynamic: boolean): SourceRef;
41
+ //#endregion
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
@@ -0,0 +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"}
@@ -0,0 +1,98 @@
1
+ import { a as addReactRouterRoute, p as addImport } from "./codemod-auol-p4R.js";
2
+ import fs from "node:fs/promises";
3
+ import path from "node:path";
4
+ //#region src/features/utils.ts
5
+ /** the shallowest `.ts`/`.tsx` file under `dir` whose content includes `needle`, relative to `cwd` */
6
+ async function findSource(cwd, dir, needle, match = () => true) {
7
+ const entries = await fs.readdir(path.join(cwd, dir), {
8
+ recursive: true,
9
+ withFileTypes: true
10
+ }).catch(() => []);
11
+ const depth = (dir) => dir.split(path.sep).length;
12
+ entries.sort((a, b) => depth(a.parentPath) - depth(b.parentPath));
13
+ for (const entry of entries) {
14
+ if (!entry.isFile() || !/\.tsx?$/.test(entry.name) || entry.parentPath.includes("node_modules")) continue;
15
+ const file = path.join(entry.parentPath, entry.name);
16
+ if (!match(file)) continue;
17
+ if ((await fs.readFile(file, "utf-8")).includes(needle)) return path.relative(cwd, file);
18
+ }
19
+ }
20
+ function scripts(json, scripts) {
21
+ Object.assign(json.scripts ??= {}, scripts);
22
+ }
23
+ const reactOnly = (project) => project.framework !== "astro" || "Astro is not supported by this feature";
24
+ function reactFramework(project) {
25
+ if (project.framework === "astro") throw new Error("Astro is not supported by this feature");
26
+ return project.framework;
27
+ }
28
+ /** POSIX path for generated code and route configs */
29
+ const posix = (file) => file.split(path.sep).join("/");
30
+ /** add an export to a source file, unless a declaration with the same name exists */
31
+ async function addExport(ctx, file, name, code) {
32
+ const current = await fs.readFile(path.join(ctx.project.cwd, file), "utf-8").catch(() => "");
33
+ if (new RegExp(`export (async )?(const|function) ${name}\\b`).test(current)) return false;
34
+ await ctx.append(file, [code]);
35
+ return true;
36
+ }
37
+ /** add a page URL helper to `lib/shared.ts`, where the route constants live */
38
+ async function addPageUrl(ctx, name, code) {
39
+ const file = path.join(ctx.project.baseDir, "lib/shared.ts");
40
+ if (!await addExport(ctx, file, name, code)) return false;
41
+ await ctx.source(file, (source) => addImport(source, {
42
+ from: "fumadocs-core/source",
43
+ named: ["createGetUrl"]
44
+ }));
45
+ if (ctx.project.i18n) await ctx.source(file, (source) => addImport(source, {
46
+ from: "./i18n",
47
+ named: ["i18n"]
48
+ }));
49
+ return true;
50
+ }
51
+ /** the value of a route constant in `lib/shared.ts`, defined with `value` when missing */
52
+ async function sharedRoute(ctx, name, value) {
53
+ const file = path.join(ctx.project.baseDir, "lib/shared.ts");
54
+ const current = await fs.readFile(path.join(ctx.project.cwd, file), "utf-8").catch(() => "");
55
+ const existing = new RegExp(`export const ${name}\\s*=\\s*['"]([^'"]*)['"]`).exec(current)?.[1];
56
+ if (existing !== void 0) return existing;
57
+ const separator = current.trimEnd().endsWith(";") || current.length === 0 ? "" : "\n";
58
+ await ctx.append(file, [`${separator}export const ${name} = '${value}';`]);
59
+ return value;
60
+ }
61
+ /** add routes to `routes.ts`, or tell the user when the config isn't an array (e.g. file-based routes) */
62
+ async function registerReactRouterRoutes(ctx, routes) {
63
+ const { baseDir } = ctx.project;
64
+ const entries = routes.map((item) => typeof item === "string" ? item : {
65
+ path: item.path,
66
+ entry: item.file
67
+ });
68
+ let added = false;
69
+ if (!await ctx.source(path.join(baseDir, "routes.ts"), (file) => {
70
+ const first = entries[0];
71
+ if (file.code.includes(typeof first === "string" ? first : first.entry)) {
72
+ added = true;
73
+ return;
74
+ }
75
+ added = addReactRouterRoute(file, entries);
76
+ }) || !added) {
77
+ const list = entries.map((item) => typeof item === "string" ? item : `route('${item.path}', '${item.entry}')`);
78
+ ctx.note(`Register the routes in your route config:\n ${list.join("\n ")}`);
79
+ }
80
+ return added;
81
+ }
82
+ /** features reading page Markdown need Fumadocs MDX collections, or a runtime source exposing `page.data.content` */
83
+ const requiresMarkdown = (project) => project.framework === "astro" ? "Astro is not supported by this feature" : project.source.collections !== null || project.source.dynamic || "requires Fumadocs MDX or a runtime content source";
84
+ function sourceRef(dynamic) {
85
+ return dynamic ? {
86
+ dynamic: true,
87
+ ref: "getSource",
88
+ resolved: "(await getSource())"
89
+ } : {
90
+ dynamic: false,
91
+ ref: "source",
92
+ resolved: "source"
93
+ };
94
+ }
95
+ //#endregion
96
+ export { reactFramework as a, requiresMarkdown as c, sourceRef as d, posix as i, scripts as l, addPageUrl as n, reactOnly as o, findSource as r, registerReactRouterRoutes as s, addExport as t, sharedRoute as u };
97
+
98
+ //# sourceMappingURL=utils-DbMe7h6c.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"utils-DbMe7h6c.js","names":[],"sources":["../src/features/utils.ts"],"sourcesContent":["import fs from 'node:fs/promises';\nimport path from 'node:path';\nimport type { Project, ReactFramework } from '@/project';\nimport type { FormattedRoute } from '@/project/route';\nimport type { FeatureContext, PackageJson } from '@/features';\nimport { addImport, addReactRouterRoute } from '@/codemod';\n\n/** the shallowest `.ts`/`.tsx` file under `dir` whose content includes `needle`, relative to `cwd` */\nexport async function findSource(\n cwd: string,\n dir: string,\n needle: string,\n match: (file: string) => boolean = () => true,\n) {\n const entries = await fs\n .readdir(path.join(cwd, dir), { recursive: true, withFileTypes: true })\n .catch(() => []);\n const depth = (dir: string) => dir.split(path.sep).length;\n entries.sort((a, b) => depth(a.parentPath) - depth(b.parentPath));\n\n for (const entry of entries) {\n if (!entry.isFile() || !/\\.tsx?$/.test(entry.name) || entry.parentPath.includes('node_modules'))\n continue;\n const file = path.join(entry.parentPath, entry.name);\n if (!match(file)) continue;\n if ((await fs.readFile(file, 'utf-8')).includes(needle)) return path.relative(cwd, file);\n }\n}\n\nexport function scripts(json: PackageJson, scripts: Record<string, string>) {\n Object.assign((json.scripts ??= {}), scripts);\n}\n\nexport const reactOnly = (project: Project) =>\n project.framework !== 'astro' || 'Astro is not supported by this feature';\n\nexport function reactFramework(project: Project): ReactFramework {\n if (project.framework === 'astro') throw new Error('Astro is not supported by this feature');\n return project.framework;\n}\n\n/** POSIX path for generated code and route configs */\nexport const posix = (file: string) => file.split(path.sep).join('/');\n\n/** add an export to a source file, unless a declaration with the same name exists */\nexport async function addExport(ctx: FeatureContext, file: string, name: string, code: string) {\n const current = await fs.readFile(path.join(ctx.project.cwd, file), 'utf-8').catch(() => '');\n if (new RegExp(`export (async )?(const|function) ${name}\\\\b`).test(current)) return false;\n await ctx.append(file, [code]);\n return true;\n}\n\n/** add a page URL helper to `lib/shared.ts`, where the route constants live */\nexport async function addPageUrl(ctx: FeatureContext, name: string, code: string) {\n const file = path.join(ctx.project.baseDir, 'lib/shared.ts');\n if (!(await addExport(ctx, file, name, code))) return false;\n\n await ctx.source(file, (source) =>\n addImport(source, { from: 'fumadocs-core/source', named: ['createGetUrl'] }),\n );\n // a second pass, so the import lands after the one above instead of before it\n if (ctx.project.i18n) {\n await ctx.source(file, (source) => addImport(source, { from: './i18n', named: ['i18n'] }));\n }\n return true;\n}\n\n/** the value of a route constant in `lib/shared.ts`, defined with `value` when missing */\nexport async function sharedRoute(ctx: FeatureContext, name: string, value: string) {\n const file = path.join(ctx.project.baseDir, 'lib/shared.ts');\n const current = await fs.readFile(path.join(ctx.project.cwd, file), 'utf-8').catch(() => '');\n const existing = new RegExp(`export const ${name}\\\\s*=\\\\s*['\"]([^'\"]*)['\"]`).exec(current)?.[1];\n if (existing !== undefined) return existing;\n // keep a blank line after the declarations already in the file\n const separator = current.trimEnd().endsWith(';') || current.length === 0 ? '' : '\\n';\n await ctx.append(file, [`${separator}export const ${name} = '${value}';`]);\n return value;\n}\n\n/** add routes to `routes.ts`, or tell the user when the config isn't an array (e.g. file-based routes) */\nexport async function registerReactRouterRoutes(\n ctx: FeatureContext,\n routes: (FormattedRoute | string)[],\n): Promise<boolean> {\n const { baseDir } = ctx.project;\n const entries = routes.map((item) =>\n typeof item === 'string' ? item : { path: item.path, entry: item.file },\n );\n let added = false;\n const edited = await ctx.source(path.join(baseDir, 'routes.ts'), (file) => {\n const first = entries[0];\n if (file.code.includes(typeof first === 'string' ? first : first.entry)) {\n added = true;\n return;\n }\n added = addReactRouterRoute(file, entries);\n });\n\n if (!edited || !added) {\n const list = entries.map((item) =>\n typeof item === 'string' ? item : `route('${item.path}', '${item.entry}')`,\n );\n ctx.note(`Register the routes in your route config:\\n ${list.join('\\n ')}`);\n }\n return added;\n}\n\n/** features reading page Markdown need Fumadocs MDX collections, or a runtime source exposing `page.data.content` */\nexport const requiresMarkdown = (project: Project) =>\n project.framework === 'astro'\n ? 'Astro is not supported by this feature'\n : project.source.collections !== null ||\n project.source.dynamic ||\n 'requires Fumadocs MDX or a runtime content source';\n\n/** how generated code refers to the loader of `lib/source.ts` */\nexport interface SourceRef {\n /** resolved on demand, `lib/source.ts` exports `getSource()` instead of `source` */\n dynamic: boolean;\n /** passed to Fumadocs APIs, which accept a loader or a function resolving one */\n ref: string;\n /** reads the loader inside an async function */\n resolved: string;\n}\n\nexport function sourceRef(dynamic: boolean): SourceRef {\n return dynamic\n ? { dynamic: true, ref: 'getSource', resolved: '(await getSource())' }\n : { dynamic: false, ref: 'source', resolved: 'source' };\n}\n"],"mappings":"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"}