boltdocs 2.5.4 → 2.5.6
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/bin/boltdocs.js +1 -1
- package/dist/cache-Cr8W2zgZ.cjs +6 -0
- package/dist/cache-DFdakSmR.mjs +6 -0
- package/dist/client/index.d.mts +1276 -861
- package/dist/client/index.d.ts +1276 -861
- package/dist/client/index.js +6 -1
- package/dist/client/index.mjs +6 -1
- package/dist/client/ssr.cjs +6 -0
- package/dist/client/ssr.d.cts +80 -0
- package/dist/client/ssr.d.mts +63 -61
- package/dist/client/ssr.mjs +6 -1
- package/dist/client/theme/neutral.css +388 -0
- package/dist/node/cli-entry.cjs +8 -0
- package/dist/node/cli-entry.d.cts +2 -0
- package/dist/node/cli-entry.d.mts +2 -1
- package/dist/node/cli-entry.mjs +7 -5
- package/dist/node/index.cjs +6 -0
- package/dist/node/index.d.cts +574 -0
- package/dist/node/index.d.mts +385 -378
- package/dist/node/index.mjs +6 -1
- package/dist/node-CWXme96p.mjs +73 -0
- package/dist/node-VYfhzGrh.cjs +73 -0
- package/dist/package-BY8Jd2j4.cjs +6 -0
- package/dist/package-OFZf0s2j.mjs +6 -0
- package/dist/search-dialog-BeNyI_KQ.mjs +6 -0
- package/dist/search-dialog-dYsCAk5S.js +6 -0
- package/dist/use-search-D25n0PrV.mjs +6 -0
- package/dist/use-search-WuzdH1cJ.js +6 -0
- package/package.json +16 -12
- package/src/client/app/index.tsx +15 -12
- package/src/client/components/default-layout.tsx +21 -19
- package/src/client/hooks/use-i18n.ts +1 -1
- package/src/client/hooks/use-routes.ts +1 -1
- package/src/client/hooks/use-version.ts +1 -1
- package/src/client/store/boltdocs-context.tsx +119 -0
- package/CHANGELOG.md +0 -92
- package/dist/cache-3FOEPC2P.mjs +0 -1
- package/dist/chunk-IMEKU5U3.mjs +0 -75
- package/dist/chunk-J2PTDWZM.mjs +0 -1
- package/dist/chunk-TP5KMRD3.mjs +0 -1
- package/dist/chunk-Y4RE5KI7.mjs +0 -1
- package/dist/client/ssr.d.ts +0 -78
- package/dist/client/ssr.js +0 -1
- package/dist/node/cli-entry.d.ts +0 -1
- package/dist/node/cli-entry.js +0 -80
- package/dist/node/index.d.ts +0 -567
- package/dist/node/index.js +0 -75
- package/dist/package-KCTE4HFV.mjs +0 -1
- package/dist/search-dialog-O6VLVSOA.mjs +0 -1
- package/src/client/store/use-boltdocs-store.ts +0 -44
- package/src/node/cache.ts +0 -408
- package/src/node/cli/build.ts +0 -53
- package/src/node/cli/dev.ts +0 -22
- package/src/node/cli/doctor.ts +0 -243
- package/src/node/cli/index.ts +0 -9
- package/src/node/cli/ui.ts +0 -54
- package/src/node/cli-entry.ts +0 -24
- package/src/node/config.ts +0 -382
- package/src/node/errors.ts +0 -44
- package/src/node/index.ts +0 -84
- package/src/node/mdx/cache.ts +0 -12
- package/src/node/mdx/highlighter.ts +0 -47
- package/src/node/mdx/index.ts +0 -122
- package/src/node/mdx/rehype-shiki.ts +0 -62
- package/src/node/mdx/remark-code-meta.ts +0 -35
- package/src/node/mdx/remark-shiki.ts +0 -61
- package/src/node/plugin/entry.ts +0 -87
- package/src/node/plugin/html.ts +0 -99
- package/src/node/plugin/index.ts +0 -464
- package/src/node/plugin/types.ts +0 -9
- package/src/node/plugins/index.ts +0 -17
- package/src/node/plugins/plugin-errors.ts +0 -62
- package/src/node/plugins/plugin-lifecycle.ts +0 -117
- package/src/node/plugins/plugin-sandbox.ts +0 -59
- package/src/node/plugins/plugin-store.ts +0 -54
- package/src/node/plugins/plugin-types.ts +0 -107
- package/src/node/plugins/plugin-validator.ts +0 -105
- package/src/node/routes/cache.ts +0 -28
- package/src/node/routes/index.ts +0 -293
- package/src/node/routes/parser.ts +0 -262
- package/src/node/routes/sorter.ts +0 -42
- package/src/node/routes/types.ts +0 -61
- package/src/node/schema/config.ts +0 -195
- package/src/node/schema/frontmatter.ts +0 -17
- package/src/node/search/index.ts +0 -55
- package/src/node/security/constants/index.ts +0 -10
- package/src/node/security/csp.ts +0 -31
- package/src/node/security/headers.ts +0 -27
- package/src/node/ssg/index.ts +0 -205
- package/src/node/ssg/meta.ts +0 -33
- package/src/node/ssg/options.ts +0 -15
- package/src/node/ssg/robots.ts +0 -53
- package/src/node/ssg/sitemap.ts +0 -55
- package/src/node/utils.ts +0 -349
- package/tsconfig.json +0 -26
- package/tsup.config.ts +0 -56
package/src/node/plugin/index.ts
DELETED
|
@@ -1,464 +0,0 @@
|
|
|
1
|
-
import { type Plugin, type ResolvedConfig, loadEnv } from 'vite'
|
|
2
|
-
import { generateRoutes, invalidateRouteCache, invalidateFile } from '../routes'
|
|
3
|
-
import { ViteImageOptimizer } from 'vite-plugin-image-optimizer'
|
|
4
|
-
import { resolveConfig, type BoltdocsConfig, CONFIG_FILES } from '../config'
|
|
5
|
-
import { generateStaticPages } from '../ssg'
|
|
6
|
-
import { normalizePath, isDocFile } from '../utils'
|
|
7
|
-
import path from 'path'
|
|
8
|
-
import type { BoltdocsPluginOptions } from './types'
|
|
9
|
-
import { generateEntryCode } from './entry'
|
|
10
|
-
import { injectHtmlMeta, getHtmlTemplate } from './html'
|
|
11
|
-
import { generateRobotsTxt } from '../ssg/robots'
|
|
12
|
-
import { generateSearchData } from '../search'
|
|
13
|
-
import { SECURITY_HEADERS } from '../security/headers'
|
|
14
|
-
import { getCSPHeader } from '../security/csp'
|
|
15
|
-
import fs from 'fs'
|
|
16
|
-
import {
|
|
17
|
-
PluginLifecycleManager,
|
|
18
|
-
validatePlugins,
|
|
19
|
-
PluginSandbox,
|
|
20
|
-
type SecureBoltdocsPlugin,
|
|
21
|
-
} from '../plugins'
|
|
22
|
-
|
|
23
|
-
export * from './types'
|
|
24
|
-
|
|
25
|
-
/**
|
|
26
|
-
* The core Boltdocs Vite plugin.
|
|
27
|
-
* Handles virtual module resolution, HMR for documentation files,
|
|
28
|
-
* injecting HTML meta tags for SEO, and triggering the SSG process on build.
|
|
29
|
-
*
|
|
30
|
-
* @param options - Optional configuration for the plugin
|
|
31
|
-
* @param passedConfig - Pre-resolved configuration (internal use)
|
|
32
|
-
* @returns An array of Vite plugins
|
|
33
|
-
*/
|
|
34
|
-
export function boltdocsPlugin(
|
|
35
|
-
options: BoltdocsPluginOptions = {},
|
|
36
|
-
passedConfig?: BoltdocsConfig,
|
|
37
|
-
): Plugin[] {
|
|
38
|
-
const docsDir = path.resolve(process.cwd(), options.docsDir || 'docs')
|
|
39
|
-
const normalizedDocsDir = normalizePath(docsDir)
|
|
40
|
-
let config: BoltdocsConfig = passedConfig!
|
|
41
|
-
let viteConfig: ResolvedConfig
|
|
42
|
-
let isBuild = false
|
|
43
|
-
let lifecycle: PluginLifecycleManager
|
|
44
|
-
|
|
45
|
-
// Use a placeholder for extra plugins that will be populated once config is resolved
|
|
46
|
-
let resolvedExtraVitePlugins: Plugin[] = []
|
|
47
|
-
|
|
48
|
-
return [
|
|
49
|
-
{
|
|
50
|
-
name: 'vite-plugin-boltdocs',
|
|
51
|
-
enforce: 'pre',
|
|
52
|
-
|
|
53
|
-
async config(userConfig, env) {
|
|
54
|
-
isBuild = env.command === 'build'
|
|
55
|
-
|
|
56
|
-
// Load env variables and inject into process.env so they are available in boltdocs.config.js
|
|
57
|
-
const envDir = userConfig.envDir || process.cwd()
|
|
58
|
-
const envs = loadEnv(env.mode, envDir, '')
|
|
59
|
-
Object.assign(process.env, envs)
|
|
60
|
-
|
|
61
|
-
// Resolve config async if not already passed
|
|
62
|
-
if (!config) {
|
|
63
|
-
config = await resolveConfig(docsDir)
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
// --- NEW: Secure Plugin Initialization ---
|
|
67
|
-
const boltdocsVersion = (await import('../../../package.json')).version
|
|
68
|
-
const validatedPlugins = validatePlugins(
|
|
69
|
-
(config.plugins || []) as SecureBoltdocsPlugin[],
|
|
70
|
-
boltdocsVersion,
|
|
71
|
-
)
|
|
72
|
-
|
|
73
|
-
// Replace config plugins with validated ones
|
|
74
|
-
config.plugins = validatedPlugins as any
|
|
75
|
-
|
|
76
|
-
// Initialize lifecycle manager
|
|
77
|
-
lifecycle = new PluginLifecycleManager(validatedPlugins, config)
|
|
78
|
-
|
|
79
|
-
// Populate validated extra Vite plugins
|
|
80
|
-
resolvedExtraVitePlugins = validatedPlugins.flatMap((p) => {
|
|
81
|
-
const caps = PluginSandbox.getSanitizedCapabilities(p)
|
|
82
|
-
return (caps.vitePlugins || []) as Plugin[]
|
|
83
|
-
})
|
|
84
|
-
|
|
85
|
-
// Run beforeBuild if building
|
|
86
|
-
if (isBuild) {
|
|
87
|
-
await lifecycle.runHook('beforeBuild')
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
return {
|
|
91
|
-
optimizeDeps: {
|
|
92
|
-
include: [
|
|
93
|
-
'react',
|
|
94
|
-
'react-dom',
|
|
95
|
-
'use-sync-external-store/shim',
|
|
96
|
-
'use-sync-external-store/shim/index.js',
|
|
97
|
-
'use-sync-external-store/with-selector',
|
|
98
|
-
'use-sync-external-store'
|
|
99
|
-
],
|
|
100
|
-
exclude: [
|
|
101
|
-
'boltdocs',
|
|
102
|
-
'boltdocs/client',
|
|
103
|
-
'boltdocs/hooks',
|
|
104
|
-
'boltdocs/primitives',
|
|
105
|
-
'boltdocs/base-ui',
|
|
106
|
-
'boltdocs/mdx',
|
|
107
|
-
'boltdocs/integrations',
|
|
108
|
-
'boltdocs/client/hooks',
|
|
109
|
-
'boltdocs/client/primitives',
|
|
110
|
-
],
|
|
111
|
-
},
|
|
112
|
-
}
|
|
113
|
-
},
|
|
114
|
-
|
|
115
|
-
configResolved(resolved) {
|
|
116
|
-
viteConfig = resolved
|
|
117
|
-
lifecycle?.runHook('configResolved', config)
|
|
118
|
-
},
|
|
119
|
-
|
|
120
|
-
async configureServer(server) {
|
|
121
|
-
await lifecycle?.runHook('beforeDev')
|
|
122
|
-
|
|
123
|
-
// Security: Apply hardened headers and CSP
|
|
124
|
-
server.middlewares.use((_req, res, next) => {
|
|
125
|
-
const isProd = process.env.NODE_ENV === 'production'
|
|
126
|
-
|
|
127
|
-
if (isProd) {
|
|
128
|
-
Object.entries(SECURITY_HEADERS).forEach(([header, value]) => {
|
|
129
|
-
res.setHeader(header, value)
|
|
130
|
-
})
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
// Inject CSP if enabled in configuration
|
|
134
|
-
if (config.security?.enableCSP) {
|
|
135
|
-
res.setHeader('Content-Security-Policy', getCSPHeader(config))
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
next()
|
|
139
|
-
})
|
|
140
|
-
|
|
141
|
-
// Serve robots.txt from config
|
|
142
|
-
server.middlewares.use((req, res, next) => {
|
|
143
|
-
if (req.url === '/robots.txt') {
|
|
144
|
-
const robots = generateRobotsTxt(config)
|
|
145
|
-
res.statusCode = 200
|
|
146
|
-
res.setHeader('Content-Type', 'text/plain')
|
|
147
|
-
res.end(robots)
|
|
148
|
-
return
|
|
149
|
-
}
|
|
150
|
-
next()
|
|
151
|
-
})
|
|
152
|
-
|
|
153
|
-
// Serve default HTML for documentation routes or if index.html is missing
|
|
154
|
-
server.middlewares.use(async (req, res, next) => {
|
|
155
|
-
const url = req.url?.split('?')[0] || '/'
|
|
156
|
-
const accept = req.headers.accept || ''
|
|
157
|
-
|
|
158
|
-
const isDocRoute =
|
|
159
|
-
url === '/' ||
|
|
160
|
-
url.startsWith('/docs') ||
|
|
161
|
-
(config.i18n &&
|
|
162
|
-
Object.keys(config.i18n.locales).some(
|
|
163
|
-
(locale) =>
|
|
164
|
-
url.startsWith(`/${locale}/docs`) || url === `/${locale}`,
|
|
165
|
-
)) ||
|
|
166
|
-
// Handle any HTML request that isn't a known static file or docs,
|
|
167
|
-
// as it potentially could be an external page.
|
|
168
|
-
// (The client-side router will handle 404s if it doesn't match anything)
|
|
169
|
-
true
|
|
170
|
-
|
|
171
|
-
// Improved check: If it's a doc route, serve HTML even if it has a dot (e.g. version 1.1)
|
|
172
|
-
// We only skip if it has a known asset extension to prevent serving HTML for images/js/etc.
|
|
173
|
-
const isAsset =
|
|
174
|
-
/\.(js|css|png|jpe?g|gif|svg|ico|webp|woff2?|ttf|otf|mp4|webm|ogg|mp3|wav|flac|aac|pdf|zip|gz|map|json)$/i.test(
|
|
175
|
-
url,
|
|
176
|
-
)
|
|
177
|
-
|
|
178
|
-
if (accept.includes('text/html') && !isAsset && isDocRoute) {
|
|
179
|
-
let html = getHtmlTemplate(config)
|
|
180
|
-
html = injectHtmlMeta(html, config)
|
|
181
|
-
html = await server.transformIndexHtml(req.url || '/', html)
|
|
182
|
-
res.statusCode = 200
|
|
183
|
-
res.setHeader('Content-Type', 'text/html')
|
|
184
|
-
res.end(html)
|
|
185
|
-
return
|
|
186
|
-
}
|
|
187
|
-
|
|
188
|
-
next()
|
|
189
|
-
})
|
|
190
|
-
|
|
191
|
-
// Explicitly watch config files...
|
|
192
|
-
|
|
193
|
-
const configPaths = CONFIG_FILES.map((c) =>
|
|
194
|
-
path.resolve(process.cwd(), c),
|
|
195
|
-
)
|
|
196
|
-
const compExtensions = ['tsx', 'jsx']
|
|
197
|
-
const layoutCompPaths = compExtensions.map((ext) =>
|
|
198
|
-
path.resolve(docsDir, `layout.${ext}`),
|
|
199
|
-
)
|
|
200
|
-
const mdxCompExtensions = ['tsx', 'ts', 'jsx', 'js']
|
|
201
|
-
const mdxCompPaths = mdxCompExtensions.map((ext) =>
|
|
202
|
-
path.resolve(docsDir, `mdx-components.${ext}`),
|
|
203
|
-
)
|
|
204
|
-
const extPagesPaths = mdxCompExtensions.map((ext) =>
|
|
205
|
-
path.resolve(docsDir, `pages-external/index.${ext}`),
|
|
206
|
-
)
|
|
207
|
-
|
|
208
|
-
server.watcher.add([
|
|
209
|
-
...configPaths,
|
|
210
|
-
...mdxCompPaths,
|
|
211
|
-
...layoutCompPaths,
|
|
212
|
-
...extPagesPaths,
|
|
213
|
-
])
|
|
214
|
-
|
|
215
|
-
const handleFileEvent = async (
|
|
216
|
-
file: string,
|
|
217
|
-
type: 'add' | 'unlink' | 'change',
|
|
218
|
-
) => {
|
|
219
|
-
try {
|
|
220
|
-
const normalized = normalizePath(file)
|
|
221
|
-
|
|
222
|
-
// Restart the Vite server if the Boltdocs config changes
|
|
223
|
-
if (CONFIG_FILES.some((c) => normalized.endsWith(c))) {
|
|
224
|
-
server.restart()
|
|
225
|
-
return
|
|
226
|
-
}
|
|
227
|
-
|
|
228
|
-
// If mdx-components file changes, invalidate the virtual module
|
|
229
|
-
if (
|
|
230
|
-
mdxCompExtensions.some((ext) =>
|
|
231
|
-
normalized.endsWith(`mdx-components.${ext}`),
|
|
232
|
-
)
|
|
233
|
-
) {
|
|
234
|
-
const mod = server.moduleGraph.getModuleById(
|
|
235
|
-
'\0virtual:boltdocs-mdx-components',
|
|
236
|
-
)
|
|
237
|
-
if (mod) server.moduleGraph.invalidateModule(mod)
|
|
238
|
-
server.ws.send({ type: 'full-reload' })
|
|
239
|
-
return
|
|
240
|
-
}
|
|
241
|
-
|
|
242
|
-
// If layout.tsx/jsx file changes, invalidate the virtual module
|
|
243
|
-
if (
|
|
244
|
-
compExtensions.some((ext) => normalized.endsWith(`layout.${ext}`))
|
|
245
|
-
) {
|
|
246
|
-
const mod = server.moduleGraph.getModuleById(
|
|
247
|
-
'\0virtual:boltdocs-layout',
|
|
248
|
-
)
|
|
249
|
-
if (mod) server.moduleGraph.invalidateModule(mod)
|
|
250
|
-
server.ws.send({ type: 'full-reload' })
|
|
251
|
-
return
|
|
252
|
-
}
|
|
253
|
-
|
|
254
|
-
// If any pages-external file changes, invalidate the entry module
|
|
255
|
-
if (
|
|
256
|
-
normalized.includes('/pages-external/') ||
|
|
257
|
-
normalized.includes('\\pages-external\\')
|
|
258
|
-
) {
|
|
259
|
-
const mod = server.moduleGraph.getModuleById(
|
|
260
|
-
'\0virtual:boltdocs-entry',
|
|
261
|
-
)
|
|
262
|
-
if (mod) server.moduleGraph.invalidateModule(mod)
|
|
263
|
-
server.ws.send({ type: 'full-reload' })
|
|
264
|
-
return
|
|
265
|
-
}
|
|
266
|
-
|
|
267
|
-
if (
|
|
268
|
-
!normalized.startsWith(normalizedDocsDir) ||
|
|
269
|
-
!isDocFile(normalized)
|
|
270
|
-
)
|
|
271
|
-
return
|
|
272
|
-
|
|
273
|
-
// Invalidate appropriately
|
|
274
|
-
if (type === 'add' || type === 'unlink') {
|
|
275
|
-
invalidateRouteCache()
|
|
276
|
-
// Re-resolve config as it might affect versions/routes
|
|
277
|
-
config = await resolveConfig(docsDir)
|
|
278
|
-
|
|
279
|
-
const configMod = server.moduleGraph.getModuleById(
|
|
280
|
-
'\0virtual:boltdocs-config',
|
|
281
|
-
)
|
|
282
|
-
if (configMod) server.moduleGraph.invalidateModule(configMod)
|
|
283
|
-
|
|
284
|
-
server.ws.send({
|
|
285
|
-
type: 'custom',
|
|
286
|
-
event: 'boltdocs:config-update',
|
|
287
|
-
data: {
|
|
288
|
-
theme: config?.theme,
|
|
289
|
-
i18n: config?.i18n,
|
|
290
|
-
versions: config?.versions,
|
|
291
|
-
siteUrl: config?.siteUrl,
|
|
292
|
-
},
|
|
293
|
-
})
|
|
294
|
-
} else {
|
|
295
|
-
invalidateFile(file)
|
|
296
|
-
}
|
|
297
|
-
|
|
298
|
-
// Regenerate and push to client
|
|
299
|
-
// Optimization: generateRoutes is mostly incremental thanks to docCache
|
|
300
|
-
// We only force a full disk scan on add/unlink events
|
|
301
|
-
const newRoutes = await generateRoutes(
|
|
302
|
-
docsDir,
|
|
303
|
-
config,
|
|
304
|
-
'/docs',
|
|
305
|
-
type !== 'change',
|
|
306
|
-
)
|
|
307
|
-
|
|
308
|
-
const routesMod = server.moduleGraph.getModuleById(
|
|
309
|
-
'\0virtual:boltdocs-routes',
|
|
310
|
-
)
|
|
311
|
-
if (routesMod) server.moduleGraph.invalidateModule(routesMod)
|
|
312
|
-
|
|
313
|
-
server.ws.send({
|
|
314
|
-
type: 'custom',
|
|
315
|
-
event: 'boltdocs:routes-update',
|
|
316
|
-
data: newRoutes,
|
|
317
|
-
})
|
|
318
|
-
} catch (e) {
|
|
319
|
-
console.error(`[boltdocs] HMR error during ${type} event:`, e)
|
|
320
|
-
}
|
|
321
|
-
}
|
|
322
|
-
|
|
323
|
-
server.watcher.on('add', (f) => handleFileEvent(f, 'add'))
|
|
324
|
-
server.watcher.on('unlink', (f) => handleFileEvent(f, 'unlink'))
|
|
325
|
-
server.watcher.on('change', (f) => handleFileEvent(f, 'change'))
|
|
326
|
-
|
|
327
|
-
await lifecycle?.runHook('afterDev')
|
|
328
|
-
},
|
|
329
|
-
|
|
330
|
-
resolveId(id) {
|
|
331
|
-
if (
|
|
332
|
-
id === 'virtual:boltdocs-routes' ||
|
|
333
|
-
id === 'virtual:boltdocs-config' ||
|
|
334
|
-
id === 'virtual:boltdocs-entry' ||
|
|
335
|
-
id === 'virtual:boltdocs-mdx-components' ||
|
|
336
|
-
id === 'virtual:boltdocs-layout' ||
|
|
337
|
-
id === 'virtual:boltdocs-search'
|
|
338
|
-
) {
|
|
339
|
-
return '\0' + id
|
|
340
|
-
}
|
|
341
|
-
},
|
|
342
|
-
|
|
343
|
-
async load(id) {
|
|
344
|
-
if (id === '\0virtual:boltdocs-routes') {
|
|
345
|
-
const routes = await generateRoutes(docsDir, config)
|
|
346
|
-
return `export default ${JSON.stringify(routes, null, 2)};`
|
|
347
|
-
}
|
|
348
|
-
if (id === '\0virtual:boltdocs-config') {
|
|
349
|
-
const clientConfig = {
|
|
350
|
-
theme: config?.theme,
|
|
351
|
-
i18n: config?.i18n,
|
|
352
|
-
versions: config?.versions,
|
|
353
|
-
siteUrl: config?.siteUrl,
|
|
354
|
-
plugins: config?.plugins?.map((p) => ({ name: p.name })),
|
|
355
|
-
}
|
|
356
|
-
return `export default ${JSON.stringify(clientConfig, null, 2)};`
|
|
357
|
-
}
|
|
358
|
-
if (id === '\0virtual:boltdocs-entry') {
|
|
359
|
-
const code = generateEntryCode(options, config)
|
|
360
|
-
return code
|
|
361
|
-
}
|
|
362
|
-
if (id === '\0virtual:boltdocs-mdx-components') {
|
|
363
|
-
const extensions = ['tsx', 'ts', 'jsx', 'js']
|
|
364
|
-
let userMdxPath = null
|
|
365
|
-
|
|
366
|
-
for (const ext of extensions) {
|
|
367
|
-
const p = path.resolve(docsDir, `mdx-components.${ext}`)
|
|
368
|
-
if (fs.existsSync(p)) {
|
|
369
|
-
userMdxPath = p
|
|
370
|
-
break
|
|
371
|
-
}
|
|
372
|
-
}
|
|
373
|
-
|
|
374
|
-
if (userMdxPath) {
|
|
375
|
-
const normalizedPath = normalizePath(userMdxPath)
|
|
376
|
-
return `import * as components from '${normalizedPath}';
|
|
377
|
-
const mdxComponents = components.default || components;
|
|
378
|
-
export default mdxComponents;
|
|
379
|
-
export * from '${normalizedPath}';`
|
|
380
|
-
}
|
|
381
|
-
|
|
382
|
-
return `export default {};`
|
|
383
|
-
}
|
|
384
|
-
if (id === '\0virtual:boltdocs-layout') {
|
|
385
|
-
const extensions = ['tsx', 'jsx']
|
|
386
|
-
let userLayoutPath = null
|
|
387
|
-
|
|
388
|
-
for (const ext of extensions) {
|
|
389
|
-
const p = path.resolve(docsDir, `layout.${ext}`)
|
|
390
|
-
if (fs.existsSync(p)) {
|
|
391
|
-
userLayoutPath = p
|
|
392
|
-
break
|
|
393
|
-
}
|
|
394
|
-
}
|
|
395
|
-
|
|
396
|
-
if (userLayoutPath) {
|
|
397
|
-
const normalizedPath = normalizePath(userLayoutPath)
|
|
398
|
-
return `import UserLayout from '${normalizedPath}';
|
|
399
|
-
export default UserLayout;`
|
|
400
|
-
}
|
|
401
|
-
|
|
402
|
-
// No user layout — return the built-in default
|
|
403
|
-
return `import { DefaultLayout } from 'boltdocs/client';
|
|
404
|
-
export default DefaultLayout;`
|
|
405
|
-
}
|
|
406
|
-
|
|
407
|
-
if (id === '\0virtual:boltdocs-search') {
|
|
408
|
-
const routes = await generateRoutes(docsDir, config)
|
|
409
|
-
const searchData = generateSearchData(routes)
|
|
410
|
-
return `export default ${JSON.stringify(searchData, null, 2)};`
|
|
411
|
-
}
|
|
412
|
-
},
|
|
413
|
-
|
|
414
|
-
transformIndexHtml: {
|
|
415
|
-
order: 'pre',
|
|
416
|
-
handler(html) {
|
|
417
|
-
return injectHtmlMeta(html, config)
|
|
418
|
-
},
|
|
419
|
-
},
|
|
420
|
-
|
|
421
|
-
async closeBundle() {
|
|
422
|
-
if (!isBuild) return
|
|
423
|
-
const outDir = viteConfig?.build?.outDir
|
|
424
|
-
? path.resolve(viteConfig.root, viteConfig.build.outDir)
|
|
425
|
-
: path.resolve(process.cwd(), 'dist')
|
|
426
|
-
|
|
427
|
-
const docsDirName = path.basename(docsDir || 'docs')
|
|
428
|
-
await generateStaticPages({ docsDir, docsDirName, outDir, config })
|
|
429
|
-
|
|
430
|
-
const { flushCache } = await import('../cache')
|
|
431
|
-
await flushCache()
|
|
432
|
-
|
|
433
|
-
await lifecycle?.runHook('afterBuild')
|
|
434
|
-
await lifecycle?.runHook('buildEnd')
|
|
435
|
-
},
|
|
436
|
-
},
|
|
437
|
-
ViteImageOptimizer({
|
|
438
|
-
includePublic: true,
|
|
439
|
-
png: { quality: 80 },
|
|
440
|
-
jpeg: { quality: 80 },
|
|
441
|
-
jpg: { quality: 80 },
|
|
442
|
-
webp: { quality: 80 },
|
|
443
|
-
avif: { quality: 80 },
|
|
444
|
-
svg: {
|
|
445
|
-
multipass: true,
|
|
446
|
-
plugins: [
|
|
447
|
-
{
|
|
448
|
-
name: 'preset-default',
|
|
449
|
-
},
|
|
450
|
-
] as any,
|
|
451
|
-
},
|
|
452
|
-
}),
|
|
453
|
-
// Re-bind to use the lazily populated extra plugins
|
|
454
|
-
{
|
|
455
|
-
name: 'vite-plugin-boltdocs-extra-plugins',
|
|
456
|
-
async configResolved() {
|
|
457
|
-
// This is a dummy plugin to ensure the extra plugins are integrated
|
|
458
|
-
// Actually, Vite doesn't support changing the plugin list dynamically easily
|
|
459
|
-
// but we can return them in the original array if we initialize them early.
|
|
460
|
-
},
|
|
461
|
-
},
|
|
462
|
-
...(() => resolvedExtraVitePlugins)(),
|
|
463
|
-
]
|
|
464
|
-
}
|
package/src/node/plugin/types.ts
DELETED
|
@@ -1,9 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Configuration options specifically for the Boltdocs Vite plugin.
|
|
3
|
-
*/
|
|
4
|
-
export interface BoltdocsPluginOptions {
|
|
5
|
-
/** The root directory containing markdown files (default: 'docs') */
|
|
6
|
-
docsDir?: string
|
|
7
|
-
/** Path to a custom home page component (relative to project root) to render at '/' */
|
|
8
|
-
homePage?: string
|
|
9
|
-
}
|
|
@@ -1,17 +0,0 @@
|
|
|
1
|
-
export * from './plugin-types'
|
|
2
|
-
export * from './plugin-errors'
|
|
3
|
-
export * from './plugin-store'
|
|
4
|
-
export * from './plugin-validator'
|
|
5
|
-
export * from './plugin-sandbox'
|
|
6
|
-
export * from './plugin-lifecycle'
|
|
7
|
-
|
|
8
|
-
import type { SecureBoltdocsPlugin } from './plugin-types'
|
|
9
|
-
|
|
10
|
-
/**
|
|
11
|
-
* Utility to create a Boltdocs plugin with full type safety.
|
|
12
|
-
*/
|
|
13
|
-
export function createPlugin(
|
|
14
|
-
plugin: SecureBoltdocsPlugin,
|
|
15
|
-
): SecureBoltdocsPlugin {
|
|
16
|
-
return plugin
|
|
17
|
-
}
|
|
@@ -1,62 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Base class for all plugin-related errors in Boltdocs.
|
|
3
|
-
*/
|
|
4
|
-
export class PluginError extends Error {
|
|
5
|
-
public readonly pluginName: string
|
|
6
|
-
|
|
7
|
-
constructor(pluginName: string, message: string) {
|
|
8
|
-
super(`[plugin:${pluginName}] ${message}`)
|
|
9
|
-
this.name = 'PluginError'
|
|
10
|
-
this.pluginName = pluginName
|
|
11
|
-
Object.setPrototypeOf(this, PluginError.prototype)
|
|
12
|
-
}
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
/**
|
|
16
|
-
* Specifically for schema or structure validation failures.
|
|
17
|
-
*/
|
|
18
|
-
export class PluginValidationError extends PluginError {
|
|
19
|
-
constructor(pluginName: string, message: string) {
|
|
20
|
-
super(pluginName, `Validation failed: ${message}`)
|
|
21
|
-
this.name = 'PluginValidationError'
|
|
22
|
-
Object.setPrototypeOf(this, PluginValidationError.prototype)
|
|
23
|
-
}
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
/**
|
|
27
|
-
* Specifically for version mismatch or compatibility issues.
|
|
28
|
-
*/
|
|
29
|
-
export class PluginCompatibilityError extends PluginError {
|
|
30
|
-
constructor(pluginName: string, message: string) {
|
|
31
|
-
super(pluginName, `Compatibility error: ${message}`)
|
|
32
|
-
this.name = 'PluginCompatibilityError'
|
|
33
|
-
Object.setPrototypeOf(this, PluginCompatibilityError.prototype)
|
|
34
|
-
}
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
/**
|
|
38
|
-
* Specifically for attempts to use capabilities without proper permissions.
|
|
39
|
-
*/
|
|
40
|
-
export class PluginPermissionError extends PluginError {
|
|
41
|
-
constructor(pluginName: string, permission: string) {
|
|
42
|
-
super(pluginName, `Missing required permission: '${permission}'`)
|
|
43
|
-
this.name = 'PluginPermissionError'
|
|
44
|
-
Object.setPrototypeOf(this, PluginPermissionError.prototype)
|
|
45
|
-
}
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
/**
|
|
49
|
-
* Specifically for errors that occur during the execution of a lifecycle hook.
|
|
50
|
-
*/
|
|
51
|
-
export class PluginHookError extends PluginError {
|
|
52
|
-
public readonly hookName: string
|
|
53
|
-
|
|
54
|
-
constructor(pluginName: string, hookName: string, originalError: Error) {
|
|
55
|
-
super(pluginName, `Error in hook '${hookName}': ${originalError.message}`)
|
|
56
|
-
this.name = 'PluginHookError'
|
|
57
|
-
this.hookName = hookName
|
|
58
|
-
// Prepend the hook name to the stack if possible
|
|
59
|
-
this.stack = originalError.stack
|
|
60
|
-
Object.setPrototypeOf(this, PluginHookError.prototype)
|
|
61
|
-
}
|
|
62
|
-
}
|
|
@@ -1,117 +0,0 @@
|
|
|
1
|
-
import type { BoltdocsConfig } from '../config'
|
|
2
|
-
import { PluginHookError } from './plugin-errors'
|
|
3
|
-
import {
|
|
4
|
-
PluginLifecycleHooks,
|
|
5
|
-
SecureBoltdocsPlugin,
|
|
6
|
-
PluginContext,
|
|
7
|
-
PluginLogger,
|
|
8
|
-
} from './plugin-types'
|
|
9
|
-
import { BoltdocsPluginStore } from './plugin-store'
|
|
10
|
-
import { PluginSandbox } from './plugin-sandbox'
|
|
11
|
-
|
|
12
|
-
/**
|
|
13
|
-
* Manages the lifecycle of all loaded plugins, ensuring hooks are executed
|
|
14
|
-
* in the correct order with proper error isolation and context.
|
|
15
|
-
*/
|
|
16
|
-
export class PluginLifecycleManager {
|
|
17
|
-
private plugins: SecureBoltdocsPlugin[]
|
|
18
|
-
private config: BoltdocsConfig
|
|
19
|
-
private store: BoltdocsPluginStore
|
|
20
|
-
|
|
21
|
-
constructor(plugins: SecureBoltdocsPlugin[], config: BoltdocsConfig) {
|
|
22
|
-
this.plugins = plugins
|
|
23
|
-
this.config = config
|
|
24
|
-
this.store = new BoltdocsPluginStore()
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
/**
|
|
28
|
-
* Runs a specific hook for all plugins that implement it.
|
|
29
|
-
*/
|
|
30
|
-
public async runHook(
|
|
31
|
-
hookName: keyof PluginLifecycleHooks,
|
|
32
|
-
...args: any[]
|
|
33
|
-
): Promise<void> {
|
|
34
|
-
const sortedPlugins = this.getSortedPlugins()
|
|
35
|
-
|
|
36
|
-
for (const plugin of sortedPlugins) {
|
|
37
|
-
if (!plugin.hooks?.[hookName]) continue
|
|
38
|
-
|
|
39
|
-
const context = this.createContext(plugin)
|
|
40
|
-
const isBuildHook = hookName.toLowerCase().includes('build')
|
|
41
|
-
const isDevHook = hookName.toLowerCase().includes('dev')
|
|
42
|
-
const permission = isBuildHook
|
|
43
|
-
? 'hooks:build'
|
|
44
|
-
: isDevHook
|
|
45
|
-
? 'hooks:dev'
|
|
46
|
-
: undefined
|
|
47
|
-
|
|
48
|
-
try {
|
|
49
|
-
if (permission) {
|
|
50
|
-
await PluginSandbox.executeWithIsolation(
|
|
51
|
-
plugin,
|
|
52
|
-
permission,
|
|
53
|
-
hookName,
|
|
54
|
-
() => (plugin.hooks![hookName] as any)(context, ...args),
|
|
55
|
-
)
|
|
56
|
-
} else {
|
|
57
|
-
// Hooks like configResolved might not require specific permissions or are always allowed
|
|
58
|
-
await (plugin.hooks![hookName] as any)(context, ...args)
|
|
59
|
-
}
|
|
60
|
-
} catch (error) {
|
|
61
|
-
// Isolate error: logging it but allowing other plugins to continue
|
|
62
|
-
const hookError = new PluginHookError(
|
|
63
|
-
plugin.name,
|
|
64
|
-
hookName,
|
|
65
|
-
error instanceof Error ? error : new Error(String(error)),
|
|
66
|
-
)
|
|
67
|
-
context.logger.error(hookError)
|
|
68
|
-
}
|
|
69
|
-
}
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
/**
|
|
73
|
-
* Sorts plugins based on their 'enforce' property (pre -> normal -> post).
|
|
74
|
-
*/
|
|
75
|
-
private getSortedPlugins(): SecureBoltdocsPlugin[] {
|
|
76
|
-
const pre = this.plugins.filter((p) => p.enforce === 'pre')
|
|
77
|
-
const normal = this.plugins.filter((p) => !p.enforce)
|
|
78
|
-
const post = this.plugins.filter((p) => p.enforce === 'post')
|
|
79
|
-
return [...pre, ...normal, ...post]
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
/**
|
|
83
|
-
* Creates a specialized context for a plugin.
|
|
84
|
-
*/
|
|
85
|
-
private createContext(plugin: SecureBoltdocsPlugin): PluginContext {
|
|
86
|
-
return {
|
|
87
|
-
config: Object.freeze({ ...this.config }),
|
|
88
|
-
meta: {
|
|
89
|
-
name: plugin.name,
|
|
90
|
-
version: plugin.version,
|
|
91
|
-
boltdocsVersion: plugin.boltdocsVersion,
|
|
92
|
-
},
|
|
93
|
-
store: {
|
|
94
|
-
get: (p, k) => this.store.get(p, k),
|
|
95
|
-
set: (p, k, v) => this.store.set(p, k, v),
|
|
96
|
-
has: (p, k) => this.store.has(p, k),
|
|
97
|
-
},
|
|
98
|
-
logger: this.createLogger(plugin.name),
|
|
99
|
-
}
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
/**
|
|
103
|
-
* Creates a namespaced logger for a plugin.
|
|
104
|
-
*/
|
|
105
|
-
private createLogger(pluginName: string): PluginLogger {
|
|
106
|
-
const prefix = `[plugin:${pluginName}]`
|
|
107
|
-
return {
|
|
108
|
-
info: (msg) => console.log(`${prefix} INFO: ${msg}`),
|
|
109
|
-
warn: (msg) => console.warn(`${prefix} WARN: ${msg}`),
|
|
110
|
-
error: (msg) => {
|
|
111
|
-
const message = msg instanceof Error ? msg.message : msg
|
|
112
|
-
console.error(`${prefix} ERROR: ${message}`)
|
|
113
|
-
},
|
|
114
|
-
debug: (msg) => console.debug(`${prefix} DEBUG: ${msg}`),
|
|
115
|
-
}
|
|
116
|
-
}
|
|
117
|
-
}
|