@fumadocs/cli 1.7.0 → 1.7.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/ai-wXWLPFI5.js +112 -0
- package/dist/ai-wXWLPFI5.js.map +1 -0
- package/dist/{all-BBf7I8hG.js → all-BmDsMSFN.js} +3 -3
- package/dist/{all-BBf7I8hG.js.map → all-BmDsMSFN.js.map} +1 -1
- package/dist/features/ai.js +1 -1
- package/dist/features/all.js +1 -1
- package/dist/features/search.d.ts +55 -10
- package/dist/features/search.d.ts.map +1 -1
- package/dist/features/search.js +2 -2
- package/dist/index.js +68 -169
- package/dist/index.js.map +1 -1
- package/dist/{search-WfSdiGx-.js → search-BbzvfJal.js} +134 -77
- package/dist/search-BbzvfJal.js.map +1 -0
- package/package.json +4 -4
- package/dist/ai-C19ZOP53.js +0 -100
- package/dist/ai-C19ZOP53.js.map +0 -1
- package/dist/search-WfSdiGx-.js.map +0 -1
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","names":["isCancel","isCancelSymbol","isCancel","isCancel","packageJson.version"],"sources":["../src/commands/file-tree.ts","../src/utils/file-tree/run-tree.ts","../package.json","../src/commands/customise.ts","../src/utils/prompt.ts","../src/commands/add.ts","../src/commands/export-epub.ts","../src/commands/feature.ts","../src/index.ts"],"sourcesContent":["export type JsonTreeNode =\n | {\n type: 'file';\n name: string;\n }\n | {\n type: 'directory';\n name: string;\n contents: JsonTreeNode[];\n }\n | {\n type: 'report';\n }\n | {\n type: 'link';\n name: string;\n };\n\nconst scanned = ['file', 'directory', 'link'];\n\nexport function treeToMdx(input: JsonTreeNode[], noRoot = false): string {\n function toNode(item: JsonTreeNode): string {\n if (item.type === 'file' || item.type === 'link') {\n return `<File name=${JSON.stringify(item.name)} />`;\n }\n\n if (item.type === 'directory') {\n if (item.contents.length === 1 && 'name' in item.contents[0]) {\n const child = item.contents[0];\n\n return toNode({\n ...child,\n name: `${item.name}/${child.name}`,\n });\n }\n\n return `<Folder name=${JSON.stringify(item.name)}>\n${item.contents.map(toNode).filter(Boolean).join('\\n')}\n</Folder>`;\n }\n\n return '';\n }\n\n let children = input.filter((v) => scanned.includes(v.type));\n\n if (noRoot && children.length === 1 && input[0].type === 'directory') {\n children = input[0].contents;\n }\n\n return `<Files>\n${children.map(toNode).filter(Boolean).join('\\n')}\n</Files>`;\n}\n\nexport function treeToJavaScript(\n input: JsonTreeNode[],\n noRoot?: boolean,\n importName = 'fumadocs-ui/components/files',\n): string {\n return `import { File, Files, Folder } from ${JSON.stringify(importName)}\n\nexport default (${treeToMdx(input, noRoot)})`;\n}\n","import { x } from 'tinyexec';\nimport type { JsonTreeNode } from '@/commands/file-tree';\n\nexport async function runTree(args: string): Promise<JsonTreeNode[]> {\n const out = await x('tree', [args, '--gitignore', '--prune', '-J']);\n\n try {\n return JSON.parse(out.stdout) as JsonTreeNode[];\n } catch (e) {\n throw new Error('failed to run `tree` command', {\n cause: e,\n });\n }\n}\n","","import fs from 'node:fs/promises';\nimport path from 'node:path';\nimport { cancel, group, intro, log, outro, select, type Option } from '@clack/prompts';\nimport picocolors from 'picocolors';\nimport { parseSourceFile } from '@/codemod';\nimport { frameworks } from '@/project';\nimport type { Target } from '@/commands/add';\nimport { UIRegistries } from '@/commands/shared';\nimport { LoadedConfig } from '@/config';\nimport { RegistryConnector } from 'fuma-cli/registry/connector';\nimport { FumadocsComponentInstaller } from '@/registry/installer';\n\ninterface TargetInfo {\n targets: Target[];\n id: string;\n print?: () => void | Promise<void>;\n}\n\ninterface SlotPrintInfo {\n at: string;\n layoutId: string;\n name: string;\n isPage: boolean;\n uiLibrary: LoadedConfig['uiLibrary'];\n}\n\nexport async function customise(config: LoadedConfig, connector: RegistryConnector) {\n intro(picocolors.bgBlack(picocolors.whiteBright('Customize Fumadocs UI')));\n\n const installer = new FumadocsComponentInstaller(connector, config);\n const subRegistry = UIRegistries[config.uiLibrary];\n const manifest = await installer.fetchManifest(subRegistry);\n\n const result = await group(\n {\n layout: (): Promise<TargetInfo | symbol> =>\n select({\n message: 'What do you want to customize?',\n options: [\n {\n label: 'Docs Layout',\n value: {\n id: 'docs',\n targets: [{ subRegistry, name: 'layouts/docs' }],\n print: () =>\n printLayout(\n config,\n ['fumadocs-ui/layouts/docs', '@/layouts/docs'],\n ['fumadocs-ui/layouts/docs/page', '@/layouts/docs/page'],\n ),\n },\n hint: 'the default docs layout',\n },\n {\n label: 'Notebook Layout',\n value: {\n id: 'notebook',\n targets: [{ subRegistry, name: 'layouts/notebook' }],\n print: () =>\n printLayout(\n config,\n ['fumadocs-ui/layouts/notebook', '@/layouts/notebook'],\n ['fumadocs-ui/layouts/notebook/page', '@/layouts/notebook/page'],\n ),\n },\n hint: 'a more compact version of docs layout',\n },\n {\n label: 'Flux Layout',\n value: {\n id: 'flux',\n targets: [{ subRegistry, name: 'layouts/flux' }],\n print: () =>\n printLayout(\n config,\n ['fumadocs-ui/layouts/flux', '@/layouts/flux'],\n ['fumadocs-ui/layouts/flux/page', '@/layouts/flux/page'],\n ),\n },\n hint: 'the experimental variant of docs layout',\n },\n {\n label: 'Glass Layout',\n value: {\n id: 'glass',\n targets: [{ subRegistry, name: 'layouts/glass' }],\n print: () =>\n printLayout(\n config,\n ['fumadocs-ui/layouts/glass', '@/layouts/glass'],\n ['fumadocs-ui/layouts/glass/page', '@/layouts/glass/page'],\n ),\n },\n hint: 'a docs layout with floating, translucent panels',\n },\n {\n label: 'Home Layout',\n value: {\n id: 'home',\n targets: [{ subRegistry, name: 'layouts/home' }],\n print: () => printLayout(config, ['fumadocs-ui/layouts/home', '@/layouts/home']),\n },\n hint: 'the layout for other non-docs pages',\n },\n ],\n }),\n target: (v): Promise<TargetInfo | symbol> => {\n const selected = v.results.layout!;\n if (selected.id === 'home') return Promise.resolve(selected);\n\n const slots: Option<TargetInfo>[] = [];\n const prefix = `layouts/${selected.id}/`;\n for (const { name: id } of manifest.components) {\n // <layout>/slots/<name> or <layout>/page/slots/<name>\n const [dir, name] = id.startsWith(prefix) ? id.slice(prefix.length).split('slots/') : [];\n if (name === undefined) continue;\n const isPage = dir === 'page/';\n\n slots.push({\n label: `${isPage ? 'Page' : 'Layout'}: ${name}`,\n hint: `only replace a part of layout${isPage ? \"'s page\" : ''}, useful for adjusting details`,\n value: {\n id,\n targets: [{ subRegistry, name: id }],\n print() {\n printSlot({\n at: `@/${id}`,\n layoutId: selected.id,\n name,\n isPage,\n uiLibrary: config.uiLibrary,\n });\n },\n },\n });\n }\n\n return select<TargetInfo>({\n message: 'Which part do you want to customize?',\n options: [\n {\n label: 'All',\n hint: 'install the entire layout',\n value: selected,\n },\n {\n label: 'Replace & rewrite from minimal styles',\n hint: 'for those who want to build their own UI from ground up',\n value: {\n id: 'docs-min',\n targets: [{ name: 'layouts/docs-min' }],\n print: () =>\n printLayout(\n config,\n ['fumadocs-ui/layouts/docs', '@/layouts/docs'],\n ['fumadocs-ui/layouts/docs/page', '@/layouts/docs/page'],\n ),\n },\n },\n ...slots,\n ],\n });\n },\n },\n {\n onCancel: () => {\n cancel('Installation Stopped.');\n process.exit(0);\n },\n },\n );\n\n const targetInfo = result.target as TargetInfo;\n for (const target of targetInfo.targets) {\n await installer.installInteractive(target.name, target.subRegistry);\n }\n\n await targetInfo.print?.();\n\n outro(picocolors.bold('Have fun!'));\n}\n\n/** point the imports of route files at the installed layouts */\nexport async function rewriteLayoutImports(config: LoadedConfig, map: Map<string, string>) {\n const dir = path.join(config.baseDir, frameworks[config.framework].routesDir);\n const entries = await fs.readdir(dir, { recursive: true, withFileTypes: true }).catch(() => []);\n const updated: string[] = [];\n\n for (const entry of entries) {\n if (!entry.isFile() || !entry.name.endsWith('.tsx')) continue;\n const file = path.join(entry.parentPath, entry.name);\n const source = parseSourceFile(file, await fs.readFile(file, 'utf-8'));\n for (const node of source.program.body) {\n if (node.type !== 'ImportDeclaration') continue;\n const to = map.get(node.source.value);\n if (to) source.s.overwrite(node.source.start + 1, node.source.end - 1, to);\n }\n if (!source.s.hasChanged()) continue;\n await source.save();\n updated.push(file);\n }\n\n return updated;\n}\n\nasync function printLayout(config: LoadedConfig, ...maps: [from: string, to: string][]) {\n intro(picocolors.bold('What is Next?'));\n const updated = await rewriteLayoutImports(config, new Map(maps));\n\n log.info(\n [\n 'You can check the installed layouts in `layouts` folder.',\n picocolors.dim('---'),\n updated.length > 0\n ? `Updated the imports of ${updated.join(', ')}.`\n : 'Open your `layout.tsx` files, replace the imports of components:',\n ...maps.map(([from, to]) => picocolors.greenBright(`\"${from}\" -> \"${to}\"`)),\n ].join('\\n'),\n );\n}\n\nfunction printSlot({ at, layoutId, name, isPage, uiLibrary }: SlotPrintInfo) {\n intro(picocolors.bold('What is Next?'));\n\n log.info(`You can check the installed layout slot in \"${at}\".`);\n\n const code = getSlotCode({ at, layoutId, name, isPage, uiLibrary });\n\n if (code) {\n const layoutComponent = layoutId === 'glass' ? '<GlassLayout />' : '<DocsLayout />';\n\n if (isPage) {\n log.info(\n `${picocolors.bold('At your <DocsPage /> component, update your \"slots\" prop:')}\\n\\n${code}`,\n );\n } else {\n log.info(\n `${picocolors.bold(`At your ${layoutComponent} component, update your \"slots\" prop:`)}\\n\\n${code}`,\n );\n }\n }\n}\n\nfunction getSlotCode({ at, layoutId, name, isPage, uiLibrary }: SlotPrintInfo): string | undefined {\n if (layoutId === 'glass') {\n // Glass layout wires its page slots directly, only layout-level slots are swappable.\n if (isPage) return;\n\n switch (name) {\n case 'header':\n return `import { Header } from '${at}';\n\nreturn (\n <GlassLayout\n slots={{\n header: Header,\n }}\n >\n ...\n </GlassLayout>\n);`;\n case 'sidebar': {\n // `sidebar` slot file holds the whole sidebar system (desktop, mobile drawer, provider).\n // Base UI additionally exposes a `drawerHandle` for its swipeable drawer.\n const imports = ['Sidebar', 'SidebarDrawer', 'SidebarProvider', 'useSidebar'];\n if (uiLibrary === 'base-ui') imports.push('drawerHandle');\n\n return `import { ${imports.join(', ')} } from '${at}';\n\nreturn (\n <GlassLayout\n slots={{\n sidebar: {\n main: Sidebar,\n provider: SidebarProvider,\n use: useSidebar,\n drawer: SidebarDrawer,${uiLibrary === 'base-ui' ? '\\n drawerHandle,' : ''}\n },\n }}\n >\n ...\n </GlassLayout>\n);`;\n }\n default:\n return;\n }\n }\n\n if (isPage) {\n switch (name) {\n case 'toc':\n if (layoutId === 'flux') {\n return `import { TOCProvider, TOC } from '${at}';\n\nreturn (\n <DocsPage\n slots={{\n toc: {\n provider: TOCProvider,\n main: TOC,\n },\n }}\n >\n ...\n </DocsPage>\n);`;\n }\n\n return `import { TOCProvider, TOC, TOCPopover } from '${at}';\n\nreturn (\n <DocsPage\n slots={{\n toc: {\n provider: TOCProvider,\n main: TOC,\n popover: TOCPopover,\n },\n }}\n >\n ...\n </DocsPage>\n);`;\n case 'container': {\n return `import { Container } from '${at}';\n\nreturn (\n <DocsPage\n slots={{\n container: Container,\n }}\n >\n ...\n </DocsPage>\n);`;\n }\n case 'footer': {\n return `import { Footer } from '${at}';\n\nreturn (\n <DocsPage\n slots={{\n footer: Footer,\n }}\n >\n ...\n </DocsPage>\n);`;\n }\n case 'breadcrumb': {\n return `import { Breadcrumb } from '${at}';\n\nreturn (\n <DocsPage\n slots={{\n breadcrumb: Breadcrumb,\n }}\n >\n ...\n </DocsPage>\n);`;\n }\n default:\n return;\n }\n }\n\n switch (name) {\n case 'sidebar': {\n if (layoutId === 'notebook') {\n return `import {\n SidebarProvider,\n Sidebar,\n SidebarTrigger,\n SidebarCollapseTrigger,\n useSidebar,\n} from '${at}';\n\nreturn (\n <DocsLayout\n slots={{\n sidebar: {\n provider: SidebarProvider,\n root: Sidebar,\n trigger: SidebarTrigger,\n collapseTrigger: SidebarCollapseTrigger,\n useSidebar: useSidebar,\n },\n }}\n >\n ...\n </DocsLayout>\n);`;\n }\n\n return `import { SidebarProvider, Sidebar, SidebarTrigger, useSidebar } from '${at}';\n\nreturn (\n <DocsLayout\n slots={{\n sidebar: {\n provider: SidebarProvider,\n root: Sidebar,\n trigger: SidebarTrigger,\n useSidebar: useSidebar,\n },\n }}\n >\n ...\n </DocsLayout>\n);`;\n }\n case 'container': {\n return `import { Container } from '${at}';\n\nreturn (\n <DocsLayout\n slots={{\n container: Container,\n }}\n >\n ...\n </DocsLayout>\n);`;\n }\n case 'header': {\n return `import { Header } from '${at}';\n\nreturn (\n <DocsLayout\n slots={{\n header: Header,\n }}\n >\n ...\n </DocsLayout>\n);`;\n }\n case 'tab-dropdown': {\n return `import { TabDropdown } from '${at}';\n\nreturn (\n <DocsLayout\n slots={{\n tabDropdown: TabDropdown,\n }}\n >\n ...\n </DocsLayout>\n);`;\n }\n default:\n return;\n }\n}\n","import { isCancel as isCancelSymbol } from '@clack/prompts';\n\n/**\n * `@clack/core` narrows to its own `unique symbol`, which can't exclude the wider `symbol` that\n * `@clack/prompts` returns, so a prompt result never narrows without this.\n */\nexport function isCancel<T>(value: T | symbol): value is symbol {\n return isCancelSymbol(value);\n}\n","import { autocompleteMultiselect, outro, spinner } from '@clack/prompts';\nimport { isCancel } from '@/utils/prompt';\nimport picocolors from 'picocolors';\nimport { UIRegistries } from '@/commands/shared';\nimport { RegistryConnector } from 'fuma-cli/registry/connector';\nimport { LoadedConfig } from '@/config';\nimport { FumadocsComponentInstaller } from '@/registry/installer';\n\ninterface AddOption {\n label: string;\n value: Target;\n hint?: string;\n}\n\nexport interface Target {\n name: string;\n subRegistry?: string;\n}\n\nexport async function add(input: string[], connector: RegistryConnector, config: LoadedConfig) {\n let targets: Target[];\n const installer = new FumadocsComponentInstaller(connector, config);\n const subRegistry = UIRegistries[config.uiLibrary];\n\n if (input.length === 0) {\n const spin = spinner();\n spin.start('fetching registry');\n\n async function scan(subRegistry?: string, prefix?: string): Promise<AddOption[]> {\n const manifest = await installer.fetchManifest(subRegistry);\n const options: AddOption[] = [];\n\n for (const item of manifest.components) {\n if (item.unlisted) continue;\n options.push({\n label: `${prefix ? `${picocolors.bold(prefix)} - ` : ''}${item.title ?? item.name}`,\n value: { name: item.name, subRegistry },\n hint: item.description,\n });\n }\n return options;\n }\n\n const groups = await Promise.all([\n scan(undefined, 'common'),\n scan('sanity', 'sanity'),\n scan('openapi', 'openapi'),\n scan('api-docs', 'api-docs'),\n scan(subRegistry, 'ui'),\n ]);\n\n spin.stop(picocolors.bold(picocolors.greenBright('registry fetched')));\n const value = await autocompleteMultiselect({\n message: 'Select components to install',\n options: groups.flat().sort((a, b) => a.label.localeCompare(b.label)),\n });\n\n if (isCancel(value)) {\n outro('Ended');\n return;\n }\n\n targets = value;\n } else {\n const root = new Set<string>();\n for (const item of (await installer.fetchManifest()).components) root.add(item.name);\n targets = input.map((name) => (root.has(name) ? { name } : { subRegistry, name }));\n }\n\n for (const target of targets) {\n await installer.installInteractive(target.name, target.subRegistry);\n }\n\n outro(picocolors.bold(picocolors.greenBright('Successful')));\n}\n","import fs from 'node:fs/promises';\nimport path from 'node:path';\nimport { exists } from '@/utils/fs';\nimport picocolors from 'picocolors';\nimport { spinner } from '@clack/prompts';\nimport { exec } from 'node:child_process';\nimport { promisify } from 'node:util';\n\nconst execAsync = promisify(exec);\n\ninterface PackageJson {\n dependencies?: Record<string, string>;\n devDependencies?: Record<string, string>;\n peerDependencies?: Record<string, string>;\n}\n\nasync function readPackageJson(cwd: string): Promise<PackageJson | null> {\n try {\n const raw = await fs.readFile(path.join(cwd, 'package.json'), 'utf-8');\n return JSON.parse(raw) as PackageJson;\n } catch {\n return null;\n }\n}\n\n/** Path of pre-rendered EPUB, choose one according to your React framework. Next.js fetches from the running server instead. */\nconst EPUB_BUILD_PATHS: Record<string, string> = {\n next: '', // Fetched from /export/epub at runtime; route handlers don't produce static files\n astro: 'dist/export/epub',\n 'tanstack-start': '.output/public/export/epub',\n 'tanstack-start-spa': 'dist/client/export/epub',\n 'react-router': 'build/client/export/epub',\n 'react-router-spa': 'build/client/export/epub',\n waku: 'dist/public/export/epub',\n};\n\nexport async function exportEpub(options: { output?: string; framework: string }) {\n const cwd = process.cwd();\n const outputPath = path.resolve(cwd, options.output ?? 'docs.epub');\n const framework = options.framework;\n\n const spin = spinner();\n\n const buildPath = EPUB_BUILD_PATHS[framework];\n if (!(framework in EPUB_BUILD_PATHS)) {\n const valid = Object.keys(EPUB_BUILD_PATHS).join(', ');\n console.error(picocolors.red(`Invalid --framework \"${framework}\". Must be one of: ${valid}`));\n process.exit(1);\n }\n\n const pkg = await readPackageJson(cwd);\n // Check for fumadocs-epub dependency\n if (!pkg) {\n console.error(\n picocolors.red('Cannot read or parse package.json. Ensure it exists and is valid JSON.'),\n );\n process.exit(1);\n }\n const deps = { ...pkg.dependencies, ...pkg.devDependencies };\n if (!deps['fumadocs-epub']) {\n console.log(picocolors.yellow('\\nInstalling fumadocs-epub...'));\n const packageManager = process.env.npm_execpath?.includes('pnpm')\n ? 'pnpm'\n : process.env.npm_execpath?.includes('bun')\n ? 'bun'\n : 'npm';\n const installCmd = `${packageManager} add fumadocs-epub`;\n try {\n await execAsync(installCmd, { cwd });\n } catch (err: unknown) {\n const stderr =\n err && typeof err === 'object' && 'stderr' in err\n ? String((err as { stderr?: string }).stderr)\n : '';\n console.error(picocolors.red(`Failed to install fumadocs-epub. Command: ${installCmd}`));\n if (stderr) console.error(stderr);\n process.exit(1);\n }\n }\n\n if (framework === 'next') {\n const secret = process.env.EXPORT_SECRET;\n if (!secret) {\n console.error(\n picocolors.red('EXPORT_SECRET is required for Next.js export. Set it in your environment.'),\n );\n process.exit(1);\n }\n const port = process.env.PORT || '3000';\n const url = `http://localhost:${port}/export/epub`;\n spin.start('Fetching EPUB from server');\n const controller = new AbortController();\n const timeoutId = setTimeout(() => controller.abort(), 30_000);\n try {\n const res = await fetch(url, {\n headers: { Authorization: `Bearer ${secret}` },\n signal: controller.signal,\n });\n if (!res.ok) {\n if (res.status === 401 || res.status === 403) {\n console.error(\n picocolors.red('Auth failed. Check that EXPORT_SECRET matches the value in your app.'),\n );\n } else if (res.status === 404) {\n console.error(\n picocolors.red('Export route not found, run `fumadocs feature epub` to create it.'),\n );\n } else {\n console.error(\n picocolors.red(\n `Server returned ${res.status}. Ensure the app is running (e.g. pnpm start) on port ${port}.`,\n ),\n );\n }\n process.exit(1);\n }\n const buffer = Buffer.from(await res.arrayBuffer());\n await fs.mkdir(path.dirname(outputPath), { recursive: true });\n await fs.writeFile(outputPath, buffer);\n spin.stop(picocolors.green(`EPUB saved to ${outputPath}`));\n } catch (err: unknown) {\n if (err instanceof Error && err.name === 'AbortError') {\n console.error(picocolors.red('Request timed out after 30 seconds.'));\n } else {\n const msg = err instanceof Error ? err.message : String(err);\n console.error(picocolors.red(`Could not fetch EPUB: ${msg}`));\n }\n console.error(\n picocolors.yellow(`Ensure the server is running (e.g. pnpm start) on port ${port}.`),\n );\n process.exit(1);\n } finally {\n clearTimeout(timeoutId);\n }\n return;\n }\n\n const fullBuildPath = path.join(cwd, buildPath);\n if (!(await exists(fullBuildPath))) {\n console.error(\n picocolors.red(\n `EPUB not found at ${buildPath}. Run production build first (e.g. pnpm build).`,\n ),\n );\n process.exit(1);\n }\n\n spin.start('Copying EPUB');\n await fs.mkdir(path.dirname(outputPath), { recursive: true });\n await fs.copyFile(fullBuildPath, outputPath);\n spin.stop(picocolors.green(`EPUB saved to ${outputPath}`));\n}\n","import type { CAC, Command } from 'cac';\nimport { cancel, confirm, intro, log, note, outro, select, spinner } from '@clack/prompts';\nimport { isCancel } from '@/utils/prompt';\nimport picocolors from 'picocolors';\nimport type { RegistryConnector } from 'fuma-cli/registry/connector';\nimport { createOrLoadConfig } from '@/config';\nimport { loadProject } from '@/project';\nimport { type AnyFeature, type FeatureIO, runFeature } from '@/features';\nimport { features } from '@/features/all';\n\ninterface CommandOptions extends Record<string, string | boolean | undefined> {\n config?: string;\n dir?: string;\n yes?: boolean;\n install?: boolean;\n}\n\nexport function registerFeatureCommands(\n cli: CAC,\n createConnector: (dir?: string) => RegistryConnector,\n) {\n const ids = features.map((feature) => feature.id).join(', ');\n\n register(\n cli.command('feature <id>', `configure a feature on your project (${ids})`),\n features,\n ).action(async (id: string, options: CommandOptions) => {\n const feature = features.find((item) => item.id === id);\n if (!feature) throw new Error(`unknown feature: ${id}, available: ${ids}`);\n\n await run(feature, options, createConnector(options.dir));\n });\n\n register(cli.command('init', 'set up Fumadocs on an existing app, same as `feature docs`'), [\n features[0],\n ]).action(async (options: CommandOptions) => {\n await run(features[0], options, createConnector(options.dir));\n });\n}\n\nfunction register(command: Command, list: AnyFeature[]) {\n command\n .option('--dir <string>', 'the root url or directory to resolve registry')\n .option('-y, --yes', 'skip prompts, overwrite existing files')\n .option('--no-install', 'write dependencies to package.json without installing');\n\n for (const feature of list) {\n for (const [key, option] of Object.entries(feature.options ?? {})) {\n if ('choices' in option) {\n command.option(\n `--${key} <value>`,\n `${option.message} (${option.choices.map((choice) => choice.value).join(', ')})`,\n );\n } else {\n command.option(`--${key}`, option.message);\n }\n }\n }\n\n return command;\n}\n\nasync function run(feature: AnyFeature, options: CommandOptions, connector: RegistryConnector) {\n intro(picocolors.bgBlack(picocolors.whiteBright(feature.title)));\n const config = await createOrLoadConfig(options.config);\n const project = await loadProject(config);\n log.info(`${project.framework}${project.static ? ' (static)' : ''}, ${project.packageManager}`);\n\n const values: Record<string, unknown> = {};\n for (const [key, option] of Object.entries(feature.options ?? {})) {\n const given = options[key];\n if ('choices' in option) {\n if (given !== undefined) {\n if (!option.choices.some((choice) => choice.value === given))\n throw new Error(`invalid value for --${key}: ${given}`);\n values[key] = given;\n continue;\n }\n\n const value = await select({ message: option.message, options: option.choices });\n if (isCancel(value)) {\n cancel('Stopped.');\n process.exit(0);\n }\n values[key] = value;\n } else if (given !== undefined) {\n values[key] = given === true;\n } else if (options.yes) {\n values[key] = option.initialValue ?? false;\n } else {\n const value = await confirm({ message: option.message, initialValue: option.initialValue });\n if (isCancel(value)) {\n cancel('Stopped.');\n process.exit(0);\n }\n values[key] = value;\n }\n }\n\n const spin = spinner();\n const io: FeatureIO = {\n installDependencies: options.install !== false,\n log: (message) => spin.message(message),\n async confirmOverwrite(file) {\n if (options.yes) return true;\n spin.clear();\n const value = await confirm({ message: `Overwrite ${file}?`, initialValue: false });\n if (isCancel(value)) {\n cancel('Stopped.');\n process.exit(0);\n }\n spin.start();\n return value;\n },\n };\n\n spin.start(`Applying ${feature.title}`);\n try {\n const { notes } = await runFeature(feature, values, { project, connector, io });\n spin.stop(`${feature.title} applied`);\n if (notes.length > 0) note(notes.join('\\n\\n'), 'What is Next?');\n } catch (e) {\n spin.error(e instanceof Error ? e.message : String(e));\n process.exit(1);\n }\n outro(picocolors.bold(picocolors.greenBright('Done')));\n}\n","#!/usr/bin/env node\nimport fs from 'node:fs/promises';\nimport path from 'node:path';\nimport { cac } from 'cac';\nimport picocolors from 'picocolors';\nimport { createOrLoadConfig, initConfig } from '@/config';\nimport { type JsonTreeNode, treeToJavaScript, treeToMdx } from '@/commands/file-tree';\nimport { runTree } from '@/utils/file-tree/run-tree';\nimport packageJson from '../package.json';\nimport { customise } from '@/commands/customise';\nimport { add } from '@/commands/add';\nimport { exportEpub } from '@/commands/export-epub';\nimport { registerFeatureCommands } from '@/commands/feature';\nimport { HttpRegistryConnector, LocalRegistryConnector } from 'fuma-cli/registry/connector';\n\nconst cli = cac('fumadocs');\ncli.option('--config <string>', 'path to the config file');\n\ncli.command('', 'init a `cli.json` config file').action(async () => {\n if (await initConfig()) {\n console.log(picocolors.green('Initialized a `./cli.json` config file.'));\n } else {\n console.log(picocolors.redBright('A config file already exists.'));\n }\n});\n\ncli\n .command('customise', 'simple way to customize layouts with Fumadocs UI')\n .alias('customize')\n .option('--dir <string>', 'the root url or directory to resolve registry')\n .action(async (options: { config?: string; dir?: string }) => {\n const config = await createOrLoadConfig(options.config);\n await customise(config, createClientFromDir(options.dir));\n });\n\nconst dirShortcuts: Record<string, string> = {\n ':preview': 'https://preview.fumadocs.dev/registry',\n ':dev': 'http://localhost:3000/registry',\n};\n\ncli\n .command('add [...components]', 'add a new component to your docs')\n .option('--dir <string>', 'the root url or directory to resolve registry')\n .action(async (input: string[], options: { config?: string; dir?: string }) => {\n const config = await createOrLoadConfig(options.config);\n const client = createClientFromDir(options.dir);\n await add(input, client, config);\n });\n\nregisterFeatureCommands(cli, createClientFromDir);\n\ncli\n .command(\n 'export <format>',\n 'export documentation to various formats (run after production build)',\n )\n .option('--framework <name>', 'framework: next, astro, tanstack-start, react-router, waku')\n .option('--output <path>', 'output file path', { default: 'docs.epub' })\n .action(async (format: string, options: { framework?: string; output: string }) => {\n if (format !== 'epub')\n throw new Error(`unsupported format: ${format}, only \\`epub\\` is supported.`);\n if (!options.framework) throw new Error('option `--framework <name>` is required.');\n\n await exportEpub({ framework: options.framework, output: options.output });\n });\n\ncli\n .command(\n 'tree [json_or_args] [output]',\n 'generate a file tree for the Files component, from a directory or JSON output of `tree`',\n )\n .option('--js', 'output as JavaScript file')\n .option('--no-root', 'remove the root node')\n .option('--import-name <name>', 'where to import components (JS only)')\n .action(\n async (\n str: string | undefined,\n output: string | undefined,\n { js, root, importName }: { js: boolean; root: boolean; importName?: string },\n ) => {\n const jsExtensions = ['.js', '.tsx', '.jsx'];\n const noRoot = !root;\n let nodes: JsonTreeNode[];\n\n try {\n nodes = JSON.parse(str ?? '') as JsonTreeNode[];\n } catch {\n nodes = await runTree(str ?? './');\n }\n\n const out =\n js || (output && jsExtensions.includes(path.extname(output)))\n ? treeToJavaScript(nodes, noRoot, importName)\n : treeToMdx(nodes, noRoot);\n\n if (output) {\n await fs.mkdir(path.dirname(output), { recursive: true });\n await fs.writeFile(output, out);\n } else {\n console.log(out);\n }\n },\n );\n\nfunction createClientFromDir(dir = 'https://fumadocs.dev/registry') {\n if (dir in dirShortcuts) dir = dirShortcuts[dir];\n\n return dir.startsWith('http://') || dir.startsWith('https://')\n ? new HttpRegistryConnector(dir)\n : new LocalRegistryConnector(dir);\n}\n\ncli.help();\ncli.version(packageJson.version);\n\ntry {\n cli.parse(process.argv, { run: false });\n await cli.runMatchedCommand();\n} catch (e) {\n console.error(picocolors.redBright(e instanceof Error ? e.message : String(e)));\n process.exit(1);\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.js","names":["isCancel","isCancelSymbol","isCancel","isCancel","packageJson.version"],"sources":["../src/commands/file-tree.ts","../src/utils/file-tree/run-tree.ts","../package.json","../src/commands/customise.ts","../src/utils/prompt.ts","../src/commands/add.ts","../src/commands/export-epub.ts","../src/commands/feature.ts","../src/index.ts"],"sourcesContent":["export type JsonTreeNode =\n | {\n type: 'file';\n name: string;\n }\n | {\n type: 'directory';\n name: string;\n contents: JsonTreeNode[];\n }\n | {\n type: 'report';\n }\n | {\n type: 'link';\n name: string;\n };\n\nconst scanned = ['file', 'directory', 'link'];\n\nexport function treeToMdx(input: JsonTreeNode[], noRoot = false): string {\n function toNode(item: JsonTreeNode): string {\n if (item.type === 'file' || item.type === 'link') {\n return `<File name=${JSON.stringify(item.name)} />`;\n }\n\n if (item.type === 'directory') {\n if (item.contents.length === 1 && 'name' in item.contents[0]) {\n const child = item.contents[0];\n\n return toNode({\n ...child,\n name: `${item.name}/${child.name}`,\n });\n }\n\n return `<Folder name=${JSON.stringify(item.name)}>\n${item.contents.map(toNode).filter(Boolean).join('\\n')}\n</Folder>`;\n }\n\n return '';\n }\n\n let children = input.filter((v) => scanned.includes(v.type));\n\n if (noRoot && children.length === 1 && input[0].type === 'directory') {\n children = input[0].contents;\n }\n\n return `<Files>\n${children.map(toNode).filter(Boolean).join('\\n')}\n</Files>`;\n}\n\nexport function treeToJavaScript(\n input: JsonTreeNode[],\n noRoot?: boolean,\n importName = 'fumadocs-ui/components/files',\n): string {\n return `import { File, Files, Folder } from ${JSON.stringify(importName)}\n\nexport default (${treeToMdx(input, noRoot)})`;\n}\n","import { x } from 'tinyexec';\nimport type { JsonTreeNode } from '@/commands/file-tree';\n\nexport async function runTree(args: string): Promise<JsonTreeNode[]> {\n const out = await x('tree', [args, '--gitignore', '--prune', '-J']);\n\n try {\n return JSON.parse(out.stdout) as JsonTreeNode[];\n } catch (e) {\n throw new Error('failed to run `tree` command', {\n cause: e,\n });\n }\n}\n","","import fs from 'node:fs/promises';\nimport path from 'node:path';\nimport { cancel, group, intro, log, outro, select, type Option } from '@clack/prompts';\nimport picocolors from 'picocolors';\nimport { parseSourceFile } from '@/codemod';\nimport { frameworks } from '@/project';\nimport type { Target } from '@/commands/add';\nimport { UIRegistries } from '@/commands/shared';\nimport { LoadedConfig } from '@/config';\nimport { RegistryConnector } from 'fuma-cli/registry/connector';\nimport { FumadocsComponentInstaller } from '@/registry/installer';\n\ninterface TargetInfo {\n targets: Target[];\n id: string;\n print?: () => void | Promise<void>;\n}\n\ninterface SlotPrintInfo {\n at: string;\n layoutId: string;\n name: string;\n isPage: boolean;\n uiLibrary: LoadedConfig['uiLibrary'];\n}\n\nexport async function customise(config: LoadedConfig, connector: RegistryConnector) {\n intro(picocolors.bgBlack(picocolors.whiteBright('Customize Fumadocs UI')));\n\n const installer = new FumadocsComponentInstaller(connector, config);\n const subRegistry = UIRegistries[config.uiLibrary];\n const manifest = await installer.fetchManifest(subRegistry);\n\n const result = await group(\n {\n layout: (): Promise<TargetInfo | symbol> =>\n select({\n message: 'What do you want to customize?',\n options: [\n {\n label: 'Docs Layout',\n value: {\n id: 'docs',\n targets: [{ subRegistry, name: 'layouts/docs' }],\n print: () =>\n printLayout(\n config,\n ['fumadocs-ui/layouts/docs', '@/layouts/docs'],\n ['fumadocs-ui/layouts/docs/page', '@/layouts/docs/page'],\n ),\n },\n hint: 'the default docs layout',\n },\n {\n label: 'Notebook Layout',\n value: {\n id: 'notebook',\n targets: [{ subRegistry, name: 'layouts/notebook' }],\n print: () =>\n printLayout(\n config,\n ['fumadocs-ui/layouts/notebook', '@/layouts/notebook'],\n ['fumadocs-ui/layouts/notebook/page', '@/layouts/notebook/page'],\n ),\n },\n hint: 'a more compact version of docs layout',\n },\n {\n label: 'Flux Layout',\n value: {\n id: 'flux',\n targets: [{ subRegistry, name: 'layouts/flux' }],\n print: () =>\n printLayout(\n config,\n ['fumadocs-ui/layouts/flux', '@/layouts/flux'],\n ['fumadocs-ui/layouts/flux/page', '@/layouts/flux/page'],\n ),\n },\n hint: 'the experimental variant of docs layout',\n },\n {\n label: 'Glass Layout',\n value: {\n id: 'glass',\n targets: [{ subRegistry, name: 'layouts/glass' }],\n print: () =>\n printLayout(\n config,\n ['fumadocs-ui/layouts/glass', '@/layouts/glass'],\n ['fumadocs-ui/layouts/glass/page', '@/layouts/glass/page'],\n ),\n },\n hint: 'a docs layout with floating, translucent panels',\n },\n ...(config.uiLibrary === 'base-ui'\n ? [\n {\n label: 'Spacious Layout',\n value: {\n id: 'spacious',\n targets: [{ subRegistry, name: 'layouts/spacious' }],\n print: () =>\n printLayout(\n config,\n ['fumadocs-ui/layouts/spacious', '@/layouts/spacious'],\n ['fumadocs-ui/layouts/spacious/page', '@/layouts/spacious/page'],\n ),\n },\n hint: 'a docs layout with the page in an inset panel, Base UI only',\n },\n ]\n : []),\n {\n label: 'Home Layout',\n value: {\n id: 'home',\n targets: [{ subRegistry, name: 'layouts/home' }],\n print: () => printLayout(config, ['fumadocs-ui/layouts/home', '@/layouts/home']),\n },\n hint: 'the layout for other non-docs pages',\n },\n ],\n }),\n target: (v): Promise<TargetInfo | symbol> => {\n const selected = v.results.layout!;\n if (selected.id === 'home') return Promise.resolve(selected);\n\n const slots: Option<TargetInfo>[] = [];\n const prefix = `layouts/${selected.id}/`;\n for (const { name: id } of manifest.components) {\n // <layout>/slots/<name> or <layout>/page/slots/<name>\n const [dir, name] = id.startsWith(prefix) ? id.slice(prefix.length).split('slots/') : [];\n if (name === undefined) continue;\n const isPage = dir === 'page/';\n\n slots.push({\n label: `${isPage ? 'Page' : 'Layout'}: ${name}`,\n hint: `only replace a part of layout${isPage ? \"'s page\" : ''}, useful for adjusting details`,\n value: {\n id,\n targets: [{ subRegistry, name: id }],\n print() {\n printSlot({\n at: `@/${id}`,\n layoutId: selected.id,\n name,\n isPage,\n uiLibrary: config.uiLibrary,\n });\n },\n },\n });\n }\n\n return select<TargetInfo>({\n message: 'Which part do you want to customize?',\n options: [\n {\n label: 'All',\n hint: 'install the entire layout',\n value: selected,\n },\n {\n label: 'Replace & rewrite from minimal styles',\n hint: 'for those who want to build their own UI from ground up',\n value: {\n id: 'docs-min',\n targets: [{ name: 'layouts/docs-min' }],\n print: () =>\n printLayout(\n config,\n ['fumadocs-ui/layouts/docs', '@/layouts/docs'],\n ['fumadocs-ui/layouts/docs/page', '@/layouts/docs/page'],\n ),\n },\n },\n ...slots,\n ],\n });\n },\n },\n {\n onCancel: () => {\n cancel('Installation Stopped.');\n process.exit(0);\n },\n },\n );\n\n const targetInfo = result.target as TargetInfo;\n for (const target of targetInfo.targets) {\n await installer.installInteractive(target.name, target.subRegistry);\n }\n\n await targetInfo.print?.();\n\n outro(picocolors.bold('Have fun!'));\n}\n\n/** point the imports of route files at the installed layouts */\nexport async function rewriteLayoutImports(config: LoadedConfig, map: Map<string, string>) {\n const dir = path.join(config.baseDir, frameworks[config.framework].routesDir);\n const entries = await fs.readdir(dir, { recursive: true, withFileTypes: true }).catch(() => []);\n const updated: string[] = [];\n\n for (const entry of entries) {\n if (!entry.isFile() || !entry.name.endsWith('.tsx')) continue;\n const file = path.join(entry.parentPath, entry.name);\n const source = parseSourceFile(file, await fs.readFile(file, 'utf-8'));\n for (const node of source.program.body) {\n if (node.type !== 'ImportDeclaration') continue;\n const to = map.get(node.source.value);\n if (to) source.s.overwrite(node.source.start + 1, node.source.end - 1, to);\n }\n if (!source.s.hasChanged()) continue;\n await source.save();\n updated.push(file);\n }\n\n return updated;\n}\n\nasync function printLayout(config: LoadedConfig, ...maps: [from: string, to: string][]) {\n intro(picocolors.bold('What is Next?'));\n const updated = await rewriteLayoutImports(config, new Map(maps));\n\n log.info(\n [\n 'You can check the installed layouts in `layouts` folder.',\n picocolors.dim('---'),\n updated.length > 0\n ? `Updated the imports of ${updated.join(', ')}.`\n : 'Open your `layout.tsx` files, replace the imports of components:',\n ...maps.map(([from, to]) => picocolors.greenBright(`\"${from}\" -> \"${to}\"`)),\n ].join('\\n'),\n );\n}\n\nfunction printSlot({ at, layoutId, name, isPage, uiLibrary }: SlotPrintInfo) {\n intro(picocolors.bold('What is Next?'));\n\n log.info(`You can check the installed layout slot in \"${at}\".`);\n\n const slot = getSlot({ layoutId, name, isPage, uiLibrary });\n if (!slot) return;\n\n const [imports, value] = slot;\n // every layout names its components `DocsLayout` and `DocsPage`\n const component = isPage ? 'DocsPage' : 'DocsLayout';\n log.info(`${picocolors.bold(`At your <${component} /> component, update your \"slots\" prop:`)}\n\nimport { ${imports} } from '${at}';\n\nreturn (\n <${component}\n slots={{\n ${value},\n }}\n >\n ...\n </${component}>\n);`);\n}\n\n/** the names to import from slot file, and the entry of `slots` prop */\nfunction getSlot({\n layoutId,\n name,\n isPage,\n uiLibrary,\n}: Omit<SlotPrintInfo, 'at'>): [imports: string, value: string] | undefined {\n if (isPage) {\n // Glass layout wires its page slots directly, only layout-level slots are swappable.\n if (layoutId === 'glass') return;\n\n switch (name) {\n case 'toc':\n if (layoutId === 'flux')\n return [\n 'TOCProvider, TOC',\n `toc: {\n provider: TOCProvider,\n main: TOC,\n }`,\n ];\n if (layoutId === 'spacious')\n return [\n 'TOCProvider, TOC, TOCPopover, TOCDropdown',\n `toc: {\n provider: TOCProvider,\n main: TOC,\n popover: TOCPopover,\n dropdown: TOCDropdown,\n }`,\n ];\n return [\n 'TOCProvider, TOC, TOCPopover',\n `toc: {\n provider: TOCProvider,\n main: TOC,\n popover: TOCPopover,\n }`,\n ];\n case 'container':\n return ['Container', 'container: Container'];\n case 'footer':\n return ['Footer', 'footer: Footer'];\n case 'breadcrumb':\n return ['Breadcrumb', 'breadcrumb: Breadcrumb'];\n default:\n return;\n }\n }\n\n switch (name) {\n case 'sidebar':\n if (layoutId === 'glass') {\n // the slot file holds the whole sidebar system, Base UI additionally exposes a `drawerHandle` for its swipeable drawer\n const drawerHandle = uiLibrary === 'base-ui';\n return [\n `Sidebar, SidebarDrawer, SidebarProvider, useSidebar${drawerHandle ? ', drawerHandle' : ''}`,\n `sidebar: {\n main: Sidebar,\n provider: SidebarProvider,\n use: useSidebar,\n drawer: SidebarDrawer,${drawerHandle ? '\\n drawerHandle,' : ''}\n }`,\n ];\n }\n if (layoutId === 'spacious')\n return [\n 'Sidebar, SidebarDrawer, SidebarProvider',\n `sidebar: {\n provider: SidebarProvider,\n main: Sidebar,\n drawer: SidebarDrawer,\n }`,\n ];\n if (layoutId === 'notebook')\n return [\n 'SidebarProvider, Sidebar, SidebarTrigger, SidebarCollapseTrigger, useSidebar',\n `sidebar: {\n provider: SidebarProvider,\n root: Sidebar,\n trigger: SidebarTrigger,\n collapseTrigger: SidebarCollapseTrigger,\n useSidebar: useSidebar,\n }`,\n ];\n return [\n 'SidebarProvider, Sidebar, SidebarTrigger, useSidebar',\n `sidebar: {\n provider: SidebarProvider,\n root: Sidebar,\n trigger: SidebarTrigger,\n useSidebar: useSidebar,\n }`,\n ];\n case 'header':\n return ['Header', 'header: Header'];\n case 'container':\n return ['Container', 'container: Container'];\n case 'tab-dropdown':\n return ['TabDropdown', 'tabDropdown: TabDropdown'];\n case 'actions':\n return ['HeaderActions', 'actions: HeaderActions'];\n default:\n return;\n }\n}\n","import { isCancel as isCancelSymbol } from '@clack/prompts';\n\n/**\n * `@clack/core` narrows to its own `unique symbol`, which can't exclude the wider `symbol` that\n * `@clack/prompts` returns, so a prompt result never narrows without this.\n */\nexport function isCancel<T>(value: T | symbol): value is symbol {\n return isCancelSymbol(value);\n}\n","import { autocompleteMultiselect, outro, spinner } from '@clack/prompts';\nimport { isCancel } from '@/utils/prompt';\nimport picocolors from 'picocolors';\nimport { UIRegistries } from '@/commands/shared';\nimport { RegistryConnector } from 'fuma-cli/registry/connector';\nimport { LoadedConfig } from '@/config';\nimport { FumadocsComponentInstaller } from '@/registry/installer';\n\ninterface AddOption {\n label: string;\n value: Target;\n hint?: string;\n}\n\nexport interface Target {\n name: string;\n subRegistry?: string;\n}\n\nexport async function add(input: string[], connector: RegistryConnector, config: LoadedConfig) {\n let targets: Target[];\n const installer = new FumadocsComponentInstaller(connector, config);\n const subRegistry = UIRegistries[config.uiLibrary];\n\n if (input.length === 0) {\n const spin = spinner();\n spin.start('fetching registry');\n\n async function scan(subRegistry?: string, prefix?: string): Promise<AddOption[]> {\n const manifest = await installer.fetchManifest(subRegistry);\n const options: AddOption[] = [];\n\n for (const item of manifest.components) {\n if (item.unlisted) continue;\n options.push({\n label: `${prefix ? `${picocolors.bold(prefix)} - ` : ''}${item.title ?? item.name}`,\n value: { name: item.name, subRegistry },\n hint: item.description,\n });\n }\n return options;\n }\n\n const groups = await Promise.all([\n scan(undefined, 'common'),\n scan('sanity', 'sanity'),\n scan('openapi', 'openapi'),\n scan('api-docs', 'api-docs'),\n scan(subRegistry, 'ui'),\n ]);\n\n spin.stop(picocolors.bold(picocolors.greenBright('registry fetched')));\n const value = await autocompleteMultiselect({\n message: 'Select components to install',\n options: groups.flat().sort((a, b) => a.label.localeCompare(b.label)),\n });\n\n if (isCancel(value)) {\n outro('Ended');\n return;\n }\n\n targets = value;\n } else {\n const root = new Set<string>();\n for (const item of (await installer.fetchManifest()).components) root.add(item.name);\n targets = input.map((name) => (root.has(name) ? { name } : { subRegistry, name }));\n }\n\n for (const target of targets) {\n await installer.installInteractive(target.name, target.subRegistry);\n }\n\n outro(picocolors.bold(picocolors.greenBright('Successful')));\n}\n","import fs from 'node:fs/promises';\nimport path from 'node:path';\nimport { exists } from '@/utils/fs';\nimport picocolors from 'picocolors';\nimport { spinner } from '@clack/prompts';\nimport { exec } from 'node:child_process';\nimport { promisify } from 'node:util';\n\nconst execAsync = promisify(exec);\n\ninterface PackageJson {\n dependencies?: Record<string, string>;\n devDependencies?: Record<string, string>;\n peerDependencies?: Record<string, string>;\n}\n\nasync function readPackageJson(cwd: string): Promise<PackageJson | null> {\n try {\n const raw = await fs.readFile(path.join(cwd, 'package.json'), 'utf-8');\n return JSON.parse(raw) as PackageJson;\n } catch {\n return null;\n }\n}\n\n/** Path of pre-rendered EPUB, choose one according to your React framework. Next.js fetches from the running server instead. */\nconst EPUB_BUILD_PATHS: Record<string, string> = {\n next: '', // Fetched from /export/epub at runtime; route handlers don't produce static files\n astro: 'dist/export/epub',\n 'tanstack-start': '.output/public/export/epub',\n 'tanstack-start-spa': 'dist/client/export/epub',\n 'react-router': 'build/client/export/epub',\n 'react-router-spa': 'build/client/export/epub',\n waku: 'dist/public/export/epub',\n};\n\nexport async function exportEpub(options: { output?: string; framework: string }) {\n const cwd = process.cwd();\n const outputPath = path.resolve(cwd, options.output ?? 'docs.epub');\n const framework = options.framework;\n\n const spin = spinner();\n\n const buildPath = EPUB_BUILD_PATHS[framework];\n if (!(framework in EPUB_BUILD_PATHS)) {\n const valid = Object.keys(EPUB_BUILD_PATHS).join(', ');\n console.error(picocolors.red(`Invalid --framework \"${framework}\". Must be one of: ${valid}`));\n process.exit(1);\n }\n\n const pkg = await readPackageJson(cwd);\n // Check for fumadocs-epub dependency\n if (!pkg) {\n console.error(\n picocolors.red('Cannot read or parse package.json. Ensure it exists and is valid JSON.'),\n );\n process.exit(1);\n }\n const deps = { ...pkg.dependencies, ...pkg.devDependencies };\n if (!deps['fumadocs-epub']) {\n console.log(picocolors.yellow('\\nInstalling fumadocs-epub...'));\n const packageManager = process.env.npm_execpath?.includes('pnpm')\n ? 'pnpm'\n : process.env.npm_execpath?.includes('bun')\n ? 'bun'\n : 'npm';\n const installCmd = `${packageManager} add fumadocs-epub`;\n try {\n await execAsync(installCmd, { cwd });\n } catch (err: unknown) {\n const stderr =\n err && typeof err === 'object' && 'stderr' in err\n ? String((err as { stderr?: string }).stderr)\n : '';\n console.error(picocolors.red(`Failed to install fumadocs-epub. Command: ${installCmd}`));\n if (stderr) console.error(stderr);\n process.exit(1);\n }\n }\n\n if (framework === 'next') {\n const secret = process.env.EXPORT_SECRET;\n if (!secret) {\n console.error(\n picocolors.red('EXPORT_SECRET is required for Next.js export. Set it in your environment.'),\n );\n process.exit(1);\n }\n const port = process.env.PORT || '3000';\n const url = `http://localhost:${port}/export/epub`;\n spin.start('Fetching EPUB from server');\n const controller = new AbortController();\n const timeoutId = setTimeout(() => controller.abort(), 30_000);\n try {\n const res = await fetch(url, {\n headers: { Authorization: `Bearer ${secret}` },\n signal: controller.signal,\n });\n if (!res.ok) {\n if (res.status === 401 || res.status === 403) {\n console.error(\n picocolors.red('Auth failed. Check that EXPORT_SECRET matches the value in your app.'),\n );\n } else if (res.status === 404) {\n console.error(\n picocolors.red('Export route not found, run `fumadocs feature epub` to create it.'),\n );\n } else {\n console.error(\n picocolors.red(\n `Server returned ${res.status}. Ensure the app is running (e.g. pnpm start) on port ${port}.`,\n ),\n );\n }\n process.exit(1);\n }\n const buffer = Buffer.from(await res.arrayBuffer());\n await fs.mkdir(path.dirname(outputPath), { recursive: true });\n await fs.writeFile(outputPath, buffer);\n spin.stop(picocolors.green(`EPUB saved to ${outputPath}`));\n } catch (err: unknown) {\n if (err instanceof Error && err.name === 'AbortError') {\n console.error(picocolors.red('Request timed out after 30 seconds.'));\n } else {\n const msg = err instanceof Error ? err.message : String(err);\n console.error(picocolors.red(`Could not fetch EPUB: ${msg}`));\n }\n console.error(\n picocolors.yellow(`Ensure the server is running (e.g. pnpm start) on port ${port}.`),\n );\n process.exit(1);\n } finally {\n clearTimeout(timeoutId);\n }\n return;\n }\n\n const fullBuildPath = path.join(cwd, buildPath);\n if (!(await exists(fullBuildPath))) {\n console.error(\n picocolors.red(\n `EPUB not found at ${buildPath}. Run production build first (e.g. pnpm build).`,\n ),\n );\n process.exit(1);\n }\n\n spin.start('Copying EPUB');\n await fs.mkdir(path.dirname(outputPath), { recursive: true });\n await fs.copyFile(fullBuildPath, outputPath);\n spin.stop(picocolors.green(`EPUB saved to ${outputPath}`));\n}\n","import type { CAC, Command } from 'cac';\nimport { cancel, confirm, intro, log, note, outro, select, spinner } from '@clack/prompts';\nimport { isCancel } from '@/utils/prompt';\nimport picocolors from 'picocolors';\nimport type { RegistryConnector } from 'fuma-cli/registry/connector';\nimport { createOrLoadConfig } from '@/config';\nimport { loadProject } from '@/project';\nimport { type AnyFeature, type FeatureIO, runFeature } from '@/features';\nimport { features } from '@/features/all';\n\ninterface CommandOptions extends Record<string, string | boolean | undefined> {\n config?: string;\n dir?: string;\n yes?: boolean;\n install?: boolean;\n}\n\nexport function registerFeatureCommands(\n cli: CAC,\n createConnector: (dir?: string) => RegistryConnector,\n) {\n const ids = features.map((feature) => feature.id).join(', ');\n\n register(\n cli.command('feature <id>', `configure a feature on your project (${ids})`),\n features,\n ).action(async (id: string, options: CommandOptions) => {\n const feature = features.find((item) => item.id === id);\n if (!feature) throw new Error(`unknown feature: ${id}, available: ${ids}`);\n\n await run(feature, options, createConnector(options.dir));\n });\n\n register(cli.command('init', 'set up Fumadocs on an existing app, same as `feature docs`'), [\n features[0],\n ]).action(async (options: CommandOptions) => {\n await run(features[0], options, createConnector(options.dir));\n });\n}\n\nfunction register(command: Command, list: AnyFeature[]) {\n command\n .option('--dir <string>', 'the root url or directory to resolve registry')\n .option('-y, --yes', 'skip prompts, overwrite existing files')\n .option('--no-install', 'write dependencies to package.json without installing');\n\n for (const feature of list) {\n for (const [key, option] of Object.entries(feature.options ?? {})) {\n if ('choices' in option) {\n command.option(\n `--${key} <value>`,\n `${option.message} (${option.choices.map((choice) => choice.value).join(', ')})`,\n );\n } else {\n command.option(`--${key}`, option.message);\n }\n }\n }\n\n return command;\n}\n\nasync function run(feature: AnyFeature, options: CommandOptions, connector: RegistryConnector) {\n intro(picocolors.bgBlack(picocolors.whiteBright(feature.title)));\n const config = await createOrLoadConfig(options.config);\n const project = await loadProject(config);\n log.info(`${project.framework}${project.static ? ' (static)' : ''}, ${project.packageManager}`);\n\n const values: Record<string, unknown> = {};\n for (const [key, option] of Object.entries(feature.options ?? {})) {\n const given = options[key];\n if ('choices' in option) {\n if (given !== undefined) {\n if (!option.choices.some((choice) => choice.value === given))\n throw new Error(`invalid value for --${key}: ${given}`);\n values[key] = given;\n continue;\n }\n\n const value = await select({ message: option.message, options: option.choices });\n if (isCancel(value)) {\n cancel('Stopped.');\n process.exit(0);\n }\n values[key] = value;\n } else if (given !== undefined) {\n values[key] = given === true;\n } else if (options.yes) {\n values[key] = option.initialValue ?? false;\n } else {\n const value = await confirm({ message: option.message, initialValue: option.initialValue });\n if (isCancel(value)) {\n cancel('Stopped.');\n process.exit(0);\n }\n values[key] = value;\n }\n }\n\n const spin = spinner();\n const io: FeatureIO = {\n installDependencies: options.install !== false,\n log: (message) => spin.message(message),\n async confirmOverwrite(file) {\n if (options.yes) return true;\n spin.clear();\n const value = await confirm({ message: `Overwrite ${file}?`, initialValue: false });\n if (isCancel(value)) {\n cancel('Stopped.');\n process.exit(0);\n }\n spin.start();\n return value;\n },\n };\n\n spin.start(`Applying ${feature.title}`);\n try {\n const { notes } = await runFeature(feature, values, { project, connector, io });\n spin.stop(`${feature.title} applied`);\n if (notes.length > 0) note(notes.join('\\n\\n'), 'What is Next?');\n } catch (e) {\n spin.error(e instanceof Error ? e.message : String(e));\n process.exit(1);\n }\n outro(picocolors.bold(picocolors.greenBright('Done')));\n}\n","#!/usr/bin/env node\nimport fs from 'node:fs/promises';\nimport path from 'node:path';\nimport { cac } from 'cac';\nimport picocolors from 'picocolors';\nimport { createOrLoadConfig, initConfig } from '@/config';\nimport { type JsonTreeNode, treeToJavaScript, treeToMdx } from '@/commands/file-tree';\nimport { runTree } from '@/utils/file-tree/run-tree';\nimport packageJson from '../package.json';\nimport { customise } from '@/commands/customise';\nimport { add } from '@/commands/add';\nimport { exportEpub } from '@/commands/export-epub';\nimport { registerFeatureCommands } from '@/commands/feature';\nimport { HttpRegistryConnector, LocalRegistryConnector } from 'fuma-cli/registry/connector';\n\nconst cli = cac('fumadocs');\ncli.option('--config <string>', 'path to the config file');\n\ncli.command('', 'init a `cli.json` config file').action(async () => {\n if (await initConfig()) {\n console.log(picocolors.green('Initialized a `./cli.json` config file.'));\n } else {\n console.log(picocolors.redBright('A config file already exists.'));\n }\n});\n\ncli\n .command('customise', 'simple way to customize layouts with Fumadocs UI')\n .alias('customize')\n .option('--dir <string>', 'the root url or directory to resolve registry')\n .action(async (options: { config?: string; dir?: string }) => {\n const config = await createOrLoadConfig(options.config);\n await customise(config, createClientFromDir(options.dir));\n });\n\nconst dirShortcuts: Record<string, string> = {\n ':preview': 'https://preview.fumadocs.dev/registry',\n ':dev': 'http://localhost:3000/registry',\n};\n\ncli\n .command('add [...components]', 'add a new component to your docs')\n .option('--dir <string>', 'the root url or directory to resolve registry')\n .action(async (input: string[], options: { config?: string; dir?: string }) => {\n const config = await createOrLoadConfig(options.config);\n const client = createClientFromDir(options.dir);\n await add(input, client, config);\n });\n\nregisterFeatureCommands(cli, createClientFromDir);\n\ncli\n .command(\n 'export <format>',\n 'export documentation to various formats (run after production build)',\n )\n .option('--framework <name>', 'framework: next, astro, tanstack-start, react-router, waku')\n .option('--output <path>', 'output file path', { default: 'docs.epub' })\n .action(async (format: string, options: { framework?: string; output: string }) => {\n if (format !== 'epub')\n throw new Error(`unsupported format: ${format}, only \\`epub\\` is supported.`);\n if (!options.framework) throw new Error('option `--framework <name>` is required.');\n\n await exportEpub({ framework: options.framework, output: options.output });\n });\n\ncli\n .command(\n 'tree [json_or_args] [output]',\n 'generate a file tree for the Files component, from a directory or JSON output of `tree`',\n )\n .option('--js', 'output as JavaScript file')\n .option('--no-root', 'remove the root node')\n .option('--import-name <name>', 'where to import components (JS only)')\n .action(\n async (\n str: string | undefined,\n output: string | undefined,\n { js, root, importName }: { js: boolean; root: boolean; importName?: string },\n ) => {\n const jsExtensions = ['.js', '.tsx', '.jsx'];\n const noRoot = !root;\n let nodes: JsonTreeNode[];\n\n try {\n nodes = JSON.parse(str ?? '') as JsonTreeNode[];\n } catch {\n nodes = await runTree(str ?? './');\n }\n\n const out =\n js || (output && jsExtensions.includes(path.extname(output)))\n ? treeToJavaScript(nodes, noRoot, importName)\n : treeToMdx(nodes, noRoot);\n\n if (output) {\n await fs.mkdir(path.dirname(output), { recursive: true });\n await fs.writeFile(output, out);\n } else {\n console.log(out);\n }\n },\n );\n\nfunction createClientFromDir(dir = 'https://fumadocs.dev/registry') {\n if (dir in dirShortcuts) dir = dirShortcuts[dir];\n\n return dir.startsWith('http://') || dir.startsWith('https://')\n ? new HttpRegistryConnector(dir)\n : new LocalRegistryConnector(dir);\n}\n\ncli.help();\ncli.version(packageJson.version);\n\ntry {\n cli.parse(process.argv, { run: false });\n await cli.runMatchedCommand();\n} catch (e) {\n console.error(picocolors.redBright(e instanceof Error ? e.message : String(e)));\n process.exit(1);\n}\n"],"mappings":"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"}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { b as findJsxElement, m as addJsxAttribute, o as filterReactRouterPrerenderArray, p as addImport, r as addTanstackPrerender, s as filterReactRouterRoute } from "./codemod-auol-p4R.js";
|
|
2
2
|
import { a as routeModule, r as formatRoute, t as docs } from "./docs--WqEHTyl.js";
|
|
3
3
|
import { a as reactFramework, d as sourceRef, i as posix, l as scripts, r as findSource, s as registerReactRouterRoutes } from "./utils-DbMe7h6c.js";
|
|
4
|
+
import fs from "node:fs/promises";
|
|
4
5
|
import path from "node:path";
|
|
5
6
|
//#region src/features/search.ts
|
|
6
7
|
const publicEnv = (next) => next ? {
|
|
@@ -48,13 +49,13 @@ const dialogBody = (footer, url) => ` return (
|
|
|
48
49
|
}
|
|
49
50
|
`;
|
|
50
51
|
/** `structuredData` is a function on async collections (React Router & TanStack Start) */
|
|
51
|
-
const
|
|
52
|
+
const typesenseDocuments = ({ ref, resolved }, async) => `import { ${ref} } from '@/lib/source';
|
|
52
53
|
import type { DocumentRecord } from 'typesense-fumadocs-adapter';
|
|
53
54
|
|
|
54
|
-
|
|
55
|
-
const
|
|
55
|
+
async function getDocuments() {
|
|
56
|
+
const documents: DocumentRecord[] = [];
|
|
56
57
|
for (const page of ${resolved}.getPages()) {
|
|
57
|
-
|
|
58
|
+
documents.push({
|
|
58
59
|
_id: page.url,
|
|
59
60
|
structured: ${async ? "await page.data.structuredData()" : "page.data.structuredData"},
|
|
60
61
|
url: page.url,
|
|
@@ -63,23 +64,16 @@ export async function exportSearchIndexes() {
|
|
|
63
64
|
locale: page.locale,
|
|
64
65
|
});
|
|
65
66
|
}
|
|
66
|
-
return
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
const syncScript = (imports, body) => `import * as fs from 'node:fs/promises';
|
|
67
|
+
return documents;
|
|
68
|
+
}`;
|
|
69
|
+
/** `scripts/sync-content.ts`, syncing the search indexes pre-rendered to `output` */
|
|
70
|
+
const syncScript = (output, imports, body) => `import * as fs from 'node:fs/promises';
|
|
70
71
|
${imports}
|
|
71
72
|
|
|
72
|
-
|
|
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
|
-
}
|
|
73
|
+
const records = JSON.parse(await fs.readFile('${output}', 'utf-8')) as DocumentRecord[];
|
|
78
74
|
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
if (!filePath) throw new Error('missing the path of static.json');
|
|
82
|
-
void main(filePath);
|
|
75
|
+
${body}
|
|
76
|
+
console.log(\`search updated: \${records.length} records\`);
|
|
83
77
|
`;
|
|
84
78
|
const providers = {
|
|
85
79
|
"orama-cloud": {
|
|
@@ -113,20 +107,20 @@ export default function CustomSearchDialog(props: SharedProps) {
|
|
|
113
107
|
|
|
114
108
|
${dialogBody("Orama", "https://orama.com")}`,
|
|
115
109
|
exportIndexes: ({ ref }) => ({
|
|
116
|
-
|
|
110
|
+
head: `import { ${ref} } from '@/lib/source';
|
|
117
111
|
import { toDocuments } from 'fumadocs-core/search/orama-cloud';`,
|
|
118
112
|
documents: `toDocuments(${ref})`
|
|
119
113
|
}),
|
|
120
|
-
sync: (env) => syncScript(`import { type OramaDocument as DocumentRecord, sync } from 'fumadocs-core/search/orama-cloud';
|
|
121
|
-
import { OramaCloud } from '@orama/core';`, `
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
114
|
+
sync: (env, { output }) => syncScript(output, `import { type OramaDocument as DocumentRecord, sync } from 'fumadocs-core/search/orama-cloud';
|
|
115
|
+
import { OramaCloud } from '@orama/core';`, `const orama = new OramaCloud({
|
|
116
|
+
projectId: process.env.${env.prefix}ORAMA_PROJECT_ID!,
|
|
117
|
+
apiKey: process.env.ORAMA_PRIVATE_API_KEY!,
|
|
118
|
+
});
|
|
125
119
|
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
120
|
+
await sync(orama, {
|
|
121
|
+
index: process.env.${env.prefix}ORAMA_DATASOURCE_ID!,
|
|
122
|
+
documents: records,
|
|
123
|
+
});
|
|
130
124
|
`)
|
|
131
125
|
},
|
|
132
126
|
algolia: {
|
|
@@ -156,20 +150,66 @@ export default function CustomSearchDialog(props: SharedProps) {
|
|
|
156
150
|
|
|
157
151
|
${dialogBody("Algolia", "https://algolia.com")}`,
|
|
158
152
|
exportIndexes: ({ ref }) => ({
|
|
159
|
-
|
|
153
|
+
head: `import { ${ref} } from '@/lib/source';
|
|
160
154
|
import { toDocuments } from 'fumadocs-core/search/algolia';`,
|
|
161
155
|
documents: `toDocuments(${ref})`
|
|
162
156
|
}),
|
|
163
|
-
sync: (env) => syncScript(`import { type DocumentRecord, sync } from 'fumadocs-core/search/algolia';
|
|
164
|
-
import { algoliasearch } from 'algoliasearch';`, `
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
157
|
+
sync: (env, { output }) => syncScript(output, `import { type DocumentRecord, sync } from 'fumadocs-core/search/algolia';
|
|
158
|
+
import { algoliasearch } from 'algoliasearch';`, `const client = algoliasearch(
|
|
159
|
+
process.env.${env.prefix}ALGOLIA_APP_ID!,
|
|
160
|
+
process.env.ALGOLIA_WRITE_KEY!,
|
|
161
|
+
);
|
|
168
162
|
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
163
|
+
await sync(client, {
|
|
164
|
+
indexName: 'document',
|
|
165
|
+
documents: records,
|
|
166
|
+
});
|
|
167
|
+
`)
|
|
168
|
+
},
|
|
169
|
+
meilisearch: {
|
|
170
|
+
label: "Meilisearch",
|
|
171
|
+
hint: "self-hosted or cloud",
|
|
172
|
+
dependencies: { meilisearch: null },
|
|
173
|
+
publicEnv: ["MEILISEARCH_HOST", "MEILISEARCH_SEARCH_KEY"],
|
|
174
|
+
privateEnv: ["MEILISEARCH_ADMIN_KEY"],
|
|
175
|
+
static: true,
|
|
176
|
+
dialog: (env) => `'use client';
|
|
177
|
+
${dialogImports}
|
|
178
|
+
import { useDocsSearch } from 'fumadocs-core/search/client';
|
|
179
|
+
import { meilisearchClient } from 'fumadocs-core/search/client/meilisearch';
|
|
180
|
+
import { Meilisearch } from 'meilisearch';
|
|
181
|
+
|
|
182
|
+
const client = new Meilisearch({
|
|
183
|
+
host: ${env.read}MEILISEARCH_HOST!,
|
|
184
|
+
apiKey: ${env.read}MEILISEARCH_SEARCH_KEY!,
|
|
185
|
+
});
|
|
186
|
+
|
|
187
|
+
export default function CustomSearchDialog(props: SharedProps) {
|
|
188
|
+
const { locale } = useI18n();
|
|
189
|
+
const { search, setSearch, query } = useDocsSearch({
|
|
190
|
+
client: meilisearchClient({
|
|
191
|
+
client,
|
|
192
|
+
indexName: 'docs',
|
|
193
|
+
locale,
|
|
194
|
+
}),
|
|
172
195
|
});
|
|
196
|
+
|
|
197
|
+
${dialogBody("Meilisearch", "https://www.meilisearch.com")}`,
|
|
198
|
+
exportIndexes: ({ ref }) => ({
|
|
199
|
+
head: `import { ${ref} } from '@/lib/source';
|
|
200
|
+
import { toDocuments } from 'fumadocs-core/search/meilisearch';`,
|
|
201
|
+
documents: `toDocuments(${ref})`
|
|
202
|
+
}),
|
|
203
|
+
sync: (env, { output }) => syncScript(output, `import { type DocumentRecord, sync } from 'fumadocs-core/search/meilisearch';
|
|
204
|
+
import { Meilisearch } from 'meilisearch';`, `const client = new Meilisearch({
|
|
205
|
+
host: process.env.${env.prefix}MEILISEARCH_HOST!,
|
|
206
|
+
apiKey: process.env.MEILISEARCH_ADMIN_KEY!,
|
|
207
|
+
});
|
|
208
|
+
|
|
209
|
+
await sync(client, {
|
|
210
|
+
indexName: 'docs',
|
|
211
|
+
documents: records,
|
|
212
|
+
});
|
|
173
213
|
`)
|
|
174
214
|
},
|
|
175
215
|
typesense: {
|
|
@@ -201,22 +241,21 @@ export default function CustomSearchDialog(props: SharedProps) {
|
|
|
201
241
|
});
|
|
202
242
|
|
|
203
243
|
${dialogBody("Typesense", "https://typesense.org")}`,
|
|
204
|
-
exportIndexes: (src) => ({
|
|
205
|
-
|
|
206
|
-
documents: "
|
|
207
|
-
file: (async) => typesenseIndexes(async, src)
|
|
244
|
+
exportIndexes: (src, async) => ({
|
|
245
|
+
head: typesenseDocuments(src, async),
|
|
246
|
+
documents: "getDocuments()"
|
|
208
247
|
}),
|
|
209
|
-
sync: (env) => syncScript(`import { type DocumentRecord, sync } from 'typesense-fumadocs-adapter';
|
|
210
|
-
import { Client } from 'typesense';`, `
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
248
|
+
sync: (env, { output }) => syncScript(output, `import { type DocumentRecord, sync } from 'typesense-fumadocs-adapter';
|
|
249
|
+
import { Client } from 'typesense';`, `const client = new Client({
|
|
250
|
+
nodes: [{ url: process.env.${env.prefix}TYPESENSE_URL! }],
|
|
251
|
+
apiKey: process.env.TYPESENSE_API_KEY!,
|
|
252
|
+
connectionTimeoutSeconds: 60 * 15,
|
|
253
|
+
});
|
|
215
254
|
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
255
|
+
await sync(client, {
|
|
256
|
+
typesenseCollectionName: 'docs',
|
|
257
|
+
documents: records,
|
|
258
|
+
});
|
|
220
259
|
`)
|
|
221
260
|
},
|
|
222
261
|
mixedbread: {
|
|
@@ -241,7 +280,7 @@ export default function CustomSearchDialog(props: SharedProps) {
|
|
|
241
280
|
});
|
|
242
281
|
|
|
243
282
|
${dialogBody("Mixedbread", "https://mixedbread.com")}`,
|
|
244
|
-
sync: (_env, dir) => `import { spawnSync } from 'node:child_process';
|
|
283
|
+
sync: (_env, { dir }) => `import { spawnSync } from 'node:child_process';
|
|
245
284
|
|
|
246
285
|
// sync the content with Mixedbread CLI
|
|
247
286
|
const result = spawnSync(
|
|
@@ -290,12 +329,42 @@ export const { GET } = server;
|
|
|
290
329
|
})()
|
|
291
330
|
}
|
|
292
331
|
};
|
|
293
|
-
|
|
294
|
-
|
|
332
|
+
/** pre-rendered on build to export the search indexes */
|
|
333
|
+
const staticJson = (framework) => formatRoute({ segments: ["static.json"] }, framework, null);
|
|
334
|
+
const staticRoute = (framework, head, documents) => routeModule(framework, staticJson(framework), {
|
|
335
|
+
imports: head,
|
|
295
336
|
body: `Response.json(await ${documents})`,
|
|
296
337
|
revalidate: true,
|
|
297
338
|
static: true
|
|
298
339
|
});
|
|
340
|
+
/** where `static.json` is pre-rendered on build, `config` is the Vite config of TanStack Start */
|
|
341
|
+
function staticOutput(framework, config = "") {
|
|
342
|
+
switch (framework) {
|
|
343
|
+
case "next": return ".next/server/app/static.json.body";
|
|
344
|
+
case "react-router": return "build/client/static.json";
|
|
345
|
+
case "waku": return "dist/public/static.json";
|
|
346
|
+
case "tanstack-start":
|
|
347
|
+
if (/preset:\s*['"]vercel['"]/.test(config)) return ".vercel/output/static/static.json";
|
|
348
|
+
return /\bnitro\(/.test(config) ? ".output/public/static.json" : "dist/client/static.json";
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
/** runs the sync script after build */
|
|
352
|
+
const syncCommand = "node --env-file-if-exists=.env.local scripts/sync-content.ts";
|
|
353
|
+
/** the files written by a provider, relative to the project, `output` is where `static.json` is pre-rendered */
|
|
354
|
+
function templates(name, framework, { baseDir, source }, output) {
|
|
355
|
+
const provider = providers[name];
|
|
356
|
+
const env = publicEnv(framework === "next");
|
|
357
|
+
const files = [[path.posix.join(baseDir, "components/search.tsx"), provider.dialog(env)]];
|
|
358
|
+
if (provider.exportIndexes) {
|
|
359
|
+
const { head, documents } = provider.exportIndexes(sourceRef(source.dynamic), source.async);
|
|
360
|
+
files.push([path.posix.join(baseDir, staticJson(framework).file), staticRoute(framework, head, documents)]);
|
|
361
|
+
}
|
|
362
|
+
if (provider.sync) files.push(["scripts/sync-content.ts", provider.sync(env, {
|
|
363
|
+
dir: source.dir,
|
|
364
|
+
output
|
|
365
|
+
})]);
|
|
366
|
+
return files;
|
|
367
|
+
}
|
|
299
368
|
const search = {
|
|
300
369
|
id: "search",
|
|
301
370
|
title: "Search",
|
|
@@ -316,19 +385,16 @@ const search = {
|
|
|
316
385
|
const framework = reactFramework(project);
|
|
317
386
|
const provider = providers[name];
|
|
318
387
|
if (project.static && !provider.static) throw new Error(`${provider.label} requires a server at runtime`);
|
|
319
|
-
const
|
|
388
|
+
const output = staticOutput(framework, framework === "tanstack-start" && project.configFile ? await fs.readFile(path.join(project.cwd, project.configFile), "utf-8") : "");
|
|
320
389
|
ctx.addDependencies(provider.dependencies);
|
|
321
|
-
|
|
390
|
+
for (const [file, content] of templates(name, framework, project, output)) await ctx.write(file, content);
|
|
322
391
|
const searchRoute = await findSource(project.cwd, path.join(baseDir, project.info.routesDir), "createFromSource(", (file) => /(^|[\\/])search(\.ts|[\\/]route\.ts)$/.test(file));
|
|
323
392
|
if (provider.searchRoute) {
|
|
324
393
|
if (searchRoute) await ctx.write(searchRoute, provider.searchRoute[framework]);
|
|
325
394
|
else ctx.note("Create the search route with `createMixedbreadSearchAPI`, see https://fumadocs.dev/docs/headless/search/mixedbread.");
|
|
326
395
|
} else if (searchRoute) await removeDefaultSearch(ctx, searchRoute);
|
|
327
396
|
if (provider.exportIndexes) {
|
|
328
|
-
const
|
|
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));
|
|
397
|
+
const route = staticJson(framework);
|
|
332
398
|
if (framework === "react-router") await registerReactRouterRoutes(ctx, [route]);
|
|
333
399
|
else if (framework === "tanstack-start" && project.configFile) await ctx.source(project.configFile, (file) => addTanstackPrerender(file, [route.path]));
|
|
334
400
|
}
|
|
@@ -342,21 +408,12 @@ const search = {
|
|
|
342
408
|
default: "SearchDialog"
|
|
343
409
|
});
|
|
344
410
|
}))) ctx.note("Pass `search={{ SearchDialog }}` from `@/components/search` to `<RootProvider />`.");
|
|
345
|
-
if (provider.sync) {
|
|
346
|
-
const
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
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
|
-
}
|
|
411
|
+
if (provider.sync) await ctx.packageJson((data) => {
|
|
412
|
+
const build = data.scripts?.build ?? "";
|
|
413
|
+
if (build.includes("sync-content")) return;
|
|
414
|
+
scripts(data, { build: build ? `${build} && ${syncCommand}` : syncCommand });
|
|
415
|
+
});
|
|
416
|
+
const env = publicEnv(framework === "next");
|
|
360
417
|
for (const key of provider.publicEnv) ctx.env(env.prefix + key, "");
|
|
361
418
|
for (const key of provider.privateEnv) ctx.env(key, "");
|
|
362
419
|
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}.`);
|
|
@@ -376,6 +433,6 @@ async function removeDefaultSearch(ctx, file) {
|
|
|
376
433
|
});
|
|
377
434
|
}
|
|
378
435
|
//#endregion
|
|
379
|
-
export { search as t };
|
|
436
|
+
export { syncCommand as a, staticRoute as i, staticJson as n, syncScript as o, staticOutput as r, templates as s, search as t };
|
|
380
437
|
|
|
381
|
-
//# sourceMappingURL=search-
|
|
438
|
+
//# sourceMappingURL=search-BbzvfJal.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"search-BbzvfJal.js","names":[],"sources":["../src/features/search.ts"],"sourcesContent":["import fs from 'node:fs/promises';\nimport path from 'node:path';\nimport type { Feature, FeatureContext } from '@/features';\nimport type { ReactFramework, SourceInfo } from '@/project';\nimport { formatRoute, routeModule } from '@/project/route';\nimport {\n addImport,\n addJsxAttribute,\n addTanstackPrerender,\n filterReactRouterPrerenderArray,\n filterReactRouterRoute,\n findJsxElement,\n} from '@/codemod';\nimport { docs } from './docs';\nimport {\n findSource,\n posix,\n reactFramework,\n registerReactRouterRoutes,\n scripts,\n type SourceRef,\n sourceRef,\n} from './utils';\n\n/** how client code reads public env variables */\ninterface Env {\n prefix: string;\n read: string;\n}\n\nconst publicEnv = (next: boolean): Env =>\n next\n ? { prefix: 'NEXT_PUBLIC_', read: 'process.env.NEXT_PUBLIC_' }\n : { prefix: 'VITE_', read: 'import.meta.env.VITE_' };\n\nconst dialogImports = `import {\n SearchDialog,\n SearchDialogClose,\n SearchDialogContent,\n SearchDialogFooter,\n SearchDialogHeader,\n SearchDialogIcon,\n SearchDialogInput,\n SearchDialogList,\n SearchDialogOverlay,\n type SharedProps,\n} from 'fumadocs-ui/components/dialog/search';\nimport { useI18n } from 'fumadocs-ui/contexts/i18n';`;\n\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 typesenseDocuments = (\n { ref, resolved }: SourceRef,\n async: boolean,\n) => `import { ${ref} } from '@/lib/source';\nimport type { DocumentRecord } from 'typesense-fumadocs-adapter';\n\nasync function getDocuments() {\n const documents: DocumentRecord[] = [];\n for (const page of ${resolved}.getPages()) {\n documents.push({\n _id: page.url,\n structured: ${async ? 'await page.data.structuredData()' : 'page.data.structuredData'},\n url: page.url,\n title: page.data.title,\n description: page.data.description,\n locale: page.locale,\n });\n }\n return documents;\n}`;\n\n/** `scripts/sync-content.ts`, syncing the search indexes pre-rendered to `output` */\nexport const syncScript = (\n output: string,\n imports: string,\n body: string,\n) => `import * as fs from 'node:fs/promises';\n${imports}\n\nconst records = JSON.parse(await fs.readFile('${output}', 'utf-8')) as DocumentRecord[];\n\n${body}\nconsole.log(\\`search updated: \\${records.length} records\\`);\n`;\n\ninterface Provider {\n label: string;\n hint: string;\n dependencies: Record<string, string | null>;\n /** keys exposed to the client, prefixed per framework */\n publicEnv: string[];\n privateEnv: string[];\n /** works without a server at runtime */\n static: boolean;\n dialog: (env: Env) => string;\n /** exports search indexes to a pre-rendered `static.json`, synced by `sync` after build */\n exportIndexes?: (\n src: SourceRef,\n async: boolean,\n ) => {\n /** code before the route handler */\n head: string;\n /** expression producing the documents */\n documents: string;\n };\n /** `scripts/sync-content.ts`, run after build */\n sync?: (env: Env, paths: { dir: string; output: string }) => string;\n /** server-side search, replaces the default search route */\n searchRoute?: Record<ReactFramework, string>;\n}\n\nconst providers = {\n 'orama-cloud': {\n label: 'Orama Cloud',\n hint: 'signup needed',\n dependencies: { '@orama/core': null },\n publicEnv: ['ORAMA_DATASOURCE_ID', 'ORAMA_PROJECT_ID', 'ORAMA_API_KEY'],\n privateEnv: ['ORAMA_PRIVATE_API_KEY'],\n static: true,\n dialog: (env) => `'use client';\n${dialogImports}\nimport { 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 head: `import { ${ref} } from '@/lib/source';\nimport { toDocuments } from 'fumadocs-core/search/orama-cloud';`,\n documents: `toDocuments(${ref})`,\n }),\n sync: (env, { output }) =>\n syncScript(\n output,\n `import { type OramaDocument as DocumentRecord, sync } from 'fumadocs-core/search/orama-cloud';\nimport { OramaCloud } from '@orama/core';`,\n `const orama = new OramaCloud({\n projectId: process.env.${env.prefix}ORAMA_PROJECT_ID!,\n apiKey: process.env.ORAMA_PRIVATE_API_KEY!,\n});\n\nawait sync(orama, {\n index: process.env.${env.prefix}ORAMA_DATASOURCE_ID!,\n documents: records,\n});\n`,\n ),\n },\n algolia: {\n label: 'Algolia',\n hint: 'signup needed',\n dependencies: { algoliasearch: null },\n publicEnv: ['ALGOLIA_APP_ID', 'ALGOLIA_SEARCH_KEY'],\n privateEnv: ['ALGOLIA_WRITE_KEY'],\n static: true,\n dialog: (env) => `'use client';\n${dialogImports}\nimport { 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 head: `import { ${ref} } from '@/lib/source';\nimport { toDocuments } from 'fumadocs-core/search/algolia';`,\n documents: `toDocuments(${ref})`,\n }),\n sync: (env, { output }) =>\n syncScript(\n output,\n `import { type DocumentRecord, sync } from 'fumadocs-core/search/algolia';\nimport { algoliasearch } from 'algoliasearch';`,\n `const client = algoliasearch(\n process.env.${env.prefix}ALGOLIA_APP_ID!,\n process.env.ALGOLIA_WRITE_KEY!,\n);\n\nawait sync(client, {\n indexName: 'document',\n documents: records,\n});\n`,\n ),\n },\n meilisearch: {\n label: 'Meilisearch',\n hint: 'self-hosted or cloud',\n dependencies: { meilisearch: null },\n publicEnv: ['MEILISEARCH_HOST', 'MEILISEARCH_SEARCH_KEY'],\n privateEnv: ['MEILISEARCH_ADMIN_KEY'],\n static: true,\n dialog: (env) => `'use client';\n${dialogImports}\nimport { useDocsSearch } from 'fumadocs-core/search/client';\nimport { meilisearchClient } from 'fumadocs-core/search/client/meilisearch';\nimport { Meilisearch } from 'meilisearch';\n\nconst client = new Meilisearch({\n host: ${env.read}MEILISEARCH_HOST!,\n apiKey: ${env.read}MEILISEARCH_SEARCH_KEY!,\n});\n\nexport default function CustomSearchDialog(props: SharedProps) {\n const { locale } = useI18n();\n const { search, setSearch, query } = useDocsSearch({\n client: meilisearchClient({\n client,\n indexName: 'docs',\n locale,\n }),\n });\n\n${dialogBody('Meilisearch', 'https://www.meilisearch.com')}`,\n exportIndexes: ({ ref }) => ({\n head: `import { ${ref} } from '@/lib/source';\nimport { toDocuments } from 'fumadocs-core/search/meilisearch';`,\n documents: `toDocuments(${ref})`,\n }),\n sync: (env, { output }) =>\n syncScript(\n output,\n `import { type DocumentRecord, sync } from 'fumadocs-core/search/meilisearch';\nimport { Meilisearch } from 'meilisearch';`,\n `const client = new Meilisearch({\n host: process.env.${env.prefix}MEILISEARCH_HOST!,\n apiKey: process.env.MEILISEARCH_ADMIN_KEY!,\n});\n\nawait sync(client, {\n indexName: 'docs',\n documents: records,\n});\n`,\n ),\n },\n typesense: {\n label: 'Typesense',\n hint: 'self-hosted or cloud, community adapter',\n dependencies: { typesense: null, 'typesense-fumadocs-adapter': null },\n publicEnv: ['TYPESENSE_URL', 'TYPESENSE_SEARCH_KEY'],\n privateEnv: ['TYPESENSE_API_KEY'],\n static: true,\n dialog: (env) => `'use client';\n${dialogImports}\nimport { Client } from 'typesense';\nimport { useTypesenseSearch } from 'typesense-fumadocs-adapter/client';\n\nconst client = new Client({\n nodes: [{ url: ${env.read}TYPESENSE_URL! }],\n apiKey: ${env.read}TYPESENSE_SEARCH_KEY!,\n});\n\nexport default function CustomSearchDialog(props: SharedProps) {\n const { locale } = useI18n();\n const { search, setSearch, query } = useTypesenseSearch({\n typesenseCollectionName: 'docs',\n locale,\n client,\n });\n\n${dialogBody('Typesense', 'https://typesense.org')}`,\n exportIndexes: (src, async) => ({\n head: typesenseDocuments(src, async),\n documents: 'getDocuments()',\n }),\n sync: (env, { output }) =>\n syncScript(\n output,\n `import { type DocumentRecord, sync } from 'typesense-fumadocs-adapter';\nimport { Client } from 'typesense';`,\n `const client = new Client({\n nodes: [{ url: process.env.${env.prefix}TYPESENSE_URL! }],\n apiKey: process.env.TYPESENSE_API_KEY!,\n connectionTimeoutSeconds: 60 * 15,\n});\n\nawait sync(client, {\n typesenseCollectionName: 'docs',\n documents: records,\n});\n`,\n ),\n },\n mixedbread: {\n label: 'Mixedbread',\n hint: 'AI search, signup needed',\n dependencies: { '@mixedbread/sdk': null },\n publicEnv: [],\n privateEnv: ['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\n/** pre-rendered on build to export the search indexes */\nexport const staticJson = (framework: ReactFramework) =>\n formatRoute({ segments: ['static.json'] }, framework, null);\n\nexport const staticRoute = (framework: ReactFramework, head: string, documents: string) =>\n routeModule(framework, staticJson(framework), {\n imports: head,\n body: `Response.json(await ${documents})`,\n revalidate: true,\n static: true,\n });\n\n/** where `static.json` is pre-rendered on build, `config` is the Vite config of TanStack Start */\nexport function staticOutput(framework: ReactFramework, config = ''): string {\n switch (framework) {\n case 'next':\n return '.next/server/app/static.json.body';\n case 'react-router':\n return 'build/client/static.json';\n case 'waku':\n return 'dist/public/static.json';\n case 'tanstack-start':\n // Nitro moves the client output to the directory of its preset\n if (/preset:\\s*['\"]vercel['\"]/.test(config)) return '.vercel/output/static/static.json';\n return /\\bnitro\\(/.test(config) ? '.output/public/static.json' : 'dist/client/static.json';\n }\n}\n\n/** runs the sync script after build */\nexport const syncCommand = 'node --env-file-if-exists=.env.local scripts/sync-content.ts';\n\n/** the files written by a provider, relative to the project, `output` is where `static.json` is pre-rendered */\nexport function templates(\n name: SearchProvider,\n framework: ReactFramework,\n { baseDir, source }: { baseDir: string; source: Pick<SourceInfo, 'dynamic' | 'async' | 'dir'> },\n output: string,\n): [file: string, content: string][] {\n const provider: Provider = providers[name];\n const env = publicEnv(framework === 'next');\n const files: [string, string][] = [\n [path.posix.join(baseDir, 'components/search.tsx'), provider.dialog(env)],\n ];\n\n if (provider.exportIndexes) {\n const { head, documents } = provider.exportIndexes(sourceRef(source.dynamic), source.async);\n files.push([\n path.posix.join(baseDir, staticJson(framework).file),\n staticRoute(framework, head, documents),\n ]);\n }\n if (provider.sync)\n files.push(['scripts/sync-content.ts', provider.sync(env, { dir: source.dir, output })]);\n return files;\n}\n\nexport const search: Feature<{ provider: SearchProvider }> = {\n id: 'search',\n title: 'Search',\n description: 'replace the default search with a 3rd party search solution',\n requires: [docs],\n supports: (project) =>\n project.source.loader ||\n project.source.dynamic ||\n '`lib/source.ts` must export a `source` loader',\n options: {\n provider: {\n message: 'Choose a search provider',\n choices: Object.entries(providers).map(([value, { label, hint }]) => ({\n value: value as SearchProvider,\n label,\n hint,\n })),\n },\n },\n async apply(ctx, { provider: name }) {\n const { project } = ctx;\n const { baseDir } = project;\n const framework = reactFramework(project);\n const provider: Provider = providers[name];\n if (project.static && !provider.static)\n throw new Error(`${provider.label} requires a server at runtime`);\n\n const config =\n framework === 'tanstack-start' && project.configFile\n ? await fs.readFile(path.join(project.cwd, project.configFile), 'utf-8')\n : '';\n const output = staticOutput(framework, config);\n\n ctx.addDependencies(provider.dependencies);\n for (const [file, content] of templates(name, framework, project, output))\n await ctx.write(file, content);\n\n // other routes (e.g. MCP) also create a search server, only match the search route itself\n const searchRoute = await findSource(\n project.cwd,\n path.join(baseDir, project.info.routesDir),\n 'createFromSource(',\n (file) => /(^|[\\\\/])search(\\.ts|[\\\\/]route\\.ts)$/.test(file),\n );\n if (provider.searchRoute) {\n if (searchRoute) await ctx.write(searchRoute, provider.searchRoute[framework]);\n else\n ctx.note(\n 'Create the search route with `createMixedbreadSearchAPI`, see https://fumadocs.dev/docs/headless/search/mixedbread.',\n );\n } else if (searchRoute) {\n await removeDefaultSearch(ctx, searchRoute);\n }\n\n if (provider.exportIndexes) {\n const route = staticJson(framework);\n if (framework === 'react-router') {\n await registerReactRouterRoutes(ctx, [route]);\n } else if (framework === 'tanstack-start' && project.configFile) {\n await ctx.source(project.configFile, (file) => addTanstackPrerender(file, [route.path]));\n }\n }\n\n const rootProvider = await findSource(project.cwd, baseDir, '<RootProvider');\n const edited =\n rootProvider !== undefined &&\n (await ctx.source(rootProvider, (file) => {\n const element = findJsxElement(file, 'RootProvider')?.openingElement;\n if (!element || file.code.slice(element.start, element.end).includes('search=')) return;\n addJsxAttribute(file, element, 'search={{ SearchDialog }}');\n addImport(file, { from: '@/components/search', default: 'SearchDialog' });\n }));\n if (!edited)\n ctx.note(\n 'Pass `search={{ SearchDialog }}` from `@/components/search` to `<RootProvider />`.',\n );\n\n if (provider.sync) {\n await ctx.packageJson((data) => {\n const build = data.scripts?.build ?? '';\n if (build.includes('sync-content')) return;\n scripts(data, { build: build ? `${build} && ${syncCommand}` : syncCommand });\n });\n }\n\n const env = publicEnv(framework === 'next');\n for (const key of provider.publicEnv) ctx.env(env.prefix + key, '');\n for (const key of provider.privateEnv) ctx.env(key, '');\n ctx.note(\n `Fill the ${provider.label} keys in \\`.env.local\\`, the search index is synced after each build.\\nSee https://fumadocs.dev/docs/headless/search/${name}.`,\n );\n },\n};\n\n/** remove the default search route, which conflicts with the search provider */\nasync function removeDefaultSearch(ctx: FeatureContext, file: string) {\n const { baseDir, framework } = ctx.project;\n await ctx.remove(file);\n if (framework !== 'react-router') return;\n\n const entry = posix(path.relative(baseDir, file));\n await ctx.source(path.join(baseDir, 'routes.ts'), (source) => {\n filterReactRouterRoute(source, (item) => item.entry !== entry);\n });\n await ctx.source('react-router.config.ts', (source) => {\n filterReactRouterPrerenderArray(source, 'excluded', (item) => item !== '/api/search');\n });\n}\n"],"mappings":"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"}
|