boltdocs 2.0.0 → 2.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (67) hide show
  1. package/CHANGELOG.md +12 -0
  2. package/bin/boltdocs.js +12 -0
  3. package/dist/cache-Q4T6VAUL.mjs +1 -0
  4. package/dist/chunk-52MVMZWS.mjs +1 -0
  5. package/dist/chunk-BVWWKXJH.mjs +1 -0
  6. package/dist/chunk-DVY3RDXD.mjs +1 -0
  7. package/dist/chunk-FUVYCYWC.mjs +1 -0
  8. package/dist/{chunk-BA5NH5HU.mjs → chunk-GBLMDJ2B.mjs} +1 -1
  9. package/dist/{chunk-H63UMKYF.mjs → chunk-ISPX45DF.mjs} +1 -1
  10. package/dist/chunk-PNXZMUCO.mjs +1 -0
  11. package/dist/chunk-V2ZHKQSP.mjs +74 -0
  12. package/dist/client/components/mdx/index.d.mts +2 -3
  13. package/dist/client/components/mdx/index.d.ts +2 -3
  14. package/dist/client/components/mdx/index.js +1 -1
  15. package/dist/client/components/mdx/index.mjs +1 -1
  16. package/dist/client/hooks/index.d.mts +2 -3
  17. package/dist/client/hooks/index.d.ts +2 -3
  18. package/dist/client/hooks/index.js +1 -1
  19. package/dist/client/hooks/index.mjs +1 -1
  20. package/dist/client/index.d.mts +3 -5
  21. package/dist/client/index.d.ts +3 -5
  22. package/dist/client/index.js +1 -1
  23. package/dist/client/index.mjs +1 -1
  24. package/dist/client/ssr.d.mts +53 -6
  25. package/dist/client/ssr.d.ts +53 -6
  26. package/dist/client/ssr.mjs +1 -1
  27. package/dist/node/cli-entry.d.mts +1 -0
  28. package/dist/node/cli-entry.d.ts +1 -0
  29. package/dist/node/cli-entry.js +75 -0
  30. package/dist/node/cli-entry.mjs +2 -0
  31. package/dist/node/index.d.mts +213 -4
  32. package/dist/node/index.d.ts +213 -4
  33. package/dist/node/index.js +40 -23
  34. package/dist/node/index.mjs +1 -57
  35. package/dist/search-dialog-TWGYKF2D.mjs +1 -0
  36. package/dist/types-Cp21DHI6.d.mts +355 -0
  37. package/dist/types-Cp21DHI6.d.ts +355 -0
  38. package/dist/{use-routes-BefRXY3v.d.ts → use-routes-8Iei6jTp.d.mts} +1 -2
  39. package/dist/{use-routes-5bAtAAYX.d.mts → use-routes-xLhumjbV.d.ts} +1 -2
  40. package/package.json +7 -1
  41. package/src/client/components/ui-base/breadcrumbs.tsx +2 -1
  42. package/src/client/components/ui-base/navbar.tsx +3 -3
  43. package/src/client/components/ui-base/sidebar.tsx +2 -1
  44. package/src/client/hooks/use-navbar.ts +1 -1
  45. package/src/node/cli-entry.ts +24 -0
  46. package/src/node/cli.ts +59 -0
  47. package/src/node/config.ts +63 -11
  48. package/src/node/index.ts +39 -3
  49. package/src/node/plugin/entry.ts +7 -0
  50. package/src/node/plugin/html.ts +49 -9
  51. package/src/node/plugin/index.ts +42 -3
  52. package/src/node/ssg/index.ts +6 -1
  53. package/src/node/ssg/robots.ts +50 -0
  54. package/tsup.config.ts +36 -10
  55. package/dist/cache-7G6D532T.mjs +0 -1
  56. package/dist/chunk-A4HQPEPU.mjs +0 -1
  57. package/dist/chunk-BQCD3DWG.mjs +0 -1
  58. package/dist/chunk-IWHRQHS7.mjs +0 -1
  59. package/dist/chunk-JZXLCA2E.mjs +0 -1
  60. package/dist/chunk-MFU7Q6WF.mjs +0 -1
  61. package/dist/chunk-QYPNX5UN.mjs +0 -1
  62. package/dist/chunk-XEAPSFMB.mjs +0 -1
  63. package/dist/config-CX4l-ZNp.d.mts +0 -166
  64. package/dist/config-CX4l-ZNp.d.ts +0 -166
  65. package/dist/search-dialog-EB3N4TYM.mjs +0 -1
  66. package/dist/types-BuZWFT7r.d.ts +0 -159
  67. package/dist/types-CvT-SGbK.d.mts +0 -159
@@ -1,5 +1,4 @@
1
- import { B as BoltdocsConfig } from './config-CX4l-ZNp.js';
2
- import { C as ComponentRoute } from './types-BuZWFT7r.js';
1
+ import { C as ComponentRoute, B as BoltdocsConfig } from './types-Cp21DHI6.mjs';
3
2
 
4
3
  /**
5
4
  * Hook to access the framework's routing state.
@@ -1,5 +1,4 @@
1
- import { B as BoltdocsConfig } from './config-CX4l-ZNp.mjs';
2
- import { C as ComponentRoute } from './types-CvT-SGbK.mjs';
1
+ import { C as ComponentRoute, B as BoltdocsConfig } from './types-Cp21DHI6.js';
3
2
 
4
3
  /**
5
4
  * Hook to access the framework's routing state.
package/package.json CHANGED
@@ -1,10 +1,13 @@
1
1
  {
2
2
  "name": "boltdocs",
3
- "version": "2.0.0",
3
+ "version": "2.1.1",
4
4
  "description": "A lightweight documentation generator for React projects.",
5
5
  "main": "dist/node/index.js",
6
6
  "module": "dist/node/index.mjs",
7
7
  "types": "dist/node/index.d.ts",
8
+ "bin": {
9
+ "boltdocs": "bin/boltdocs.js"
10
+ },
8
11
  "publishConfig": {
9
12
  "access": "public"
10
13
  },
@@ -53,6 +56,9 @@
53
56
  "dependencies": {
54
57
  "@mdx-js/react": "^3.1.1",
55
58
  "@mdx-js/rollup": "^3.1.1",
59
+ "@tailwindcss/vite": "^4.2.2",
60
+ "@vitejs/plugin-react": "^4.2.1",
61
+ "cac": "^7.0.0",
56
62
  "class-variance-authority": "^0.7.1",
57
63
  "clsx": "^2.1.1",
58
64
  "codesandbox": "^2.2.3",
@@ -12,10 +12,11 @@ import { useConfig } from '@client/app/config-context'
12
12
  export function Breadcrumbs() {
13
13
  const { crumbs, activeRoute } = useBreadcrumbs()
14
14
  const config = useConfig()
15
+ const themeConfig = config.theme || config.themeConfig || {}
15
16
 
16
17
  if (crumbs.length === 0) return null
17
18
 
18
- if (!config.themeConfig?.breadcrumbs) return null
19
+ if (!themeConfig?.breadcrumbs) return null
19
20
 
20
21
  return (
21
22
  <BreadcrumbsRoot>
@@ -24,7 +24,7 @@ export function Navbar() {
24
24
  const { links, title, logo, logoProps, github, social, config } = useNavbar()
25
25
  const { routes, allRoutes, currentVersion, currentLocale } = useRoutes()
26
26
  const { pathname } = useLocation()
27
- const { themeConfig } = useConfig()
27
+ const themeConfig = config.theme || config.themeConfig || {}
28
28
 
29
29
  const hasTabs = themeConfig?.tabs && themeConfig.tabs.length > 0
30
30
 
@@ -76,10 +76,10 @@ export function Navbar() {
76
76
  </NavbarPrimitive.NavbarRight>
77
77
  </NavbarPrimitive.Content>
78
78
 
79
- {pathname !== '/' && hasTabs && config.themeConfig?.tabs && (
79
+ {pathname !== '/' && hasTabs && themeConfig?.tabs && (
80
80
  <div className="w-full border-b border-border-subtle bg-bg-main">
81
81
  <Tabs
82
- tabs={config.themeConfig.tabs}
82
+ tabs={themeConfig.tabs}
83
83
  routes={allRoutes || routes || []}
84
84
  />
85
85
  </div>
@@ -67,6 +67,7 @@ export function Sidebar({
67
67
  config: BoltdocsConfig
68
68
  }) {
69
69
  const { groups, ungrouped, activePath } = useSidebar(routes)
70
+ const themeConfig = config.theme || config.themeConfig || {}
70
71
 
71
72
  return (
72
73
  <SidebarPrimitive.SidebarRoot>
@@ -94,7 +95,7 @@ export function Sidebar({
94
95
  />
95
96
  ))}
96
97
 
97
- {config.themeConfig?.poweredBy && (
98
+ {themeConfig?.poweredBy && (
98
99
  <div className="mt-auto pt-8">
99
100
  <PoweredBy />
100
101
  </div>
@@ -8,7 +8,7 @@ export function useNavbar() {
8
8
  const { theme } = useTheme()
9
9
  const location = useLocation()
10
10
 
11
- const themeConfig = config.themeConfig || {}
11
+ const themeConfig = config.theme || config.themeConfig || {}
12
12
  const title = themeConfig.title || 'Boltdocs'
13
13
  const rawLinks = themeConfig.navbar || []
14
14
  const socialLinks = themeConfig.socialLinks || []
@@ -0,0 +1,24 @@
1
+ #!/usr/bin/env node
2
+ import cac from "cac";
3
+ import { devAction, buildAction, previewAction } from "./cli";
4
+
5
+ const cli = cac("boltdocs");
6
+
7
+ cli
8
+ .command("[root]", "Start development server")
9
+ .alias("dev")
10
+ .action(devAction);
11
+
12
+ cli
13
+ .command("build [root]", "Build for production")
14
+ .action(buildAction);
15
+
16
+ cli
17
+ .command("preview [root]", "Preview production build")
18
+ .action(previewAction);
19
+
20
+ cli.help();
21
+ // This will be replaced at build time or package publishing, but hardcoded to 2.0.0 for now
22
+ cli.version("2.0.0");
23
+
24
+ cli.parse();
@@ -0,0 +1,59 @@
1
+ import { createServer, build, preview } from 'vite'
2
+ import { createViteConfig, resolveConfig } from './index'
3
+ import { getHtmlTemplate } from './plugin/html'
4
+ import path from 'path'
5
+ import fs from 'fs'
6
+
7
+ /**
8
+ * Core logic for the Boltdocs CLI commands.
9
+ * These functions wrap Vite's JS API to provide a seamless experience.
10
+ */
11
+
12
+ export async function devAction(root: string = process.cwd()) {
13
+ try {
14
+ const viteConfig = await createViteConfig(root, 'development')
15
+ const server = await createServer(viteConfig)
16
+ await server.listen()
17
+ server.printUrls()
18
+ server.bindCLIShortcuts({ print: true })
19
+ } catch (e) {
20
+ console.error('[boltdocs] Failed to start dev server:', e)
21
+ process.exit(1)
22
+ }
23
+ }
24
+
25
+ export async function buildAction(root: string = process.cwd()) {
26
+ let createdIndexHtml = false
27
+ const indexPath = path.resolve(root, 'index.html')
28
+
29
+ try {
30
+ if (!fs.existsSync(indexPath)) {
31
+ const config = await resolveConfig('docs', root)
32
+ fs.writeFileSync(indexPath, getHtmlTemplate(config), 'utf-8')
33
+ createdIndexHtml = true
34
+ }
35
+
36
+ const viteConfig = await createViteConfig(root, 'production')
37
+ await build(viteConfig)
38
+ console.log('[boltdocs] Build completed successfully.')
39
+ } catch (e) {
40
+ console.error('[boltdocs] Build failed:', e)
41
+ process.exit(1)
42
+ } finally {
43
+ if (createdIndexHtml && fs.existsSync(indexPath)) {
44
+ fs.unlinkSync(indexPath)
45
+ }
46
+ }
47
+ }
48
+
49
+ export async function previewAction(root: string = process.cwd()) {
50
+ try {
51
+ const viteConfig = await createViteConfig(root, 'production')
52
+ const previewServer = await preview(viteConfig)
53
+ previewServer.printUrls()
54
+ } catch (e) {
55
+ console.error('[boltdocs] Failed to start preview server:', e)
56
+ process.exit(1)
57
+ }
58
+ }
59
+
@@ -63,6 +63,15 @@ export interface BoltdocsThemeConfig {
63
63
  version?: string
64
64
  /** The GitHub repository in the format 'owner/repo' to fetch and display star count. */
65
65
  githubRepo?: string
66
+ /**
67
+ * URL path to the site favicon.
68
+ * If not specified, the logo will be used if available.
69
+ */
70
+ favicon?: string
71
+ /**
72
+ * The Open Graph image URL to display when the site is shared on social media.
73
+ */
74
+ ogImage?: string
66
75
  /** Whether to show the 'Powered by LiteDocs' badge in the sidebar (default: true) */
67
76
  poweredBy?: boolean
68
77
  /**
@@ -85,6 +94,24 @@ export interface BoltdocsThemeConfig {
85
94
  copyMarkdown?: boolean | { text?: string; icon?: string }
86
95
  }
87
96
 
97
+ /**
98
+ * Configuration for the robots.txt file.
99
+ */
100
+ export type BoltdocsRobotsConfig =
101
+ | string
102
+ | {
103
+ /** User-agent rules */
104
+ rules?: Array<{
105
+ userAgent: string
106
+ /** Paths allowed to be crawled */
107
+ allow?: string | string[]
108
+ /** Paths disallowed to be crawled */
109
+ disallow?: string | string[]
110
+ }>
111
+ /** Sitemaps to include in the robots.txt */
112
+ sitemaps?: string[]
113
+ }
114
+
88
115
  /**
89
116
  * Configuration for internationalization (i18n).
90
117
  */
@@ -142,10 +169,12 @@ export interface BoltdocsIntegrationsConfig {
142
169
  export interface BoltdocsConfig {
143
170
  /** The base URL of the site, used for generating the sitemap */
144
171
  siteUrl?: string
145
- /** Configuration pertaining to the UI and appearance */
146
- themeConfig?: BoltdocsThemeConfig
147
172
  /** The root directory containing markdown documentation files (default: 'docs') */
148
173
  docsDir?: string
174
+ /** Path to a custom HomePage component */
175
+ homePage?: string
176
+ /** Configuration pertaining to the UI and appearance */
177
+ theme?: BoltdocsThemeConfig
149
178
  /** Configuration for internationalization */
150
179
  i18n?: BoltdocsI18nConfig
151
180
  /** Configuration for documentation versioning */
@@ -156,6 +185,16 @@ export interface BoltdocsConfig {
156
185
  external?: Record<string, string>
157
186
  /** External integrations configuration */
158
187
  integrations?: BoltdocsIntegrationsConfig
188
+ /** Configuration for the robots.txt file */
189
+ robots?: BoltdocsRobotsConfig
190
+ /** Low-level Vite configuration overrides */
191
+ vite?: import('vite').InlineConfig
192
+ /** @deprecated Use theme instead */
193
+ themeConfig?: BoltdocsThemeConfig
194
+ }
195
+
196
+ export function defineConfig(config: BoltdocsConfig): BoltdocsConfig {
197
+ return config
159
198
  }
160
199
 
161
200
  export const CONFIG_FILES = [
@@ -169,7 +208,10 @@ export const CONFIG_FILES = [
169
208
  */
170
209
  interface RawUserConfig
171
210
  extends Partial<BoltdocsConfig>,
172
- Partial<BoltdocsThemeConfig> {}
211
+ Partial<BoltdocsThemeConfig> {
212
+ favicon?: string
213
+ ogImage?: string
214
+ }
173
215
 
174
216
  /**
175
217
  * Loads user's configuration file (e.g., `boltdocs.config.js` or `boltdocs.config.ts`) if it exists,
@@ -187,7 +229,7 @@ export async function resolveConfig(
187
229
 
188
230
  const defaults: BoltdocsConfig = {
189
231
  docsDir: path.resolve(docsDir),
190
- themeConfig: {
232
+ theme: {
191
233
  title: 'Boltdocs',
192
234
  description: 'A Vite documentation framework',
193
235
  navbar: [
@@ -230,18 +272,28 @@ export async function resolveConfig(
230
272
  title: userConfig.title,
231
273
  description: userConfig.description,
232
274
  logo: userConfig.logo,
275
+ favicon: userConfig.favicon,
276
+ ogImage: userConfig.ogImage,
233
277
  navbar: userConfig.navbar,
234
278
  sidebar: userConfig.sidebar,
235
279
  socialLinks: userConfig.socialLinks,
236
280
  footer: userConfig.footer,
237
281
  githubRepo: userConfig.githubRepo,
238
282
  tabs: userConfig.tabs,
283
+ codeTheme: userConfig.codeTheme,
284
+ copyMarkdown: userConfig.copyMarkdown,
285
+ breadcrumbs: userConfig.breadcrumbs,
286
+ poweredBy: userConfig.poweredBy,
287
+ communityHelp: userConfig.communityHelp,
288
+ version: userConfig.version,
289
+ editLink: userConfig.editLink
239
290
  }
240
291
 
241
- // User can define properties at top level or inside themeConfig
292
+ // User can define properties at top level or inside themeConfig/theme
242
293
  const userThemeConfig: BoltdocsThemeConfig = {
243
294
  ...themeConfigFromTop,
244
295
  ...(userConfig.themeConfig || {}),
296
+ ...(userConfig.theme || {}),
245
297
  }
246
298
 
247
299
  // Clean undefined properties
@@ -263,13 +315,10 @@ export async function resolveConfig(
263
315
 
264
316
  return {
265
317
  docsDir: path.resolve(docsDir),
266
- themeConfig: {
267
- ...defaults.themeConfig,
318
+ homePage: userConfig.homePage,
319
+ theme: {
320
+ ...defaults.theme,
268
321
  ...cleanThemeConfig,
269
- codeTheme:
270
- cleanThemeConfig.codeTheme ||
271
- (userConfig.themeConfig || userConfig).codeTheme ||
272
- defaults.themeConfig?.codeTheme,
273
322
  },
274
323
  i18n: userConfig.i18n,
275
324
  versions: userConfig.versions,
@@ -277,5 +326,8 @@ export async function resolveConfig(
277
326
  plugins: userConfig.plugins || [],
278
327
  external: userConfig.external,
279
328
  integrations: userConfig.integrations,
329
+ robots: userConfig.robots,
330
+ vite: userConfig.vite,
280
331
  }
281
332
  }
333
+
package/src/node/index.ts CHANGED
@@ -1,9 +1,11 @@
1
- import type { Plugin } from 'vite'
1
+ import type { Plugin, InlineConfig } from 'vite'
2
+ import react from '@vitejs/plugin-react'
3
+ import tailwindcss from '@tailwindcss/vite'
2
4
  import { boltdocsPlugin } from './plugin/index'
3
5
  import { boltdocsMdxPlugin } from './mdx'
4
6
  import type { BoltdocsPluginOptions } from './plugin/index'
5
7
 
6
- import { resolveConfig } from './config'
8
+ import { resolveConfig, type BoltdocsConfig } from './config'
7
9
 
8
10
  export default async function boltdocs(
9
11
  options?: BoltdocsPluginOptions,
@@ -11,7 +13,40 @@ export default async function boltdocs(
11
13
  const docsDir = options?.docsDir || 'docs'
12
14
  const config = await resolveConfig(docsDir)
13
15
 
14
- return [...boltdocsPlugin(options, config), boltdocsMdxPlugin(config)]
16
+ // Merge options with config
17
+ const mergedOptions: BoltdocsPluginOptions = {
18
+ ...options,
19
+ homePage: options?.homePage || config.homePage,
20
+ }
21
+
22
+ return [...boltdocsPlugin(mergedOptions, config), boltdocsMdxPlugin(config)]
23
+ }
24
+
25
+ /**
26
+ * Generates the complete Vite configuration for a Boltdocs project.
27
+ * This is used by the Boltdocs CLI to run Vite without a user-defined vite.config.ts.
28
+ */
29
+ export async function createViteConfig(
30
+ root: string,
31
+ mode: 'development' | 'production' = 'development',
32
+ ): Promise<InlineConfig> {
33
+ const config = await resolveConfig('docs', root)
34
+
35
+ const viteConfig: InlineConfig = {
36
+ root,
37
+ mode,
38
+ plugins: [
39
+ react(),
40
+ tailwindcss(),
41
+ await boltdocs({
42
+ docsDir: config.docsDir,
43
+ homePage: config.homePage,
44
+ }),
45
+ ],
46
+ ...config.vite,
47
+ }
48
+
49
+ return viteConfig
15
50
  }
16
51
 
17
52
  export type { BoltdocsPluginOptions }
@@ -19,3 +54,4 @@ export { generateStaticPages } from './ssg'
19
54
  export type { SSGOptions } from './ssg'
20
55
  export type { RouteMeta } from './routes'
21
56
  export type { BoltdocsConfig, BoltdocsThemeConfig } from './config'
57
+ export { resolveConfig, defineConfig } from './config'
@@ -2,6 +2,7 @@ import { normalizePath } from '../utils'
2
2
  import type { BoltdocsConfig } from '../config'
3
3
  import type { BoltdocsPluginOptions } from './types'
4
4
  import path from 'path'
5
+ import fs from 'fs'
5
6
 
6
7
  /**
7
8
  * Generates the raw source code for the virtual entry file (`\0virtual:boltdocs-entry`).
@@ -18,6 +19,11 @@ export function generateEntryCode(
18
19
  const homeImport = options.homePage
19
20
  ? `import HomePage from '${normalizePath(options.homePage)}';`
20
21
  : ''
22
+
23
+ // Auto-import index.css if it exists
24
+ const cssPath = path.resolve(process.cwd(), 'index.css')
25
+ const cssImport = fs.existsSync(cssPath) ? "import './index.css';" : ''
26
+
21
27
  const homeOption = options.homePage ? 'homePage: HomePage,' : ''
22
28
  const pluginComponents =
23
29
  config?.plugins?.flatMap((p) => Object.entries(p.components || {})) || []
@@ -54,6 +60,7 @@ import { createBoltdocsApp as _createApp } from 'boltdocs/client';
54
60
  import _routes from 'virtual:boltdocs-routes';
55
61
  import _config from 'virtual:boltdocs-config';
56
62
  import _user_mdx_components from 'virtual:boltdocs-mdx-components';
63
+ ${cssImport}
57
64
  ${homeImport}
58
65
  ${componentImports}
59
66
  ${externalImports}
@@ -1,27 +1,61 @@
1
1
  import type { BoltdocsConfig } from '../config'
2
2
 
3
+ /**
4
+ * Provides a default HTML template if none is found in the project root.
5
+ */
6
+ export function getHtmlTemplate(config: BoltdocsConfig): string {
7
+ const title = config.theme?.title || config.themeConfig?.title || 'Boltdocs'
8
+ return `<!doctype html>
9
+ <html lang="en">
10
+ <head>
11
+ <meta charset="UTF-8" />
12
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
13
+ <title>${title}</title>
14
+ </head>
15
+ <body>
16
+ <div id="root"></div>
17
+ </body>
18
+ </html>`
19
+ }
20
+
3
21
  /**
4
22
  * Injects OpenGraph, Twitter, and generic SEO meta tags into the final HTML output.
5
23
  * Also ensures the virtual entry file is injected if it's missing (e.g., standard Vite index.html).
6
24
  *
7
- * @param html - The original HTML string
8
- * @param config - The resolved Boltdocs configuration containing site metadata
9
- * @returns The modified HTML string with injected tags
25
+ * @param html - {string} The original HTML string
26
+ * @param config - {BoltdocsConfig} The resolved Boltdocs configuration containing site metadata
27
+ * @returns {string} The modified HTML string with injected tags
10
28
  */
11
29
  export function injectHtmlMeta(html: string, config: BoltdocsConfig): string {
12
- const title = config.themeConfig?.title || 'Boltdocs'
13
- const description = config.themeConfig?.description || ''
30
+ const theme = config.theme || config.themeConfig
31
+ const title = theme?.title || 'Boltdocs'
32
+ const description = theme?.description || ''
33
+
34
+ // Determine favicon
35
+ let favicon = theme?.favicon
36
+ if (!favicon && theme?.logo) {
37
+ if (typeof theme.logo === 'string') {
38
+ favicon = theme.logo
39
+ } else {
40
+ favicon = theme.logo.light || theme.logo.dark
41
+ }
42
+ }
14
43
 
15
44
  const seoTags = [
45
+ favicon ? `<link rel="icon" href="${favicon}">` : '',
16
46
  `<meta name="description" content="${description}">`,
17
47
  `<meta property="og:title" content="${title}">`,
18
48
  `<meta property="og:description" content="${description}">`,
49
+ theme?.ogImage ? `<meta property="og:image" content="${theme.ogImage}">` : '',
19
50
  `<meta property="og:type" content="website">`,
20
- `<meta name="twitter:card" content="summary">`,
51
+ `<meta name="twitter:card" content="summary_large_image">`,
21
52
  `<meta name="twitter:title" content="${title}">`,
22
53
  `<meta name="twitter:description" content="${description}">`,
54
+ theme?.ogImage ? `<meta name="twitter:image" content="${theme.ogImage}">` : '',
23
55
  `<meta name="generator" content="Boltdocs">`,
24
- ].join('\n ')
56
+ ]
57
+ .filter(Boolean)
58
+ .join('\n ')
25
59
 
26
60
  const themeScript = `
27
61
  <script>
@@ -41,10 +75,16 @@ export function injectHtmlMeta(html: string, config: BoltdocsConfig): string {
41
75
  </script>
42
76
  `
43
77
 
44
- html = html.replace(/<title>.*?<\/title>/, `<title>${title}</title>`)
78
+ // Use regex to replace title or inject it if missing
79
+ if (html.includes('<title>')) {
80
+ html = html.replace(/<title>.*?<\/title>/, `<title>${title}</title>`)
81
+ } else {
82
+ html = html.replace('</head>', ` <title>${title}</title>\n </head>`)
83
+ }
84
+
45
85
  html = html.replace('</head>', ` ${seoTags}\n${themeScript} </head>`)
46
86
 
47
- if (!html.includes('src/main')) {
87
+ if (!html.includes('src/main') && !html.includes('virtual:boltdocs-entry')) {
48
88
  html = html.replace(
49
89
  '</body>',
50
90
  ' <script type="module">import "virtual:boltdocs-entry";</script>\n </body>',
@@ -5,12 +5,13 @@ import { resolveConfig, type BoltdocsConfig, CONFIG_FILES } from '../config'
5
5
  import { generateStaticPages } from '../ssg'
6
6
  import { normalizePath, isDocFile } from '../utils'
7
7
  import path from 'path'
8
-
9
8
  import type { BoltdocsPluginOptions } from './types'
10
9
  import { generateEntryCode } from './entry'
11
- import { injectHtmlMeta } from './html'
10
+ import { injectHtmlMeta, getHtmlTemplate } from './html'
11
+ import { generateRobotsTxt } from '../ssg/robots'
12
12
  import fs from 'fs'
13
13
 
14
+
14
15
  export * from './types'
15
16
 
16
17
  /**
@@ -63,7 +64,44 @@ export function boltdocsPlugin(
63
64
  },
64
65
 
65
66
  configureServer(server) {
66
- // Explicitly watch config files and mdx-components to trigger server restarts or module invalidations
67
+ // Serve robots.txt from config
68
+ server.middlewares.use((req, res, next) => {
69
+ if (req.url === '/robots.txt') {
70
+ const robots = generateRobotsTxt(config)
71
+ res.statusCode = 200
72
+ res.setHeader('Content-Type', 'text/plain')
73
+ res.end(robots)
74
+ return
75
+ }
76
+ next()
77
+ })
78
+
79
+ // Serve default HTML if index.html is missing
80
+ server.middlewares.use(async (req, res, next) => {
81
+ const url = req.url?.split('?')[0] || '/'
82
+ const accept = req.headers.accept || ''
83
+
84
+ if (
85
+ accept.includes('text/html') &&
86
+ !url.includes('.') // Simple check for assets
87
+ ) {
88
+ const indexPath = path.resolve(process.cwd(), 'index.html')
89
+ if (!fs.existsSync(indexPath)) {
90
+ let html = getHtmlTemplate(config)
91
+ html = injectHtmlMeta(html, config)
92
+ html = await server.transformIndexHtml(req.url || '/', html)
93
+ res.statusCode = 200
94
+ res.setHeader('Content-Type', 'text/html')
95
+ res.end(html)
96
+ return
97
+ }
98
+ }
99
+
100
+ next()
101
+ })
102
+
103
+ // Explicitly watch config files...
104
+
67
105
  const configPaths = CONFIG_FILES.map((c) =>
68
106
  path.resolve(process.cwd(), c),
69
107
  )
@@ -172,6 +210,7 @@ export function boltdocsPlugin(
172
210
  }
173
211
  if (id === '\0virtual:boltdocs-config') {
174
212
  const clientConfig = {
213
+ theme: config?.theme,
175
214
  themeConfig: config?.themeConfig,
176
215
  integrations: config?.integrations,
177
216
  i18n: config?.i18n,
@@ -8,6 +8,7 @@ import { createRequire } from 'module'
8
8
  import type { SSGOptions } from './options'
9
9
  import { replaceMetaTags } from './meta'
10
10
  import { generateSitemap } from './sitemap'
11
+ import { generateRobotsTxt } from './robots'
11
12
 
12
13
  // Re-export options for consumers
13
14
  export type { SSGOptions }
@@ -122,8 +123,12 @@ export async function generateStaticPages(options: SSGOptions): Promise<void> {
122
123
  )
123
124
  fs.writeFileSync(path.join(outDir, 'sitemap.xml'), sitemap, 'utf-8')
124
125
 
126
+ // Generate robots.txt
127
+ const robots = generateRobotsTxt(config!)
128
+ fs.writeFileSync(path.join(outDir, 'robots.txt'), robots, 'utf-8')
129
+
125
130
  console.log(
126
- `[boltdocs] Generated ${routes.length} static pages + sitemap.xml`,
131
+ `[boltdocs] Generated ${routes.length} static pages + sitemap.xml + robots.txt`,
127
132
  )
128
133
 
129
134
  // Ensure all cache operations (like index persistence) are finished
@@ -0,0 +1,50 @@
1
+ import type { BoltdocsConfig } from '../config'
2
+
3
+ /**
4
+ * Generates the content for a robots.txt file based on the Boltdocs configuration.
5
+ *
6
+ * @param config - The resolved Boltdocs configuration
7
+ * @returns The formatted robots.txt string
8
+ */
9
+ export function generateRobotsTxt(config: BoltdocsConfig): string {
10
+ if (typeof config.robots === 'string') {
11
+ return config.robots
12
+ }
13
+
14
+ const siteUrl = config.siteUrl?.replace(/\/$/, '') || ''
15
+ const robots = config.robots || {}
16
+ const rules = (robots as any).rules || [
17
+ {
18
+ userAgent: '*',
19
+ allow: '/',
20
+ },
21
+ ]
22
+ const sitemaps = (robots as any).sitemaps || (siteUrl ? [`${siteUrl}/sitemap.xml`] : [])
23
+
24
+ let content = ''
25
+
26
+ for (const rule of rules) {
27
+ content += `User-agent: ${rule.userAgent}\n`
28
+
29
+ if (rule.disallow) {
30
+ const disallows = Array.isArray(rule.disallow) ? rule.disallow : [rule.disallow]
31
+ for (const d of disallows) {
32
+ content += `Disallow: ${d}\n`
33
+ }
34
+ }
35
+
36
+ if (rule.allow) {
37
+ const allows = Array.isArray(rule.allow) ? rule.allow : [rule.allow]
38
+ for (const a of allows) {
39
+ content += `Allow: ${a}\n`
40
+ }
41
+ }
42
+ content += '\n'
43
+ }
44
+
45
+ for (const sitemap of sitemaps) {
46
+ content += `Sitemap: ${sitemap}\n`
47
+ }
48
+
49
+ return content.trim()
50
+ }