@fumadocs/cli 1.5.0 → 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.
- package/dist/ai-BVuNjYXU.js +100 -0
- package/dist/ai-BVuNjYXU.js.map +1 -0
- package/dist/all-aUHxtoXL.js +27 -0
- package/dist/all-aUHxtoXL.js.map +1 -0
- package/dist/codemod/index.d.ts +2 -0
- package/dist/codemod/index.js +2 -0
- package/dist/codemod-auol-p4R.js +404 -0
- package/dist/codemod-auol-p4R.js.map +1 -0
- package/dist/config-CT2jj39D.d.ts +52 -0
- package/dist/config-CT2jj39D.d.ts.map +1 -0
- package/dist/config-D6uv73fs.js +94 -0
- package/dist/config-D6uv73fs.js.map +1 -0
- package/dist/config.d.ts +2 -52
- package/dist/config.js +1 -72
- package/dist/docs-DeIEl_Kt.js +738 -0
- package/dist/docs-DeIEl_Kt.js.map +1 -0
- package/dist/epub-CnQFJh09.js +52 -0
- package/dist/epub-CnQFJh09.js.map +1 -0
- package/dist/features/ai.d.ts +25 -0
- package/dist/features/ai.d.ts.map +1 -0
- package/dist/features/ai.js +2 -0
- package/dist/features/all.d.ts +5 -0
- package/dist/features/all.d.ts.map +1 -0
- package/dist/features/all.js +2 -0
- package/dist/features/docs/index.d.ts +10 -0
- package/dist/features/docs/index.d.ts.map +1 -0
- package/dist/features/docs/index.js +2 -0
- package/dist/features/epub.d.ts +5 -0
- package/dist/features/epub.d.ts.map +1 -0
- package/dist/features/epub.js +2 -0
- package/dist/features/feedback.d.ts +5 -0
- package/dist/features/feedback.d.ts.map +1 -0
- package/dist/features/feedback.js +2 -0
- package/dist/features/index.d.ts +2 -0
- package/dist/features/index.js +2 -0
- package/dist/features/lint.d.ts +7 -0
- package/dist/features/lint.d.ts.map +1 -0
- package/dist/features/lint.js +2 -0
- package/dist/features/llms.d.ts +27 -0
- package/dist/features/llms.d.ts.map +1 -0
- package/dist/features/llms.js +2 -0
- package/dist/features/mcp.d.ts +9 -0
- package/dist/features/mcp.d.ts.map +1 -0
- package/dist/features/mcp.js +2 -0
- package/dist/features/og.d.ts +8 -0
- package/dist/features/og.d.ts.map +1 -0
- package/dist/features/og.js +2 -0
- package/dist/features/search.d.ts +84 -0
- package/dist/features/search.d.ts.map +1 -0
- package/dist/features/search.js +2 -0
- package/dist/features/utils.d.ts +2 -0
- package/dist/features/utils.js +2 -0
- package/dist/features/webmcp.d.ts +12 -0
- package/dist/features/webmcp.d.ts.map +1 -0
- package/dist/features/webmcp.js +2 -0
- package/dist/features-C1kf2s8j.js +132 -0
- package/dist/features-C1kf2s8j.js.map +1 -0
- package/dist/feedback-COmWtc5C.js +37 -0
- package/dist/feedback-COmWtc5C.js.map +1 -0
- package/dist/fs-D6J9dOEx.js +14 -0
- package/dist/fs-D6J9dOEx.js.map +1 -0
- package/dist/index-BCVGQxn9.d.ts +64 -0
- package/dist/index-BCVGQxn9.d.ts.map +1 -0
- package/dist/index-CR9T40ox.d.ts +76 -0
- package/dist/index-CR9T40ox.d.ts.map +1 -0
- package/dist/index-Ck0EPdgu.d.ts +126 -0
- package/dist/index-Ck0EPdgu.d.ts.map +1 -0
- package/dist/index.js +121 -112
- package/dist/index.js.map +1 -1
- package/dist/{installer-Ci3Hf9it.js → installer-D0Obb2Md.js} +16 -6
- package/dist/installer-D0Obb2Md.js.map +1 -0
- package/dist/lint-zkXUULgO.js +137 -0
- package/dist/lint-zkXUULgO.js.map +1 -0
- package/dist/llms-CONd8Vnn.js +228 -0
- package/dist/llms-CONd8Vnn.js.map +1 -0
- package/dist/mcp-BT8BLZZ-.js +67 -0
- package/dist/mcp-BT8BLZZ-.js.map +1 -0
- package/dist/og-DRDOyQ_t.js +142 -0
- package/dist/og-DRDOyQ_t.js.map +1 -0
- package/dist/project/index.d.ts +2 -0
- package/dist/project/index.js +2 -0
- package/dist/project-DwRVTm4p.js +110 -0
- package/dist/project-DwRVTm4p.js.map +1 -0
- package/dist/registry/installer.d.ts +4 -5
- package/dist/registry/installer.d.ts.map +1 -1
- package/dist/registry/installer.js +1 -1
- package/dist/schema.json +1 -1
- package/dist/search-iphnSVVY.js +381 -0
- package/dist/search-iphnSVVY.js.map +1 -0
- package/dist/utils-B1Xy4l_a.d.ts +43 -0
- package/dist/utils-B1Xy4l_a.d.ts.map +1 -0
- package/dist/utils-DbMe7h6c.js +98 -0
- package/dist/utils-DbMe7h6c.js.map +1 -0
- package/dist/webmcp-C2PM_9md.js +135 -0
- package/dist/webmcp-C2PM_9md.js.map +1 -0
- package/package.json +27 -7
- package/dist/config.d.ts.map +0 -1
- package/dist/config.js.map +0 -1
- package/dist/installer-Ci3Hf9it.js.map +0 -1
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
import { t as exists } from "./fs-D6J9dOEx.js";
|
|
2
|
+
import fs from "node:fs/promises";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
import { detectFramework as detectFramework$1, detectPackageManager } from "fuma-cli/detect";
|
|
5
|
+
//#region src/project/index.ts
|
|
6
|
+
const frameworks = {
|
|
7
|
+
next: {
|
|
8
|
+
provider: "fumadocs-ui/provider/next",
|
|
9
|
+
rootFile: "app/layout.tsx",
|
|
10
|
+
routesDir: "app",
|
|
11
|
+
configFiles: [
|
|
12
|
+
"next.config.mjs",
|
|
13
|
+
"next.config.ts",
|
|
14
|
+
"next.config.js"
|
|
15
|
+
],
|
|
16
|
+
isStatic: (config) => /output:\s*['"]export['"]/.test(config)
|
|
17
|
+
},
|
|
18
|
+
"react-router": {
|
|
19
|
+
provider: "fumadocs-ui/provider/react-router",
|
|
20
|
+
rootFile: "root.tsx",
|
|
21
|
+
routesDir: "routes",
|
|
22
|
+
configFiles: ["vite.config.ts"]
|
|
23
|
+
},
|
|
24
|
+
"tanstack-start": {
|
|
25
|
+
provider: "fumadocs-ui/provider/tanstack",
|
|
26
|
+
rootFile: "routes/__root.tsx",
|
|
27
|
+
routesDir: "routes",
|
|
28
|
+
configFiles: ["vite.config.ts"],
|
|
29
|
+
isStatic: (config) => /\bspa:\s*\{/.test(config)
|
|
30
|
+
},
|
|
31
|
+
waku: {
|
|
32
|
+
provider: "fumadocs-ui/provider/waku",
|
|
33
|
+
rootFile: "pages/_root.tsx",
|
|
34
|
+
routesDir: "pages",
|
|
35
|
+
configFiles: ["waku.config.ts"]
|
|
36
|
+
},
|
|
37
|
+
astro: {
|
|
38
|
+
provider: "fumadocs-ui/provider/astro",
|
|
39
|
+
rootFile: "layouts/Layout.astro",
|
|
40
|
+
routesDir: "pages",
|
|
41
|
+
configFiles: ["astro.config.mjs", "astro.config.ts"]
|
|
42
|
+
}
|
|
43
|
+
};
|
|
44
|
+
function isSupportedFramework(v) {
|
|
45
|
+
return v in frameworks;
|
|
46
|
+
}
|
|
47
|
+
async function loadProject(config, cwd = process.cwd(), framework = assertFramework(config.framework)) {
|
|
48
|
+
const info = frameworks[framework];
|
|
49
|
+
const [packageJson, pm] = await Promise.all([fs.readFile(path.join(cwd, "package.json"), "utf-8").then((content) => JSON.parse(content)).catch(() => ({})), detectPackageManager({ cwd })]);
|
|
50
|
+
let configFile;
|
|
51
|
+
let configContent;
|
|
52
|
+
for (const file of info.configFiles) if (await exists(path.join(cwd, file))) {
|
|
53
|
+
configFile = file;
|
|
54
|
+
configContent = await fs.readFile(path.join(cwd, file), "utf-8");
|
|
55
|
+
break;
|
|
56
|
+
}
|
|
57
|
+
let isStatic = false;
|
|
58
|
+
if (framework === "react-router") {
|
|
59
|
+
const content = await fs.readFile(path.join(cwd, "react-router.config.ts"), "utf-8").catch(() => "");
|
|
60
|
+
isStatic = /ssr:\s*false/.test(content);
|
|
61
|
+
} else if (configContent && info.isStatic) isStatic = info.isStatic(configContent);
|
|
62
|
+
const read = (file) => fs.readFile(path.join(cwd, file), "utf-8").catch(() => null);
|
|
63
|
+
const i18nConfig = await read(path.join(config.baseDir, "lib/i18n.ts"));
|
|
64
|
+
return {
|
|
65
|
+
cwd,
|
|
66
|
+
framework,
|
|
67
|
+
info,
|
|
68
|
+
static: isStatic,
|
|
69
|
+
i18n: i18nConfig === null ? null : { optionalLocale: /hideLocale:\s*['"]default-locale['"]/.test(i18nConfig) },
|
|
70
|
+
packageManager: pm?.name ?? "npm",
|
|
71
|
+
baseDir: config.baseDir,
|
|
72
|
+
config,
|
|
73
|
+
configFile,
|
|
74
|
+
packageJson,
|
|
75
|
+
source: await detectSource(cwd, config.baseDir, read)
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
async function detectSource(cwd, baseDir, read) {
|
|
79
|
+
const sourceFile = path.join(baseDir, "lib/source.ts");
|
|
80
|
+
const source = await read(sourceFile) ?? "";
|
|
81
|
+
let collections = null;
|
|
82
|
+
let content = source;
|
|
83
|
+
if (source.includes("defineDocs(")) collections = sourceFile;
|
|
84
|
+
else if (await exists(path.join(cwd, "source.config.ts"))) {
|
|
85
|
+
collections = "source.config.ts";
|
|
86
|
+
content = await read(collections) ?? "";
|
|
87
|
+
}
|
|
88
|
+
let baseUrl = /baseUrl:\s*['"]([^'"]*)['"]/.exec(source)?.[1];
|
|
89
|
+
const ref = /baseUrl:\s*(\w+)/.exec(source)?.[1];
|
|
90
|
+
if (!baseUrl && ref) {
|
|
91
|
+
const shared = await read(path.join(baseDir, "lib/shared.ts")) ?? "";
|
|
92
|
+
baseUrl = new RegExp(`${ref}\\s*=\\s*['"]([^'"]*)['"]`).exec(shared)?.[1];
|
|
93
|
+
}
|
|
94
|
+
return {
|
|
95
|
+
loader: /export const source\b/.test(source),
|
|
96
|
+
dynamic: /export (async )?function getSource\b/.test(source),
|
|
97
|
+
collections,
|
|
98
|
+
async: /async:\s*true/.test(content),
|
|
99
|
+
baseUrl: baseUrl ?? "/docs",
|
|
100
|
+
dir: /dir:\s*['"]([^'"]*)['"]/.exec(content)?.[1] ?? "content/docs"
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
function assertFramework(framework) {
|
|
104
|
+
if (isSupportedFramework(framework)) return framework;
|
|
105
|
+
throw new Error(`Unsupported framework "${framework}", supported: ${Object.keys(frameworks).join(", ")}.`);
|
|
106
|
+
}
|
|
107
|
+
//#endregion
|
|
108
|
+
export { loadProject as i, frameworks as n, isSupportedFramework as r, detectFramework$1 as t };
|
|
109
|
+
|
|
110
|
+
//# sourceMappingURL=project-DwRVTm4p.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"project-DwRVTm4p.js","names":[],"sources":["../src/project/index.ts"],"sourcesContent":["import fs from 'node:fs/promises';\nimport path from 'node:path';\nimport { detectFramework, detectPackageManager } from 'fuma-cli/detect';\nimport type { LoadedConfig } from '@/config';\nimport { exists } from '@/utils/fs';\n\nexport type PackageManager = NonNullable<Awaited<ReturnType<typeof detectPackageManager>>>['name'];\n\nexport type Framework = 'next' | 'react-router' | 'tanstack-start' | 'waku' | 'astro';\n/** frameworks supported by features that generate routes */\nexport type ReactFramework = Exclude<Framework, 'astro'>;\n\nexport interface FrameworkInfo {\n /** import specifier of `RootProvider` */\n provider: string;\n /** root file that imports the global CSS, relative to base dir */\n rootFile: string;\n /** routes directory, relative to base dir */\n routesDir: string;\n /** config file candidates (relative to cwd) that register bundler plugins */\n configFiles: string[];\n /** how to detect `static` mode from the config file */\n isStatic?: (config: string) => boolean;\n}\n\nexport const frameworks: Record<Framework, FrameworkInfo> = {\n next: {\n provider: 'fumadocs-ui/provider/next',\n rootFile: 'app/layout.tsx',\n routesDir: 'app',\n configFiles: ['next.config.mjs', 'next.config.ts', 'next.config.js'],\n isStatic: (config) => /output:\\s*['\"]export['\"]/.test(config),\n },\n 'react-router': {\n provider: 'fumadocs-ui/provider/react-router',\n rootFile: 'root.tsx',\n routesDir: 'routes',\n configFiles: ['vite.config.ts'],\n },\n 'tanstack-start': {\n provider: 'fumadocs-ui/provider/tanstack',\n rootFile: 'routes/__root.tsx',\n routesDir: 'routes',\n configFiles: ['vite.config.ts'],\n isStatic: (config) => /\\bspa:\\s*\\{/.test(config),\n },\n waku: {\n provider: 'fumadocs-ui/provider/waku',\n rootFile: 'pages/_root.tsx',\n routesDir: 'pages',\n configFiles: ['waku.config.ts'],\n },\n astro: {\n provider: 'fumadocs-ui/provider/astro',\n rootFile: 'layouts/Layout.astro',\n routesDir: 'pages',\n configFiles: ['astro.config.mjs', 'astro.config.ts'],\n },\n};\n\n/** how docs content is loaded, detected from `lib/source.ts` */\nexport interface SourceInfo {\n /** `lib/source.ts` exports a `source` loader */\n loader: boolean;\n /** `lib/source.ts` exports `getSource()`, a runtime content source resolved on demand */\n dynamic: boolean;\n /** file defining the Fumadocs MDX collections, relative to cwd: `lib/source.ts` (macro) or `source.config.ts`; `null` for other content sources */\n collections: string | null;\n /** collections are loaded lazily, `structuredData` is a function */\n async: boolean;\n /** base URL of docs pages */\n baseUrl: string;\n /** content directory */\n dir: string;\n}\n\nexport interface I18nInfo {\n /** the default locale is hidden from URLs (`hideLocale: 'default-locale'`) */\n optionalLocale: boolean;\n}\n\nexport interface Project {\n cwd: string;\n framework: Framework;\n info: FrameworkInfo;\n /** prerendered to static HTML, no server at runtime */\n static: boolean;\n /** `lib/i18n.ts` exists */\n i18n: I18nInfo | null;\n source: SourceInfo;\n packageManager: PackageManager;\n /** directory of app code, relative to `cwd` */\n baseDir: string;\n config: LoadedConfig;\n /** config file that registers bundler plugins, relative to `cwd` */\n configFile?: string;\n packageJson: {\n dependencies?: Record<string, string>;\n devDependencies?: Record<string, string>;\n };\n}\n\nexport function isSupportedFramework(v: string): v is Framework {\n return v in frameworks;\n}\n\nexport async function loadProject(\n config: LoadedConfig,\n cwd = process.cwd(),\n framework: Framework = assertFramework(config.framework),\n): Promise<Project> {\n const info = frameworks[framework];\n const [packageJson, pm] = await Promise.all([\n fs\n .readFile(path.join(cwd, 'package.json'), 'utf-8')\n .then((content) => JSON.parse(content))\n .catch(() => ({})),\n detectPackageManager({ cwd }),\n ]);\n let configFile: string | undefined;\n let configContent: string | undefined;\n for (const file of info.configFiles) {\n if (await exists(path.join(cwd, file))) {\n configFile = file;\n configContent = await fs.readFile(path.join(cwd, file), 'utf-8');\n break;\n }\n }\n\n let isStatic = false;\n if (framework === 'react-router') {\n const content = await fs\n .readFile(path.join(cwd, 'react-router.config.ts'), 'utf-8')\n .catch(() => '');\n isStatic = /ssr:\\s*false/.test(content);\n } else if (configContent && info.isStatic) {\n isStatic = info.isStatic(configContent);\n }\n\n const read = (file: string) => fs.readFile(path.join(cwd, file), 'utf-8').catch(() => null);\n const i18nConfig = await read(path.join(config.baseDir, 'lib/i18n.ts'));\n\n return {\n cwd,\n framework,\n info,\n static: isStatic,\n i18n:\n i18nConfig === null\n ? null\n : { optionalLocale: /hideLocale:\\s*['\"]default-locale['\"]/.test(i18nConfig) },\n packageManager: pm?.name ?? 'npm',\n baseDir: config.baseDir,\n config,\n configFile,\n packageJson,\n source: await detectSource(cwd, config.baseDir, read),\n };\n}\n\nasync function detectSource(\n cwd: string,\n baseDir: string,\n read: (file: string) => Promise<string | null>,\n): Promise<SourceInfo> {\n const sourceFile = path.join(baseDir, 'lib/source.ts');\n const source = (await read(sourceFile)) ?? '';\n let collections: string | null = null;\n let content = source;\n if (source.includes('defineDocs(')) {\n collections = sourceFile;\n } else if (await exists(path.join(cwd, 'source.config.ts'))) {\n collections = 'source.config.ts';\n content = (await read(collections)) ?? '';\n }\n\n let baseUrl = /baseUrl:\\s*['\"]([^'\"]*)['\"]/.exec(source)?.[1];\n // e.g. `baseUrl: docsRoute` from `lib/shared.ts`\n const ref = /baseUrl:\\s*(\\w+)/.exec(source)?.[1];\n if (!baseUrl && ref) {\n const shared = (await read(path.join(baseDir, 'lib/shared.ts'))) ?? '';\n baseUrl = new RegExp(`${ref}\\\\s*=\\\\s*['\"]([^'\"]*)['\"]`).exec(shared)?.[1];\n }\n\n return {\n loader: /export const source\\b/.test(source),\n dynamic: /export (async )?function getSource\\b/.test(source),\n collections,\n async: /async:\\s*true/.test(content),\n baseUrl: baseUrl ?? '/docs',\n dir: /dir:\\s*['\"]([^'\"]*)['\"]/.exec(content)?.[1] ?? 'content/docs',\n };\n}\n\nfunction assertFramework(framework: string): Framework {\n if (isSupportedFramework(framework)) return framework;\n throw new Error(\n `Unsupported framework \"${framework}\", supported: ${Object.keys(frameworks).join(', ')}.`,\n );\n}\n\nexport { detectFramework };\n"],"mappings":"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"}
|
|
@@ -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":";;;;
|
|
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-
|
|
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
|
|
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"}
|