boltdocs 1.0.4 → 1.3.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 (121) hide show
  1. package/dist/{SearchDialog-R36WKAQ7.mjs → SearchDialog-5EDRACEG.mjs} +1 -1
  2. package/dist/{SearchDialog-PYF3QMYG.css → SearchDialog-X57WPTNN.css} +54 -126
  3. package/dist/cache-EHR7SXRU.mjs +12 -0
  4. package/dist/chunk-GSYECEZY.mjs +381 -0
  5. package/dist/{chunk-TWSRXUFF.mjs → chunk-NS7WHDYA.mjs} +229 -418
  6. package/dist/client/index.css +54 -126
  7. package/dist/client/index.d.mts +5 -4
  8. package/dist/client/index.d.ts +5 -4
  9. package/dist/client/index.js +555 -580
  10. package/dist/client/index.mjs +304 -16
  11. package/dist/client/ssr.css +54 -126
  12. package/dist/client/ssr.js +257 -580
  13. package/dist/client/ssr.mjs +1 -1
  14. package/dist/{config-D2XmHJYe.d.mts → config-BD5ZHz15.d.mts} +7 -0
  15. package/dist/{config-D2XmHJYe.d.ts → config-BD5ZHz15.d.ts} +7 -0
  16. package/dist/node/index.d.mts +2 -2
  17. package/dist/node/index.d.ts +2 -2
  18. package/dist/node/index.js +477 -123
  19. package/dist/node/index.mjs +114 -142
  20. package/package.json +2 -2
  21. package/src/client/app/index.tsx +344 -373
  22. package/src/client/app/preload.tsx +56 -56
  23. package/src/client/index.ts +40 -40
  24. package/src/client/ssr.tsx +51 -51
  25. package/src/client/theme/components/CodeBlock/CodeBlock.tsx +76 -76
  26. package/src/client/theme/components/CodeBlock/index.ts +1 -1
  27. package/src/client/theme/components/PackageManagerTabs/PackageManagerTabs.tsx +154 -154
  28. package/src/client/theme/components/PackageManagerTabs/index.ts +1 -1
  29. package/src/client/theme/components/PackageManagerTabs/pkg-tabs.css +64 -64
  30. package/src/client/theme/components/Playground/Playground.tsx +124 -124
  31. package/src/client/theme/components/Playground/index.ts +1 -1
  32. package/src/client/theme/components/Playground/playground.css +168 -168
  33. package/src/client/theme/components/Video/Video.tsx +84 -84
  34. package/src/client/theme/components/Video/index.ts +1 -1
  35. package/src/client/theme/components/Video/video.css +41 -41
  36. package/src/client/theme/components/mdx/Admonition.tsx +80 -80
  37. package/src/client/theme/components/mdx/Badge.tsx +31 -31
  38. package/src/client/theme/components/mdx/Button.tsx +50 -50
  39. package/src/client/theme/components/mdx/Card.tsx +80 -80
  40. package/src/client/theme/components/mdx/List.tsx +57 -57
  41. package/src/client/theme/components/mdx/Tabs.tsx +94 -94
  42. package/src/client/theme/components/mdx/index.ts +18 -18
  43. package/src/client/theme/components/mdx/mdx-components.css +424 -405
  44. package/src/client/theme/icons/bun.tsx +62 -62
  45. package/src/client/theme/icons/deno.tsx +20 -20
  46. package/src/client/theme/icons/discord.tsx +12 -12
  47. package/src/client/theme/icons/github.tsx +15 -15
  48. package/src/client/theme/icons/npm.tsx +13 -13
  49. package/src/client/theme/icons/pnpm.tsx +72 -72
  50. package/src/client/theme/icons/twitter.tsx +12 -12
  51. package/src/client/theme/styles/markdown.css +343 -343
  52. package/src/client/theme/styles/variables.css +162 -162
  53. package/src/client/theme/styles.css +37 -38
  54. package/src/client/theme/ui/BackgroundGradient/BackgroundGradient.tsx +10 -10
  55. package/src/client/theme/ui/BackgroundGradient/index.ts +1 -1
  56. package/src/client/theme/ui/Breadcrumbs/Breadcrumbs.tsx +68 -68
  57. package/src/client/theme/ui/Breadcrumbs/index.ts +1 -1
  58. package/src/client/theme/ui/Footer/footer.css +32 -32
  59. package/src/client/theme/ui/Head/Head.tsx +69 -69
  60. package/src/client/theme/ui/Head/index.ts +1 -1
  61. package/src/client/theme/ui/LanguageSwitcher/LanguageSwitcher.tsx +125 -125
  62. package/src/client/theme/ui/LanguageSwitcher/index.ts +1 -1
  63. package/src/client/theme/ui/LanguageSwitcher/language-switcher.css +98 -98
  64. package/src/client/theme/ui/Layout/Layout.tsx +202 -213
  65. package/src/client/theme/ui/Layout/base.css +76 -76
  66. package/src/client/theme/ui/Layout/index.ts +2 -2
  67. package/src/client/theme/ui/Layout/pagination.css +72 -72
  68. package/src/client/theme/ui/Layout/responsive.css +36 -40
  69. package/src/client/theme/ui/Link/Link.tsx +254 -202
  70. package/src/client/theme/ui/Link/index.ts +2 -2
  71. package/src/client/theme/ui/Loading/Loading.tsx +10 -10
  72. package/src/client/theme/ui/Loading/index.ts +1 -1
  73. package/src/client/theme/ui/Loading/loading.css +30 -30
  74. package/src/client/theme/ui/Navbar/GithubStars.tsx +27 -27
  75. package/src/client/theme/ui/Navbar/Navbar.tsx +145 -145
  76. package/src/client/theme/ui/Navbar/index.ts +2 -2
  77. package/src/client/theme/ui/Navbar/navbar.css +233 -233
  78. package/src/client/theme/ui/NotFound/NotFound.tsx +19 -20
  79. package/src/client/theme/ui/NotFound/index.ts +1 -1
  80. package/src/client/theme/ui/NotFound/not-found.css +64 -64
  81. package/src/client/theme/ui/OnThisPage/OnThisPage.tsx +235 -192
  82. package/src/client/theme/ui/OnThisPage/index.ts +1 -1
  83. package/src/client/theme/ui/OnThisPage/toc.css +132 -132
  84. package/src/client/theme/ui/PoweredBy/PoweredBy.tsx +18 -18
  85. package/src/client/theme/ui/PoweredBy/index.ts +1 -1
  86. package/src/client/theme/ui/PoweredBy/powered-by.css +76 -76
  87. package/src/client/theme/ui/SearchDialog/SearchDialog.tsx +199 -199
  88. package/src/client/theme/ui/SearchDialog/index.ts +1 -1
  89. package/src/client/theme/ui/SearchDialog/search.css +152 -152
  90. package/src/client/theme/ui/Sidebar/Sidebar.tsx +204 -200
  91. package/src/client/theme/ui/Sidebar/index.ts +1 -1
  92. package/src/client/theme/ui/Sidebar/sidebar.css +236 -269
  93. package/src/client/theme/ui/ThemeToggle/ThemeToggle.tsx +69 -69
  94. package/src/client/theme/ui/ThemeToggle/index.ts +1 -1
  95. package/src/client/theme/ui/VersionSwitcher/VersionSwitcher.tsx +136 -136
  96. package/src/client/theme/ui/VersionSwitcher/index.ts +1 -1
  97. package/src/client/types.ts +50 -50
  98. package/src/client/utils.ts +26 -26
  99. package/src/node/cache.ts +408 -94
  100. package/src/node/config.ts +192 -185
  101. package/src/node/index.ts +21 -21
  102. package/src/node/mdx.ts +120 -41
  103. package/src/node/plugin/entry.ts +58 -58
  104. package/src/node/plugin/html.ts +55 -55
  105. package/src/node/plugin/index.ts +193 -190
  106. package/src/node/plugin/types.ts +11 -11
  107. package/src/node/routes/cache.ts +28 -24
  108. package/src/node/routes/index.ts +167 -152
  109. package/src/node/routes/parser.ts +153 -127
  110. package/src/node/routes/sorter.ts +42 -42
  111. package/src/node/routes/types.ts +49 -49
  112. package/src/node/ssg/index.ts +114 -110
  113. package/src/node/ssg/meta.ts +34 -34
  114. package/src/node/ssg/options.ts +13 -13
  115. package/src/node/ssg/sitemap.ts +54 -54
  116. package/src/node/utils.ts +134 -134
  117. package/tsconfig.json +20 -20
  118. package/tsup.config.ts +22 -22
  119. package/dist/Playground-B2FA34BC.mjs +0 -6
  120. package/dist/chunk-WPT4MWTQ.mjs +0 -89
  121. package/src/client/theme/styles/home.css +0 -60
@@ -5,6 +5,9 @@ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
5
  var __getOwnPropNames = Object.getOwnPropertyNames;
6
6
  var __getProtoOf = Object.getPrototypeOf;
7
7
  var __hasOwnProp = Object.prototype.hasOwnProperty;
8
+ var __esm = (fn, res) => function __init() {
9
+ return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
10
+ };
8
11
  var __export = (target, all) => {
9
12
  for (var name in all)
10
13
  __defProp(target, name, { get: all[name], enumerable: true });
@@ -27,23 +30,7 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
27
30
  ));
28
31
  var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
29
32
 
30
- // src/node/index.ts
31
- var node_exports = {};
32
- __export(node_exports, {
33
- default: () => boltdocs,
34
- generateStaticPages: () => generateStaticPages
35
- });
36
- module.exports = __toCommonJS(node_exports);
37
-
38
- // src/node/plugin/index.ts
39
- var import_vite = require("vite");
40
-
41
- // src/node/routes/index.ts
42
- var import_fast_glob = __toESM(require("fast-glob"));
43
-
44
33
  // src/node/utils.ts
45
- var import_fs = __toESM(require("fs"));
46
- var import_gray_matter = __toESM(require("gray-matter"));
47
34
  function normalizePath(p) {
48
35
  return p.replace(/\\/g, "/");
49
36
  }
@@ -89,82 +76,363 @@ function fileToRoutePath(relativePath) {
89
76
  function capitalize(str) {
90
77
  return str.charAt(0).toUpperCase() + str.slice(1);
91
78
  }
79
+ var import_fs, import_gray_matter;
80
+ var init_utils = __esm({
81
+ "src/node/utils.ts"() {
82
+ "use strict";
83
+ import_fs = __toESM(require("fs"));
84
+ import_gray_matter = __toESM(require("gray-matter"));
85
+ }
86
+ });
92
87
 
93
88
  // src/node/cache.ts
94
- var FileCache = class {
95
- entries = /* @__PURE__ */ new Map();
96
- /**
97
- * Retrieves parsed data for a file from the cache.
98
- * Compares the current filesystem mtime with the cached mtime.
99
- *
100
- * @param filePath - The absolute path of the file
101
- * @returns The cached data if valid, or `null` if the file has changed or doesn't exist
102
- */
103
- get(filePath) {
104
- const entry = this.entries.get(filePath);
105
- if (!entry) return null;
106
- const currentMtime = getFileMtime(filePath);
107
- if (currentMtime !== entry.mtime) return null;
108
- return entry.data;
109
- }
110
- /**
111
- * Stores parsed data for a file in the cache, recording its current mtime.
112
- *
113
- * @param filePath - The absolute path to the file
114
- * @param data - The parsed data to store
115
- */
116
- set(filePath, data) {
117
- this.entries.set(filePath, {
118
- data,
119
- mtime: getFileMtime(filePath)
120
- });
121
- }
122
- /**
123
- * Checks if a specific file's cache is still valid (based on its mtime).
124
- *
125
- * @param filePath - The absolute path to the file
126
- * @returns `true` if the cache is valid, `false` otherwise
127
- */
128
- isValid(filePath) {
129
- return this.get(filePath) !== null;
130
- }
131
- /**
132
- * Manually removes a specific file from the cache.
133
- * Useful when forcefully invalidating a single updated file.
134
- *
135
- * @param filePath - The absolute path to the file
136
- */
137
- invalidate(filePath) {
138
- this.entries.delete(filePath);
139
- }
140
- /**
141
- * Clears the entire cache, forcing all files to be re-parsed on the next request.
142
- * Useful when global dependencies (like config) change.
143
- */
144
- invalidateAll() {
145
- this.entries.clear();
146
- }
147
- /**
148
- * Removes cached entries for files that no longer exist on the filesystem.
149
- * Prevents memory leaks from deleted files.
150
- *
151
- * @param currentFiles - A Set of absolute file paths currently discovered on the disk
152
- */
153
- pruneStale(currentFiles) {
154
- for (const key of this.entries.keys()) {
155
- if (!currentFiles.has(key)) {
156
- this.entries.delete(key);
89
+ var cache_exports = {};
90
+ __export(cache_exports, {
91
+ AssetCache: () => AssetCache,
92
+ FileCache: () => FileCache,
93
+ TransformCache: () => TransformCache,
94
+ flushCache: () => flushCache
95
+ });
96
+ async function flushCache() {
97
+ await backgroundQueue.flush();
98
+ }
99
+ var import_fs2, import_path, import_crypto, import_zlib, import_util, writeFile, readFile, mkdir, rename, unlink, CACHE_DIR, ASSETS_DIR, SHARDS_DIR, DEFAULT_LRU_LIMIT, DEFAULT_COMPRESS, LRUCache, BackgroundQueue, backgroundQueue, FileCache, TransformCache, AssetCache;
100
+ var init_cache = __esm({
101
+ "src/node/cache.ts"() {
102
+ "use strict";
103
+ import_fs2 = __toESM(require("fs"));
104
+ import_path = __toESM(require("path"));
105
+ import_crypto = __toESM(require("crypto"));
106
+ import_zlib = __toESM(require("zlib"));
107
+ import_util = require("util");
108
+ init_utils();
109
+ writeFile = (0, import_util.promisify)(import_fs2.default.writeFile);
110
+ readFile = (0, import_util.promisify)(import_fs2.default.readFile);
111
+ mkdir = (0, import_util.promisify)(import_fs2.default.mkdir);
112
+ rename = (0, import_util.promisify)(import_fs2.default.rename);
113
+ unlink = (0, import_util.promisify)(import_fs2.default.unlink);
114
+ CACHE_DIR = process.env.BOLTDOCS_CACHE_DIR || ".boltdocs";
115
+ ASSETS_DIR = "assets";
116
+ SHARDS_DIR = "shards";
117
+ DEFAULT_LRU_LIMIT = parseInt(
118
+ process.env.BOLTDOCS_CACHE_LRU_LIMIT || "2000",
119
+ 10
120
+ );
121
+ DEFAULT_COMPRESS = process.env.BOLTDOCS_CACHE_COMPRESS !== "0";
122
+ LRUCache = class {
123
+ constructor(limit) {
124
+ this.limit = limit;
157
125
  }
158
- }
159
- }
160
- /** Number of cached entries */
161
- get size() {
162
- return this.entries.size;
126
+ cache = /* @__PURE__ */ new Map();
127
+ get(key) {
128
+ const val = this.cache.get(key);
129
+ if (val !== void 0) {
130
+ this.cache.delete(key);
131
+ this.cache.set(key, val);
132
+ }
133
+ return val;
134
+ }
135
+ set(key, value) {
136
+ if (this.cache.has(key)) {
137
+ this.cache.delete(key);
138
+ } else if (this.cache.size >= this.limit) {
139
+ const firstKey = this.cache.keys().next().value;
140
+ if (firstKey !== void 0) {
141
+ this.cache.delete(firstKey);
142
+ }
143
+ }
144
+ this.cache.set(key, value);
145
+ }
146
+ get size() {
147
+ return this.cache.size;
148
+ }
149
+ clear() {
150
+ this.cache.clear();
151
+ }
152
+ };
153
+ BackgroundQueue = class {
154
+ queue = Promise.resolve();
155
+ pendingCount = 0;
156
+ add(task) {
157
+ this.pendingCount++;
158
+ this.queue = this.queue.then(task).finally(() => {
159
+ this.pendingCount--;
160
+ });
161
+ }
162
+ async flush() {
163
+ await this.queue;
164
+ }
165
+ get pending() {
166
+ return this.pendingCount;
167
+ }
168
+ };
169
+ backgroundQueue = new BackgroundQueue();
170
+ FileCache = class {
171
+ entries = /* @__PURE__ */ new Map();
172
+ cachePath = null;
173
+ compress;
174
+ constructor(options = {}) {
175
+ this.compress = options.compress !== void 0 ? options.compress : DEFAULT_COMPRESS;
176
+ if (options.name) {
177
+ const root = options.root || process.cwd();
178
+ const ext = this.compress ? "json.gz" : "json";
179
+ this.cachePath = import_path.default.resolve(root, CACHE_DIR, `${options.name}.${ext}`);
180
+ }
181
+ }
182
+ /**
183
+ * Loads the cache. Synchronous for startup simplicity but uses fast I/O.
184
+ */
185
+ load() {
186
+ if (process.env.BOLTDOCS_NO_CACHE === "1") return;
187
+ if (!this.cachePath || !import_fs2.default.existsSync(this.cachePath)) return;
188
+ try {
189
+ let raw = import_fs2.default.readFileSync(this.cachePath);
190
+ if (this.cachePath.endsWith(".gz")) {
191
+ raw = import_zlib.default.gunzipSync(raw);
192
+ }
193
+ const data = JSON.parse(raw.toString("utf-8"));
194
+ this.entries = new Map(Object.entries(data));
195
+ } catch (e) {
196
+ }
197
+ }
198
+ /**
199
+ * Saves the cache in the background.
200
+ */
201
+ save() {
202
+ if (process.env.BOLTDOCS_NO_CACHE === "1") return;
203
+ if (!this.cachePath) return;
204
+ const data = Object.fromEntries(this.entries);
205
+ const content = JSON.stringify(data);
206
+ const target = this.cachePath;
207
+ const useCompress = this.compress;
208
+ backgroundQueue.add(async () => {
209
+ try {
210
+ await mkdir(import_path.default.dirname(target), { recursive: true });
211
+ let buffer = Buffer.from(content);
212
+ if (useCompress) {
213
+ buffer = import_zlib.default.gzipSync(buffer);
214
+ }
215
+ const tempPath = `${target}.${import_crypto.default.randomBytes(4).toString("hex")}.tmp`;
216
+ await writeFile(tempPath, buffer);
217
+ await rename(tempPath, target);
218
+ } catch (e) {
219
+ }
220
+ });
221
+ }
222
+ get(filePath) {
223
+ const entry = this.entries.get(filePath);
224
+ if (!entry) return null;
225
+ if (getFileMtime(filePath) !== entry.mtime) return null;
226
+ return entry.data;
227
+ }
228
+ set(filePath, data) {
229
+ this.entries.set(filePath, {
230
+ data,
231
+ mtime: getFileMtime(filePath)
232
+ });
233
+ }
234
+ isValid(filePath) {
235
+ const entry = this.entries.get(filePath);
236
+ if (!entry) return false;
237
+ return getFileMtime(filePath) === entry.mtime;
238
+ }
239
+ invalidate(filePath) {
240
+ this.entries.delete(filePath);
241
+ }
242
+ invalidateAll() {
243
+ this.entries.clear();
244
+ }
245
+ pruneStale(currentFiles) {
246
+ for (const key of this.entries.keys()) {
247
+ if (!currentFiles.has(key)) {
248
+ this.entries.delete(key);
249
+ }
250
+ }
251
+ }
252
+ get size() {
253
+ return this.entries.size;
254
+ }
255
+ async flush() {
256
+ await backgroundQueue.flush();
257
+ }
258
+ };
259
+ TransformCache = class {
260
+ index = /* @__PURE__ */ new Map();
261
+ // key -> hash
262
+ memoryCache = new LRUCache(DEFAULT_LRU_LIMIT);
263
+ baseDir;
264
+ shardsDir;
265
+ indexPath;
266
+ constructor(name, root = process.cwd()) {
267
+ this.baseDir = import_path.default.resolve(root, CACHE_DIR, `transform-${name}`);
268
+ this.shardsDir = import_path.default.resolve(this.baseDir, SHARDS_DIR);
269
+ this.indexPath = import_path.default.resolve(this.baseDir, "index.json");
270
+ }
271
+ /**
272
+ * Loads the index into memory.
273
+ */
274
+ load() {
275
+ if (process.env.BOLTDOCS_NO_CACHE === "1") return;
276
+ if (!import_fs2.default.existsSync(this.indexPath)) return;
277
+ try {
278
+ const data = import_fs2.default.readFileSync(this.indexPath, "utf-8");
279
+ this.index = new Map(Object.entries(JSON.parse(data)));
280
+ } catch (e) {
281
+ }
282
+ }
283
+ /**
284
+ * Persists the index in background.
285
+ */
286
+ save() {
287
+ if (process.env.BOLTDOCS_NO_CACHE === "1") return;
288
+ const data = JSON.stringify(Object.fromEntries(this.index));
289
+ const target = this.indexPath;
290
+ backgroundQueue.add(async () => {
291
+ await mkdir(import_path.default.dirname(target), { recursive: true });
292
+ await writeFile(target, data);
293
+ });
294
+ }
295
+ /**
296
+ * Batch Read: Retrieves multiple transformation results concurrently.
297
+ */
298
+ async getMany(keys) {
299
+ const results = /* @__PURE__ */ new Map();
300
+ const toLoad = [];
301
+ for (const key of keys) {
302
+ const mem = this.memoryCache.get(key);
303
+ if (mem) results.set(key, mem);
304
+ else if (this.index.has(key)) toLoad.push(key);
305
+ }
306
+ if (toLoad.length > 0) {
307
+ const shards = await Promise.all(
308
+ toLoad.map(async (key) => {
309
+ const hash = this.index.get(key);
310
+ const shardPath = import_path.default.resolve(this.shardsDir, `${hash}.gz`);
311
+ try {
312
+ const compressed = await readFile(shardPath);
313
+ const decompressed = import_zlib.default.gunzipSync(compressed).toString("utf-8");
314
+ this.memoryCache.set(key, decompressed);
315
+ return { key, val: decompressed };
316
+ } catch (e) {
317
+ return null;
318
+ }
319
+ })
320
+ );
321
+ for (const s of shards) {
322
+ if (s) results.set(s.key, s.val);
323
+ }
324
+ }
325
+ return results;
326
+ }
327
+ /**
328
+ * Retrieves a cached transformation. Fast lookup via index, lazy loading from disk.
329
+ */
330
+ get(key) {
331
+ const mem = this.memoryCache.get(key);
332
+ if (mem) return mem;
333
+ const hash = this.index.get(key);
334
+ if (!hash) return null;
335
+ const shardPath = import_path.default.resolve(this.shardsDir, `${hash}.gz`);
336
+ if (!import_fs2.default.existsSync(shardPath)) return null;
337
+ try {
338
+ const compressed = import_fs2.default.readFileSync(shardPath);
339
+ const decompressed = import_zlib.default.gunzipSync(compressed).toString("utf-8");
340
+ this.memoryCache.set(key, decompressed);
341
+ return decompressed;
342
+ } catch (e) {
343
+ return null;
344
+ }
345
+ }
346
+ /**
347
+ * Stores a transformation result.
348
+ */
349
+ set(key, result) {
350
+ const hash = import_crypto.default.createHash("md5").update(result).digest("hex");
351
+ this.index.set(key, hash);
352
+ this.memoryCache.set(key, result);
353
+ const shardPath = import_path.default.resolve(this.shardsDir, `${hash}.gz`);
354
+ backgroundQueue.add(async () => {
355
+ if (import_fs2.default.existsSync(shardPath)) return;
356
+ await mkdir(this.shardsDir, { recursive: true });
357
+ const compressed = import_zlib.default.gzipSync(Buffer.from(result));
358
+ const tempPath = `${shardPath}.${import_crypto.default.randomBytes(4).toString("hex")}.tmp`;
359
+ await writeFile(tempPath, compressed);
360
+ await rename(tempPath, shardPath);
361
+ });
362
+ }
363
+ get size() {
364
+ return this.index.size;
365
+ }
366
+ async flush() {
367
+ await backgroundQueue.flush();
368
+ }
369
+ };
370
+ AssetCache = class {
371
+ assetsDir;
372
+ constructor(root = process.cwd()) {
373
+ this.assetsDir = import_path.default.resolve(root, CACHE_DIR, ASSETS_DIR);
374
+ }
375
+ getFileHash(filePath) {
376
+ return import_crypto.default.createHash("md5").update(import_fs2.default.readFileSync(filePath)).digest("hex");
377
+ }
378
+ get(sourcePath, cacheKey) {
379
+ if (!import_fs2.default.existsSync(sourcePath)) return null;
380
+ const sourceHash = this.getFileHash(sourcePath);
381
+ const cachedPath = this.getCachedPath(
382
+ sourcePath,
383
+ `${cacheKey}-${sourceHash}`
384
+ );
385
+ return import_fs2.default.existsSync(cachedPath) ? cachedPath : null;
386
+ }
387
+ set(sourcePath, cacheKey, content) {
388
+ const sourceHash = this.getFileHash(sourcePath);
389
+ const cachedPath = this.getCachedPath(
390
+ sourcePath,
391
+ `${cacheKey}-${sourceHash}`
392
+ );
393
+ backgroundQueue.add(async () => {
394
+ await mkdir(this.assetsDir, { recursive: true });
395
+ const tempPath = `${cachedPath}.${import_crypto.default.randomBytes(4).toString("hex")}.tmp`;
396
+ await writeFile(tempPath, content);
397
+ await rename(tempPath, cachedPath);
398
+ });
399
+ }
400
+ getCachedPath(sourcePath, cacheKey) {
401
+ const ext = import_path.default.extname(sourcePath);
402
+ const name = import_path.default.basename(sourcePath, ext);
403
+ const safeKey = cacheKey.replace(/[^a-z0-9]/gi, "-").toLowerCase();
404
+ return import_path.default.join(this.assetsDir, `${name}.${safeKey}${ext}`);
405
+ }
406
+ clear() {
407
+ if (import_fs2.default.existsSync(this.assetsDir)) {
408
+ import_fs2.default.rmSync(this.assetsDir, { recursive: true, force: true });
409
+ }
410
+ }
411
+ async flush() {
412
+ await backgroundQueue.flush();
413
+ }
414
+ };
163
415
  }
164
- };
416
+ });
417
+
418
+ // src/node/index.ts
419
+ var node_exports = {};
420
+ __export(node_exports, {
421
+ default: () => boltdocs,
422
+ generateStaticPages: () => generateStaticPages
423
+ });
424
+ module.exports = __toCommonJS(node_exports);
425
+
426
+ // src/node/plugin/index.ts
427
+ var import_vite = require("vite");
428
+
429
+ // src/node/routes/index.ts
430
+ var import_fast_glob = __toESM(require("fast-glob"));
431
+ init_utils();
165
432
 
166
433
  // src/node/routes/cache.ts
167
- var docCache = new FileCache();
434
+ init_cache();
435
+ var docCache = new FileCache({ name: "routes" });
168
436
  function invalidateRouteCache() {
169
437
  docCache.invalidateAll();
170
438
  }
@@ -173,11 +441,22 @@ function invalidateFile(filePath) {
173
441
  }
174
442
 
175
443
  // src/node/routes/parser.ts
176
- var import_path = __toESM(require("path"));
444
+ var import_path2 = __toESM(require("path"));
177
445
  var import_github_slugger = __toESM(require("github-slugger"));
446
+ init_utils();
178
447
  function parseDocFile(file, docsDir, basePath, config) {
448
+ const decodedFile = decodeURIComponent(file);
449
+ const absoluteFile = import_path2.default.resolve(decodedFile);
450
+ const absoluteDocsDir = import_path2.default.resolve(docsDir);
451
+ const relativePath = normalizePath(
452
+ import_path2.default.relative(absoluteDocsDir, absoluteFile)
453
+ );
454
+ if (relativePath.startsWith("../") || relativePath === ".." || absoluteFile.includes("\0")) {
455
+ throw new Error(
456
+ `Security breach: File is outside of docs directory or contains null bytes: ${file}`
457
+ );
458
+ }
179
459
  const { data, content } = parseFrontmatter(file);
180
- const relativePath = normalizePath(import_path.default.relative(docsDir, file));
181
460
  let parts = relativePath.split("/");
182
461
  let locale;
183
462
  let version;
@@ -209,7 +488,7 @@ function parseDocFile(file, docsDir, basePath, config) {
209
488
  const rawFileName = parts[parts.length - 1];
210
489
  const cleanFileName = stripNumberPrefix(rawFileName);
211
490
  const inferredTitle = stripNumberPrefix(
212
- import_path.default.basename(file, import_path.default.extname(file))
491
+ import_path2.default.basename(file, import_path2.default.extname(file))
213
492
  );
214
493
  const sidebarPosition = data.sidebarPosition ?? extractNumberPrefix(rawFileName);
215
494
  const rawDirName = parts.length >= 2 ? parts[0] : void 0;
@@ -223,25 +502,30 @@ function parseDocFile(file, docsDir, basePath, config) {
223
502
  const level = match[1].length;
224
503
  const text = match[2].replace(/\[([^\]]+)\]\([^\)]+\)/g, "$1").replace(/[_*`]/g, "").trim();
225
504
  const id = slugger.slug(text);
226
- headings.push({ level, text, id });
505
+ headings.push({ level, text: escapeHtml(text), id });
227
506
  }
507
+ const sanitizedTitle = data.title ? escapeHtml(data.title) : inferredTitle;
508
+ const sanitizedDescription = data.description ? escapeHtml(data.description) : "";
509
+ const sanitizedBadge = data.badge ? escapeHtml(data.badge) : void 0;
228
510
  return {
229
511
  route: {
230
512
  path: finalPath,
231
513
  componentPath: file,
232
514
  filePath: relativePath,
233
- title: data.title || inferredTitle,
234
- description: data.description || "",
515
+ title: sanitizedTitle,
516
+ description: sanitizedDescription,
235
517
  sidebarPosition,
236
518
  headings,
237
519
  locale,
238
520
  version,
239
- badge: data.badge
521
+ badge: sanitizedBadge
240
522
  },
241
523
  relativeDir: cleanDirName,
242
524
  isGroupIndex,
243
525
  groupMeta: isGroupIndex ? {
244
- title: data.groupTitle || data.title || (cleanDirName ? capitalize(cleanDirName) : ""),
526
+ title: escapeHtml(
527
+ data.groupTitle || data.title || (cleanDirName ? capitalize(cleanDirName) : "")
528
+ ),
245
529
  position: data.groupPosition ?? data.sidebarPosition ?? (rawDirName ? extractNumberPrefix(rawDirName) : void 0)
246
530
  } : void 0,
247
531
  inferredGroupPosition: rawDirName ? extractNumberPrefix(rawDirName) : void 0
@@ -277,6 +561,7 @@ function compareByGroupPosition(a, b) {
277
561
 
278
562
  // src/node/routes/index.ts
279
563
  async function generateRoutes(docsDir, config, basePath = "/docs") {
564
+ docCache.load();
280
565
  const files = await (0, import_fast_glob.default)(["**/*.md", "**/*.mdx"], {
281
566
  cwd: docsDir,
282
567
  absolute: true
@@ -285,15 +570,25 @@ async function generateRoutes(docsDir, config, basePath = "/docs") {
285
570
  if (config?.i18n) {
286
571
  docCache.invalidateAll();
287
572
  }
573
+ let cacheHits = 0;
288
574
  const parsed = await Promise.all(
289
575
  files.map(async (file) => {
290
576
  const cached = docCache.get(file);
291
- if (cached) return cached;
577
+ if (cached) {
578
+ cacheHits++;
579
+ return cached;
580
+ }
292
581
  const result = parseDocFile(file, docsDir, basePath, config);
293
582
  docCache.set(file, result);
294
583
  return result;
295
584
  })
296
585
  );
586
+ if (files.length > 0) {
587
+ console.log(
588
+ `[boltdocs] Routes generated: ${files.length} files (${cacheHits} from cache, ${files.length - cacheHits} parsed)`
589
+ );
590
+ }
591
+ docCache.save();
297
592
  const groupMeta = /* @__PURE__ */ new Map();
298
593
  for (const p of parsed) {
299
594
  if (p.relativeDir) {
@@ -371,9 +666,9 @@ async function generateRoutes(docsDir, config, basePath = "/docs") {
371
666
  var import_vite_plugin_image_optimizer = require("vite-plugin-image-optimizer");
372
667
 
373
668
  // src/node/config.ts
374
- var import_path2 = __toESM(require("path"));
669
+ var import_path3 = __toESM(require("path"));
375
670
  var import_url = require("url");
376
- var import_fs2 = __toESM(require("fs"));
671
+ var import_fs3 = __toESM(require("fs"));
377
672
  var CONFIG_FILES = [
378
673
  "boltdocs.config.js",
379
674
  "boltdocs.config.mjs",
@@ -382,7 +677,7 @@ var CONFIG_FILES = [
382
677
  async function resolveConfig(docsDir) {
383
678
  const projectRoot = process.cwd();
384
679
  const defaults = {
385
- docsDir: import_path2.default.resolve(docsDir),
680
+ docsDir: import_path3.default.resolve(docsDir),
386
681
  themeConfig: {
387
682
  title: "Boltdocs",
388
683
  description: "A Vite documentation framework",
@@ -393,15 +688,15 @@ async function resolveConfig(docsDir) {
393
688
  }
394
689
  };
395
690
  for (const filename of CONFIG_FILES) {
396
- const configPath = import_path2.default.resolve(projectRoot, filename);
397
- if (import_fs2.default.existsSync(configPath)) {
691
+ const configPath = import_path3.default.resolve(projectRoot, filename);
692
+ if (import_fs3.default.existsSync(configPath)) {
398
693
  try {
399
694
  const fileUrl = (0, import_url.pathToFileURL)(configPath).href + "?t=" + Date.now();
400
695
  const mod = await import(fileUrl);
401
696
  const userConfig = mod.default || mod;
402
697
  const userThemeConfig = userConfig.themeConfig || userConfig;
403
698
  return {
404
- docsDir: import_path2.default.resolve(docsDir),
699
+ docsDir: import_path3.default.resolve(docsDir),
405
700
  themeConfig: {
406
701
  ...defaults.themeConfig,
407
702
  ...userThemeConfig
@@ -420,8 +715,9 @@ async function resolveConfig(docsDir) {
420
715
  }
421
716
 
422
717
  // src/node/ssg/index.ts
423
- var import_fs3 = __toESM(require("fs"));
424
- var import_path3 = __toESM(require("path"));
718
+ var import_fs4 = __toESM(require("fs"));
719
+ var import_path4 = __toESM(require("path"));
720
+ init_utils();
425
721
  var import_url2 = require("url");
426
722
  var import_module = require("module");
427
723
 
@@ -486,15 +782,15 @@ ${entries.map(
486
782
  // src/node/ssg/index.ts
487
783
  var import_meta2 = {};
488
784
  var _filename = (0, import_url2.fileURLToPath)(import_meta2.url);
489
- var _dirname = import_path3.default.dirname(_filename);
785
+ var _dirname = import_path4.default.dirname(_filename);
490
786
  var _require = (0, import_module.createRequire)(import_meta2.url);
491
787
  async function generateStaticPages(options) {
492
788
  const { docsDir, outDir, config } = options;
493
789
  const routes = await generateRoutes(docsDir, config);
494
790
  const siteTitle = config?.themeConfig?.title || "Boltdocs";
495
791
  const siteDescription = config?.themeConfig?.description || "";
496
- const ssrModulePath = import_path3.default.resolve(_dirname, "../client/ssr.js");
497
- if (!import_fs3.default.existsSync(ssrModulePath)) {
792
+ const ssrModulePath = import_path4.default.resolve(_dirname, "../client/ssr.js");
793
+ if (!import_fs4.default.existsSync(ssrModulePath)) {
498
794
  console.error(
499
795
  "[boltdocs] SSR module not found at",
500
796
  ssrModulePath,
@@ -503,12 +799,12 @@ async function generateStaticPages(options) {
503
799
  return;
504
800
  }
505
801
  const { render } = _require(ssrModulePath);
506
- const templatePath = import_path3.default.join(outDir, "index.html");
507
- if (!import_fs3.default.existsSync(templatePath)) {
802
+ const templatePath = import_path4.default.join(outDir, "index.html");
803
+ if (!import_fs4.default.existsSync(templatePath)) {
508
804
  console.warn("[boltdocs] No index.html found in outDir, skipping SSG.");
509
805
  return;
510
806
  }
511
- const template = import_fs3.default.readFileSync(templatePath, "utf-8");
807
+ const template = import_fs4.default.readFileSync(templatePath, "utf-8");
512
808
  let homePageComp;
513
809
  if (config?._homePagePath) {
514
810
  try {
@@ -534,10 +830,10 @@ async function generateStaticPages(options) {
534
830
  title: escapeHtml(pageTitle),
535
831
  description: escapeHtml(pageDescription)
536
832
  }).replace("<!--app-html-->", appHtml).replace(`<div id="root"></div>`, `<div id="root">${appHtml}</div>`);
537
- const routeDir = import_path3.default.join(outDir, route.path);
538
- await import_fs3.default.promises.mkdir(routeDir, { recursive: true });
539
- await import_fs3.default.promises.writeFile(
540
- import_path3.default.join(routeDir, "index.html"),
833
+ const routeDir = import_path4.default.join(outDir, route.path);
834
+ await import_fs4.default.promises.mkdir(routeDir, { recursive: true });
835
+ await import_fs4.default.promises.writeFile(
836
+ import_path4.default.join(routeDir, "index.html"),
541
837
  html,
542
838
  "utf-8"
543
839
  );
@@ -550,16 +846,20 @@ async function generateStaticPages(options) {
550
846
  routes.map((r) => r.path),
551
847
  config
552
848
  );
553
- import_fs3.default.writeFileSync(import_path3.default.join(outDir, "sitemap.xml"), sitemap, "utf-8");
849
+ import_fs4.default.writeFileSync(import_path4.default.join(outDir, "sitemap.xml"), sitemap, "utf-8");
554
850
  console.log(
555
851
  `[boltdocs] Generated ${routes.length} static pages + sitemap.xml`
556
852
  );
853
+ const { flushCache: flushCache2 } = await Promise.resolve().then(() => (init_cache(), cache_exports));
854
+ await flushCache2();
557
855
  }
558
856
 
559
857
  // src/node/plugin/index.ts
560
- var import_path4 = __toESM(require("path"));
858
+ init_utils();
859
+ var import_path5 = __toESM(require("path"));
561
860
 
562
861
  // src/node/plugin/entry.ts
862
+ init_utils();
563
863
  function generateEntryCode(options, config) {
564
864
  const homeImport = options.homePage ? `import HomePage from '${normalizePath(options.homePage)}';` : "";
565
865
  const homeOption = options.homePage ? "homePage: HomePage," : "";
@@ -568,8 +868,8 @@ function generateEntryCode(options, config) {
568
868
  const componentImports = pluginComponents.map(
569
869
  ([
570
870
  name,
571
- path5
572
- ]) => `import * as _comp_${name} from '${normalizePath(path5)}';
871
+ path6
872
+ ]) => `import * as _comp_${name} from '${normalizePath(path6)}';
573
873
  const ${name} = _comp_${name}.default || _comp_${name}['${name}'] || _comp_${name};`
574
874
  ).join("\n");
575
875
  const componentMap = pluginComponents.map(([name]) => name).join(", ");
@@ -639,7 +939,7 @@ ${themeScript} </head>`);
639
939
 
640
940
  // src/node/plugin/index.ts
641
941
  function boltdocsPlugin(options = {}, passedConfig) {
642
- const docsDir = import_path4.default.resolve(process.cwd(), options.docsDir || "docs");
942
+ const docsDir = import_path5.default.resolve(process.cwd(), options.docsDir || "docs");
643
943
  const normalizedDocsDir = normalizePath(docsDir);
644
944
  let config = passedConfig;
645
945
  let viteConfig;
@@ -669,7 +969,7 @@ function boltdocsPlugin(options = {}, passedConfig) {
669
969
  },
670
970
  configureServer(server) {
671
971
  const configPaths = CONFIG_FILES.map(
672
- (c) => import_path4.default.resolve(process.cwd(), c)
972
+ (c) => import_path5.default.resolve(process.cwd(), c)
673
973
  );
674
974
  server.watcher.add(configPaths);
675
975
  const handleFileEvent = async (file, type) => {
@@ -732,8 +1032,10 @@ function boltdocsPlugin(options = {}, passedConfig) {
732
1032
  },
733
1033
  async closeBundle() {
734
1034
  if (!isBuild) return;
735
- const outDir = viteConfig?.build?.outDir ? import_path4.default.resolve(viteConfig.root, viteConfig.build.outDir) : import_path4.default.resolve(process.cwd(), "dist");
1035
+ const outDir = viteConfig?.build?.outDir ? import_path5.default.resolve(viteConfig.root, viteConfig.build.outDir) : import_path5.default.resolve(process.cwd(), "dist");
736
1036
  await generateStaticPages({ docsDir, outDir, config });
1037
+ const { flushCache: flushCache2 } = await Promise.resolve().then(() => (init_cache(), cache_exports));
1038
+ await flushCache2();
737
1039
  }
738
1040
  },
739
1041
  (0, import_vite_plugin_image_optimizer.ViteImageOptimizer)({
@@ -763,10 +1065,14 @@ var import_remark_gfm = __toESM(require("remark-gfm"));
763
1065
  var import_remark_frontmatter = __toESM(require("remark-frontmatter"));
764
1066
  var import_rehype_slug = __toESM(require("rehype-slug"));
765
1067
  var import_rehype_pretty_code = __toESM(require("rehype-pretty-code"));
1068
+ var import_crypto2 = __toESM(require("crypto"));
1069
+ init_cache();
1070
+ var mdxCache = new TransformCache("mdx");
1071
+ var mdxCacheLoaded = false;
766
1072
  function boltdocsMdxPlugin(config) {
767
1073
  const extraRemarkPlugins = config?.plugins?.flatMap((p) => p.remarkPlugins || []) || [];
768
1074
  const extraRehypePlugins = config?.plugins?.flatMap((p) => p.rehypePlugins || []) || [];
769
- return (0, import_rollup.default)({
1075
+ const baseMdxPlugin = (0, import_rollup.default)({
770
1076
  remarkPlugins: [import_remark_gfm.default, import_remark_frontmatter.default, ...extraRemarkPlugins],
771
1077
  rehypePlugins: [
772
1078
  import_rehype_slug.default,
@@ -774,16 +1080,64 @@ function boltdocsMdxPlugin(config) {
774
1080
  [
775
1081
  import_rehype_pretty_code.default,
776
1082
  {
777
- theme: "one-dark-pro",
1083
+ theme: config?.themeConfig?.codeTheme || "one-dark-pro",
778
1084
  keepBackground: false
779
1085
  }
780
1086
  ]
781
1087
  ],
782
- // Provide React as default for JSX
783
1088
  jsxRuntime: "automatic",
784
1089
  providerImportSource: "@mdx-js/react"
785
1090
  });
1091
+ return {
1092
+ ...baseMdxPlugin,
1093
+ name: "vite-plugin-boltdocs-mdx",
1094
+ async buildStart() {
1095
+ hits = 0;
1096
+ total = 0;
1097
+ if (!mdxCacheLoaded) {
1098
+ mdxCache.load();
1099
+ mdxCacheLoaded = true;
1100
+ }
1101
+ if (baseMdxPlugin.buildStart) {
1102
+ await baseMdxPlugin.buildStart.call(this);
1103
+ }
1104
+ },
1105
+ async transform(code, id, options) {
1106
+ if (!id.endsWith(".md") && !id.endsWith(".mdx")) {
1107
+ return baseMdxPlugin.transform?.call(this, code, id, options);
1108
+ }
1109
+ total++;
1110
+ const contentHash = import_crypto2.default.createHash("md5").update(code).digest("hex");
1111
+ const cacheKey = `${id}:${contentHash}`;
1112
+ const cached = mdxCache.get(cacheKey);
1113
+ if (cached) {
1114
+ hits++;
1115
+ return { code: cached, map: null };
1116
+ }
1117
+ const result = await baseMdxPlugin.transform.call(
1118
+ this,
1119
+ code,
1120
+ id,
1121
+ options
1122
+ );
1123
+ if (result && typeof result === "object" && result.code) {
1124
+ mdxCache.set(cacheKey, result.code);
1125
+ } else if (typeof result === "string") {
1126
+ mdxCache.set(cacheKey, result);
1127
+ }
1128
+ return result;
1129
+ },
1130
+ async buildEnd() {
1131
+ mdxCache.save();
1132
+ await mdxCache.flush();
1133
+ if (baseMdxPlugin.buildEnd) {
1134
+ await baseMdxPlugin.buildEnd.call(this);
1135
+ }
1136
+ }
1137
+ };
786
1138
  }
1139
+ var hits = 0;
1140
+ var total = 0;
787
1141
 
788
1142
  // src/node/index.ts
789
1143
  async function boltdocs(options) {