sourcey 3.6.0 → 3.6.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (86) hide show
  1. package/README.md +5 -2
  2. package/dist/adapters/index.d.ts +3 -2
  3. package/dist/adapters/index.d.ts.map +1 -1
  4. package/dist/adapters/index.js +35 -3
  5. package/dist/adapters/types.d.ts +8 -2
  6. package/dist/adapters/types.d.ts.map +1 -1
  7. package/dist/cli.js +28 -34
  8. package/dist/client/search.js +181 -119
  9. package/dist/client/tabs.js +23 -0
  10. package/dist/components/layout/Head.d.ts.map +1 -1
  11. package/dist/components/layout/Head.js +7 -6
  12. package/dist/components/openapi/Introduction.d.ts.map +1 -1
  13. package/dist/components/openapi/Introduction.js +16 -1
  14. package/dist/components/openapi/Security.d.ts.map +1 -1
  15. package/dist/components/openapi/Security.js +18 -2
  16. package/dist/config.d.ts +70 -0
  17. package/dist/config.d.ts.map +1 -1
  18. package/dist/config.js +23 -7
  19. package/dist/core/api-rendering.d.ts +47 -0
  20. package/dist/core/api-rendering.d.ts.map +1 -0
  21. package/dist/core/api-rendering.js +76 -0
  22. package/dist/core/doxygen-loader.d.ts +1 -1
  23. package/dist/core/doxygen-loader.d.ts.map +1 -1
  24. package/dist/core/doxygen-loader.js +37 -19
  25. package/dist/core/godoc-loader.d.ts.map +1 -1
  26. package/dist/core/godoc-loader.js +17 -6
  27. package/dist/core/markdown-loader.d.ts.map +1 -1
  28. package/dist/core/markdown-loader.js +8 -5
  29. package/dist/core/mcp-normalizer.d.ts.map +1 -1
  30. package/dist/core/mcp-normalizer.js +48 -34
  31. package/dist/core/parser.d.ts.map +1 -1
  32. package/dist/core/parser.js +75 -4
  33. package/dist/core/rustdoc-introspector.d.ts +29 -0
  34. package/dist/core/rustdoc-introspector.d.ts.map +1 -0
  35. package/dist/core/rustdoc-introspector.js +236 -0
  36. package/dist/core/rustdoc-loader.d.ts +25 -0
  37. package/dist/core/rustdoc-loader.d.ts.map +1 -0
  38. package/dist/core/rustdoc-loader.js +472 -0
  39. package/dist/core/rustdoc-render.d.ts +82 -0
  40. package/dist/core/rustdoc-render.d.ts.map +1 -0
  41. package/dist/core/rustdoc-render.js +808 -0
  42. package/dist/core/rustdoc-types.d.ts +314 -0
  43. package/dist/core/rustdoc-types.d.ts.map +1 -0
  44. package/dist/core/rustdoc-types.js +18 -0
  45. package/dist/core/search-indexer.js +1 -1
  46. package/dist/core/sourcey-rustdoc/Cargo.lock +705 -0
  47. package/dist/core/sourcey-rustdoc/Cargo.toml +38 -0
  48. package/dist/core/sourcey-rustdoc/README.md +59 -0
  49. package/dist/core/sourcey-rustdoc/src/diagnostics.rs +42 -0
  50. package/dist/core/sourcey-rustdoc/src/doctest.rs +268 -0
  51. package/dist/core/sourcey-rustdoc/src/extract.rs +492 -0
  52. package/dist/core/sourcey-rustdoc/src/lib.rs +58 -0
  53. package/dist/core/sourcey-rustdoc/src/links.rs +126 -0
  54. package/dist/core/sourcey-rustdoc/src/main.rs +256 -0
  55. package/dist/core/sourcey-rustdoc/src/signature.rs +415 -0
  56. package/dist/core/sourcey-rustdoc/src/spec.rs +390 -0
  57. package/dist/core/sourcey-rustdoc/tests/format_version.rs +45 -0
  58. package/dist/dev-server.d.ts.map +1 -1
  59. package/dist/dev-server.js +50 -1
  60. package/dist/index.d.ts +4 -2
  61. package/dist/index.d.ts.map +1 -1
  62. package/dist/index.js +22 -13
  63. package/dist/init.js +2 -2
  64. package/dist/renderer/changelog-feed.d.ts.map +1 -1
  65. package/dist/renderer/changelog-feed.js +27 -19
  66. package/dist/renderer/context.d.ts +1 -0
  67. package/dist/renderer/context.d.ts.map +1 -1
  68. package/dist/renderer/html-builder.d.ts.map +1 -1
  69. package/dist/renderer/html-builder.js +4 -3
  70. package/dist/renderer/llms.js +57 -2
  71. package/dist/site-assembly.d.ts +5 -0
  72. package/dist/site-assembly.d.ts.map +1 -1
  73. package/dist/site-assembly.js +57 -2
  74. package/dist/themes/default/sourcey.css +300 -0
  75. package/dist/utils/code-samples.d.ts +1 -0
  76. package/dist/utils/code-samples.d.ts.map +1 -1
  77. package/dist/utils/code-samples.js +30 -22
  78. package/dist/utils/html.d.ts +10 -0
  79. package/dist/utils/html.d.ts.map +1 -0
  80. package/dist/utils/html.js +152 -0
  81. package/dist/utils/http.d.ts +0 -2
  82. package/dist/utils/http.d.ts.map +1 -1
  83. package/dist/utils/http.js +19 -34
  84. package/dist/utils/markdown.d.ts.map +1 -1
  85. package/dist/utils/markdown.js +102 -37
  86. package/package.json +10 -5
@@ -54,43 +54,28 @@ export function httpStatusText(code) {
54
54
  * Fixes legacy bug where maximumExclusive was checked with minimumExclusive.
55
55
  */
56
56
  export function schemaRange(schema) {
57
- const parts = [];
57
+ let min;
58
+ let max;
58
59
  if (schema.minimum !== undefined) {
59
- const exclusive = schema.exclusiveMinimum === true ||
60
- typeof schema.exclusiveMinimum === "number";
61
- const min = typeof schema.exclusiveMinimum === "number"
62
- ? schema.exclusiveMinimum
63
- : schema.minimum;
64
- parts.push(`${exclusive ? "(" : "["}${min}`);
60
+ const exclusive = schema.exclusiveMinimum === true || typeof schema.exclusiveMinimum === "number";
61
+ min = {
62
+ value: typeof schema.exclusiveMinimum === "number" ? schema.exclusiveMinimum : schema.minimum,
63
+ exclusive,
64
+ };
65
65
  }
66
66
  if (schema.maximum !== undefined) {
67
- const exclusive = schema.exclusiveMaximum === true ||
68
- typeof schema.exclusiveMaximum === "number";
69
- const max = typeof schema.exclusiveMaximum === "number"
70
- ? schema.exclusiveMaximum
71
- : schema.maximum;
72
- parts.push(`${max}${exclusive ? ")" : "]"}`);
67
+ const exclusive = schema.exclusiveMaximum === true || typeof schema.exclusiveMaximum === "number";
68
+ max = {
69
+ value: typeof schema.exclusiveMaximum === "number" ? schema.exclusiveMaximum : schema.maximum,
70
+ exclusive,
71
+ };
73
72
  }
74
- return parts.length === 2 ? parts.join(", ") : undefined;
75
- }
76
- /** HTTP method color classes for badges */
77
- export function methodColor(method) {
78
- switch (method.toLowerCase()) {
79
- case "get":
80
- return "bg-emerald-600";
81
- case "post":
82
- return "bg-blue-600";
83
- case "put":
84
- return "bg-amber-600";
85
- case "delete":
86
- return "bg-red-600";
87
- case "patch":
88
- return "bg-purple-600";
89
- case "options":
90
- return "bg-gray-500";
91
- case "head":
92
- return "bg-gray-600";
93
- default:
94
- return "bg-gray-500";
73
+ if (min && max) {
74
+ return `${min.exclusive ? "(" : "["}${min.value}, ${max.value}${max.exclusive ? ")" : "]"}`;
95
75
  }
76
+ if (min)
77
+ return `${min.exclusive ? ">" : ">="} ${min.value}`;
78
+ if (max)
79
+ return `${max.exclusive ? "<" : "<="} ${max.value}`;
80
+ return undefined;
96
81
  }
@@ -1 +1 @@
1
- {"version":3,"file":"markdown.d.ts","sourceRoot":"","sources":["../../src/utils/markdown.ts"],"names":[],"mappings":"AAAA,OAAO,EAAU,KAAK,KAAK,EAAe,MAAM,QAAQ,CAAC;AAOzD;;;GAGG;AACH,wBAAgB,eAAe,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,MAAM,GAAG,MAAM,CASnE;AAED,4CAA4C;AAC5C,MAAM,WAAW,WAAW;IAC1B,KAAK,EAAE,CAAC,GAAG,CAAC,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,EAAE,EAAE,MAAM,CAAC;CACZ;AA2JD;;;GAGG;AACH,wBAAgB,WAAW,CAAC,KAAK,CAAC,EAAE,MAAM,GAAG,KAAK,EAAE,CAGnD;AAED;;;GAGG;AACH,wBAAgB,eAAe,CAAC,KAAK,EAAE,MAAM,GAAG,WAAW,EAAE,CAe5D;AAED;;;GAGG;AACH,wBAAgB,qBAAqB,CAAC,KAAK,CAAC,EAAE,MAAM,GAAG,MAAM,CAY5D;AAED;;;GAGG;AACH,wBAAgB,kBAAkB,CAAC,KAAK,CAAC,EAAE,MAAM,GAAG,MAAM,CAGzD;AAED;;GAEG;AACH,wBAAgB,cAAc,CAAC,KAAK,CAAC,EAAE,MAAM,GAAG,MAAM,CAGrD;AAED;;GAEG;AACH,wBAAgB,oBAAoB,CAAC,KAAK,CAAC,EAAE,MAAM,GAAG,MAAM,CAG3D"}
1
+ {"version":3,"file":"markdown.d.ts","sourceRoot":"","sources":["../../src/utils/markdown.ts"],"names":[],"mappings":"AAAA,OAAO,EAAU,KAAK,KAAK,EAAe,MAAM,QAAQ,CAAC;AAMzD;;;GAGG;AACH,wBAAgB,eAAe,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,MAAM,GAAG,MAAM,CASnE;AAED,4CAA4C;AAC5C,MAAM,WAAW,WAAW;IAC1B,KAAK,EAAE,CAAC,GAAG,CAAC,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,EAAE,EAAE,MAAM,CAAC;CACZ;AAkLD;;;GAGG;AACH,wBAAgB,WAAW,CAAC,KAAK,CAAC,EAAE,MAAM,GAAG,KAAK,EAAE,CAGnD;AAED;;;GAGG;AACH,wBAAgB,eAAe,CAAC,KAAK,EAAE,MAAM,GAAG,WAAW,EAAE,CAe5D;AAED;;;GAGG;AACH,wBAAgB,qBAAqB,CAAC,KAAK,CAAC,EAAE,MAAM,GAAG,MAAM,CAY5D;AAED;;;GAGG;AACH,wBAAgB,kBAAkB,CAAC,KAAK,CAAC,EAAE,MAAM,GAAG,MAAM,CA2BzD;AAsBD;;GAEG;AACH,wBAAgB,cAAc,CAAC,KAAK,CAAC,EAAE,MAAM,GAAG,MAAM,CAGrD;AAED;;GAEG;AACH,wBAAgB,oBAAoB,CAAC,KAAK,CAAC,EAAE,MAAM,GAAG,MAAM,CAG3D"}
@@ -2,7 +2,7 @@ import { Marked } from "marked";
2
2
  import { highlightCode } from "./highlighter.js";
3
3
  import { COPY_ICON_SVG } from "./copy-svg.js";
4
4
  import { htmlId } from "./html-id.js";
5
- const MARKDOWN_LINK_RE = /\[((?:`[^`]*`|[^\]])+)\]\(([^)]+)\)/g;
5
+ import { escapeAttr, safeUrl, sanitizeRenderedHtml } from "./html.js";
6
6
  /**
7
7
  * Shared code block renderer used by all markdown rendering.
8
8
  * Outputs the prose-code-block wrapper with copy button and Shiki highlighting.
@@ -24,33 +24,46 @@ function parseDirectiveAttrs(raw) {
24
24
  attrs[k] = v;
25
25
  return attrs;
26
26
  }
27
- function escAttr(s) {
28
- return s.replace(/&/g, "&amp;").replace(/"/g, "&quot;").replace(/</g, "&lt;");
29
- }
30
- function requireHttps(url) {
31
- try {
32
- const u = new URL(url);
33
- if (u.protocol !== "https:" && u.protocol !== "http:")
34
- return null;
35
- return u.href;
27
+ function parseVideoUrl(url) {
28
+ const webUrl = safeUrl(url, {
29
+ allowedProtocols: ["http:", "https:"],
30
+ allowRelative: false,
31
+ allowAnchor: false,
32
+ });
33
+ if (webUrl) {
34
+ const parsed = new URL(webUrl);
35
+ const hostname = parsed.hostname.replace(/^www\./, "");
36
+ if (hostname === "youtube.com" || hostname === "m.youtube.com") {
37
+ const id = parsed.searchParams.get("v");
38
+ if (id && /^[\w-]+$/.test(id)) {
39
+ return { src: `https://www.youtube-nocookie.com/embed/${id}`, type: "iframe" };
40
+ }
41
+ }
42
+ if (hostname === "youtu.be") {
43
+ const id = parsed.pathname.split("/").filter(Boolean)[0];
44
+ if (id && /^[\w-]+$/.test(id)) {
45
+ return { src: `https://www.youtube-nocookie.com/embed/${id}`, type: "iframe" };
46
+ }
47
+ }
48
+ if (hostname === "vimeo.com" || hostname.endsWith(".vimeo.com")) {
49
+ const id = parsed.pathname
50
+ .split("/")
51
+ .filter(Boolean)
52
+ .find((segment) => /^\d+$/.test(segment));
53
+ if (id)
54
+ return { src: `https://player.vimeo.com/video/${id}`, type: "iframe" };
55
+ }
36
56
  }
37
- catch {
57
+ const rawVideoUrl = safeUrl(url, {
58
+ allowedProtocols: ["http:", "https:"],
59
+ allowRelative: true,
60
+ allowAnchor: false,
61
+ });
62
+ if (!rawVideoUrl)
38
63
  return null;
39
- }
40
- }
41
- function parseVideoUrl(url) {
42
- // YouTube
43
- let m = url.match(/(?:youtube\.com\/watch\?v=|youtu\.be\/)([\w-]+)/);
44
- if (m)
45
- return { src: `https://www.youtube-nocookie.com/embed/${m[1]}`, type: "iframe" };
46
- // Vimeo
47
- m = url.match(/vimeo\.com\/(\d+)/);
48
- if (m)
49
- return { src: `https://player.vimeo.com/video/${m[1]}`, type: "iframe" };
50
- // Raw video
51
- const ext = url.split(".").pop()?.toLowerCase();
64
+ const ext = rawVideoUrl.split(/[?#]/)[0]?.split(".").pop()?.toLowerCase();
52
65
  const mime = ext === "webm" ? "video/webm" : "video/mp4";
53
- return { src: url, type: "video", mime };
66
+ return { src: rawVideoUrl, type: "video", mime };
54
67
  }
55
68
  const videoExtension = {
56
69
  extensions: [
@@ -70,15 +83,17 @@ const videoExtension = {
70
83
  renderer(token) {
71
84
  const { url, title } = token;
72
85
  const parsed = parseVideoUrl(url);
73
- const safeTitle = escAttr(title);
86
+ if (!parsed)
87
+ return `<p>[video: invalid URL]</p>\n`;
88
+ const safeTitle = escapeAttr(title);
74
89
  if (parsed.type === "iframe") {
75
90
  return `<div class="prose-video not-prose">
76
- <iframe src="${parsed.src}" title="${safeTitle}" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen loading="lazy"></iframe>
91
+ <iframe src="${escapeAttr(parsed.src)}" title="${safeTitle}" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen loading="lazy"></iframe>
77
92
  </div>\n`;
78
93
  }
79
94
  return `<div class="prose-video not-prose">
80
95
  <video controls preload="metadata" title="${safeTitle}">
81
- <source src="${parsed.src}" type="${parsed.mime}" />
96
+ <source src="${escapeAttr(parsed.src)}" type="${escapeAttr(parsed.mime ?? "video/mp4")}" />
82
97
  </video>
83
98
  </div>\n`;
84
99
  },
@@ -109,11 +124,15 @@ const iframeExtension = {
109
124
  },
110
125
  renderer(token) {
111
126
  const { url, title, height } = token;
112
- const safeUrl = requireHttps(url);
113
- if (!safeUrl)
127
+ const iframeUrl = safeUrl(url, {
128
+ allowedProtocols: ["http:", "https:"],
129
+ allowRelative: false,
130
+ allowAnchor: false,
131
+ });
132
+ if (!iframeUrl)
114
133
  return `<p>[iframe: invalid URL]</p>\n`;
115
134
  return `<div class="prose-iframe not-prose" style="height:${height}px">
116
- <iframe src="${escAttr(safeUrl)}" title="${escAttr(title)}" frameborder="0" loading="lazy" allowfullscreen></iframe>
135
+ <iframe src="${escapeAttr(iframeUrl)}" title="${escapeAttr(title)}" frameborder="0" loading="lazy" allowfullscreen></iframe>
117
136
  </div>\n`;
118
137
  },
119
138
  },
@@ -141,10 +160,13 @@ const marked = new Marked(videoExtension, iframeExtension, {
141
160
  },
142
161
  link({ href, title, tokens }) {
143
162
  const text = this.parser.parseInline(tokens);
144
- const titleAttr = title ? ` title="${title}"` : "";
145
- const isExternal = href.startsWith("http://") || href.startsWith("https://");
163
+ const linkUrl = safeUrl(href);
164
+ if (!linkUrl)
165
+ return text;
166
+ const titleAttr = title ? ` title="${escapeAttr(title)}"` : "";
167
+ const isExternal = linkUrl.startsWith("http://") || linkUrl.startsWith("https://");
146
168
  const target = isExternal ? ' target="_blank" rel="noopener noreferrer"' : "";
147
- return `<a href="${href}"${titleAttr}${target}>${text}</a>`;
169
+ return `<a href="${escapeAttr(linkUrl)}"${titleAttr}${target}>${text}</a>`;
148
170
  },
149
171
  },
150
172
  });
@@ -204,7 +226,50 @@ export function extractFirstParagraph(input) {
204
226
  export function stripMarkdownLinks(input) {
205
227
  if (!input)
206
228
  return "";
207
- return input.replace(MARKDOWN_LINK_RE, "$1");
229
+ let output = "";
230
+ let index = 0;
231
+ while (index < input.length) {
232
+ const isImage = input[index] === "!" && input[index + 1] === "[";
233
+ const isLink = input[index] === "[";
234
+ const labelStart = isImage ? index + 2 : isLink ? index + 1 : -1;
235
+ if (labelStart !== -1) {
236
+ const labelEnd = findMarkdownLabelEnd(input, labelStart);
237
+ if (labelEnd !== -1 && input[labelEnd + 1] === "(") {
238
+ const hrefEnd = findMarkdownHrefEnd(input, labelEnd + 2);
239
+ if (hrefEnd !== -1) {
240
+ output += input.slice(labelStart, labelEnd);
241
+ index = hrefEnd + 1;
242
+ continue;
243
+ }
244
+ }
245
+ }
246
+ output += input[index];
247
+ index += 1;
248
+ }
249
+ return output;
250
+ }
251
+ function findMarkdownLabelEnd(input, start) {
252
+ let inCode = false;
253
+ for (let i = start; i < input.length; i += 1) {
254
+ const char = input[i];
255
+ if (char === "`")
256
+ inCode = !inCode;
257
+ if (char === "\n")
258
+ return -1;
259
+ if (char === "]" && !inCode)
260
+ return i;
261
+ }
262
+ return -1;
263
+ }
264
+ function findMarkdownHrefEnd(input, start) {
265
+ for (let i = start; i < input.length; i += 1) {
266
+ const char = input[i];
267
+ if (char === "\n")
268
+ return -1;
269
+ if (char === ")")
270
+ return i;
271
+ }
272
+ return -1;
208
273
  }
209
274
  /**
210
275
  * Render Markdown to HTML.
@@ -212,7 +277,7 @@ export function stripMarkdownLinks(input) {
212
277
  export function renderMarkdown(input) {
213
278
  if (!input)
214
279
  return "";
215
- return marked.parse(input, { async: false });
280
+ return sanitizeRenderedHtml(marked.parse(input, { async: false }));
216
281
  }
217
282
  /**
218
283
  * Render Markdown to inline HTML (strip wrapping <p> tags).
@@ -220,5 +285,5 @@ export function renderMarkdown(input) {
220
285
  export function renderMarkdownInline(input) {
221
286
  if (!input)
222
287
  return "";
223
- return marked.parseInline(input, { async: false });
288
+ return sanitizeRenderedHtml(marked.parseInline(input, { async: false }));
224
289
  }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "sourcey",
3
- "version": "3.6.0",
4
- "description": "Precision documentation from OpenAPI, MCP, Doxygen, godoc, and Markdown. Static HTML you own.",
3
+ "version": "3.6.2",
4
+ "description": "Precision documentation from OpenAPI, MCP, Doxygen, godoc, rustdoc, and Markdown. Static HTML you own.",
5
5
  "type": "module",
6
6
  "engines": {
7
7
  "node": ">=20"
@@ -20,7 +20,7 @@
20
20
  "LICENSE"
21
21
  ],
22
22
  "scripts": {
23
- "build": "tsc && cp -r src/themes dist/ && cp src/client/*.js dist/client/ && cp -r src/og/fonts dist/og/ && rm -rf dist/core/sourcey-godoc dist/core/godoc-introspect && mkdir -p dist/core/sourcey-godoc && cp -R go/sourcey-godoc/go.mod go/sourcey-godoc/doc.go go/sourcey-godoc/cmd dist/core/sourcey-godoc/",
23
+ "build": "tsc && cp -r src/themes dist/ && cp src/client/*.js dist/client/ && cp -r src/og/fonts dist/og/ && rm -rf dist/core/sourcey-godoc dist/core/godoc-introspect && mkdir -p dist/core/sourcey-godoc && cp -R go/sourcey-godoc/go.mod go/sourcey-godoc/doc.go go/sourcey-godoc/cmd dist/core/sourcey-godoc/ && rm -rf dist/core/sourcey-rustdoc && mkdir -p dist/core/sourcey-rustdoc && cp -R rust/sourcey-rustdoc/Cargo.toml rust/sourcey-rustdoc/Cargo.lock rust/sourcey-rustdoc/README.md rust/sourcey-rustdoc/src rust/sourcey-rustdoc/tests dist/core/sourcey-rustdoc/",
24
24
  "watch": "npm run build && tsc --watch --preserveWatchOutput",
25
25
  "test": "vitest run",
26
26
  "test:watch": "vitest",
@@ -37,7 +37,6 @@
37
37
  "dependencies": {
38
38
  "@apidevtools/json-schema-ref-parser": "^13.0.5",
39
39
  "@preact/preset-vite": "^2.10.4",
40
- "@readme/openapi-parser": "^4.0.0",
41
40
  "@resvg/resvg-js": "^2.6.2",
42
41
  "@tailwindcss/typography": "^0.5.19",
43
42
  "@tailwindcss/vite": "^4.2.2",
@@ -47,9 +46,10 @@
47
46
  "js-yaml": "^4.1.0",
48
47
  "marked": "^15.0.0",
49
48
  "mcp-parser": "^0.4.0",
50
- "moxygen": "^2.1.8",
49
+ "moxygen": "^2.1.10",
51
50
  "preact": "^10.28.4",
52
51
  "preact-render-to-string": "^6.6.6",
52
+ "sanitize-html": "^2.17.4",
53
53
  "satori": "^0.26.0",
54
54
  "shiki": "^4.0.2",
55
55
  "swagger2openapi": "^7.0.8",
@@ -57,8 +57,10 @@
57
57
  "vite": "^8.0.9"
58
58
  },
59
59
  "devDependencies": {
60
+ "@eslint/js": "^9.39.4",
60
61
  "@types/js-yaml": "^4.0.9",
61
62
  "@types/node": "^22.19.15",
63
+ "@types/sanitize-html": "^2.16.1",
62
64
  "eslint": "^9.0.0",
63
65
  "eslint-config-prettier": "^10.0.0",
64
66
  "prettier": "^3.4.0",
@@ -78,9 +80,12 @@
78
80
  "developer-documentation",
79
81
  "doxygen",
80
82
  "godoc",
83
+ "rustdoc",
81
84
  "golang",
85
+ "rust",
82
86
  "go-docs",
83
87
  "go-documentation",
88
+ "rust-docs",
84
89
  "mcp",
85
90
  "llms-txt",
86
91
  "static-docs"