sourcey 3.5.10 → 3.6.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 (100) hide show
  1. package/README.md +24 -97
  2. package/dist/adapters/index.d.ts +12 -0
  3. package/dist/adapters/index.d.ts.map +1 -0
  4. package/dist/adapters/index.js +130 -0
  5. package/dist/adapters/mkdocs.d.ts +5 -0
  6. package/dist/adapters/mkdocs.d.ts.map +1 -0
  7. package/dist/adapters/mkdocs.js +367 -0
  8. package/dist/adapters/shared.d.ts +12 -0
  9. package/dist/adapters/shared.d.ts.map +1 -0
  10. package/dist/adapters/shared.js +134 -0
  11. package/dist/adapters/types.d.ts +74 -0
  12. package/dist/adapters/types.d.ts.map +1 -0
  13. package/dist/adapters/types.js +1 -0
  14. package/dist/cli.js +28 -34
  15. package/dist/client/search.js +213 -82
  16. package/dist/client/tabs.js +23 -0
  17. package/dist/components/layout/Head.js +17 -4
  18. package/dist/components/layout/TableOfContents.d.ts.map +1 -1
  19. package/dist/components/layout/TableOfContents.js +4 -2
  20. package/dist/components/openapi/Introduction.d.ts.map +1 -1
  21. package/dist/components/openapi/Introduction.js +16 -1
  22. package/dist/components/openapi/Security.d.ts.map +1 -1
  23. package/dist/components/openapi/Security.js +18 -2
  24. package/dist/config.d.ts +101 -0
  25. package/dist/config.d.ts.map +1 -1
  26. package/dist/config.js +95 -143
  27. package/dist/core/api-rendering.d.ts +47 -0
  28. package/dist/core/api-rendering.d.ts.map +1 -0
  29. package/dist/core/api-rendering.js +76 -0
  30. package/dist/core/doxygen-loader.d.ts.map +1 -1
  31. package/dist/core/doxygen-loader.js +36 -17
  32. package/dist/core/godoc-loader.d.ts.map +1 -1
  33. package/dist/core/godoc-loader.js +17 -6
  34. package/dist/core/markdown-loader.d.ts +14 -0
  35. package/dist/core/markdown-loader.d.ts.map +1 -1
  36. package/dist/core/markdown-loader.js +70 -26
  37. package/dist/core/mcp-normalizer.d.ts.map +1 -1
  38. package/dist/core/mcp-normalizer.js +48 -34
  39. package/dist/core/navigation.d.ts.map +1 -1
  40. package/dist/core/navigation.js +8 -7
  41. package/dist/core/parser.d.ts.map +1 -1
  42. package/dist/core/parser.js +75 -4
  43. package/dist/core/rustdoc-introspector.d.ts +29 -0
  44. package/dist/core/rustdoc-introspector.d.ts.map +1 -0
  45. package/dist/core/rustdoc-introspector.js +236 -0
  46. package/dist/core/rustdoc-loader.d.ts +25 -0
  47. package/dist/core/rustdoc-loader.d.ts.map +1 -0
  48. package/dist/core/rustdoc-loader.js +469 -0
  49. package/dist/core/rustdoc-render.d.ts +76 -0
  50. package/dist/core/rustdoc-render.d.ts.map +1 -0
  51. package/dist/core/rustdoc-render.js +785 -0
  52. package/dist/core/rustdoc-types.d.ts +314 -0
  53. package/dist/core/rustdoc-types.d.ts.map +1 -0
  54. package/dist/core/rustdoc-types.js +18 -0
  55. package/dist/core/search-indexer.d.ts +10 -0
  56. package/dist/core/search-indexer.d.ts.map +1 -1
  57. package/dist/core/search-indexer.js +6 -1
  58. package/dist/core/sourcey-rustdoc/Cargo.lock +705 -0
  59. package/dist/core/sourcey-rustdoc/Cargo.toml +38 -0
  60. package/dist/core/sourcey-rustdoc/README.md +59 -0
  61. package/dist/core/sourcey-rustdoc/src/diagnostics.rs +42 -0
  62. package/dist/core/sourcey-rustdoc/src/doctest.rs +268 -0
  63. package/dist/core/sourcey-rustdoc/src/extract.rs +492 -0
  64. package/dist/core/sourcey-rustdoc/src/lib.rs +58 -0
  65. package/dist/core/sourcey-rustdoc/src/links.rs +126 -0
  66. package/dist/core/sourcey-rustdoc/src/main.rs +256 -0
  67. package/dist/core/sourcey-rustdoc/src/signature.rs +415 -0
  68. package/dist/core/sourcey-rustdoc/src/spec.rs +390 -0
  69. package/dist/core/sourcey-rustdoc/tests/format_version.rs +45 -0
  70. package/dist/dev-server.d.ts.map +1 -1
  71. package/dist/dev-server.js +109 -33
  72. package/dist/index.d.ts +4 -0
  73. package/dist/index.d.ts.map +1 -1
  74. package/dist/index.js +26 -14
  75. package/dist/init.d.ts.map +1 -1
  76. package/dist/init.js +21 -16
  77. package/dist/renderer/changelog-feed.d.ts.map +1 -1
  78. package/dist/renderer/changelog-feed.js +27 -19
  79. package/dist/renderer/html-builder.d.ts.map +1 -1
  80. package/dist/renderer/html-builder.js +4 -3
  81. package/dist/renderer/llms.js +57 -2
  82. package/dist/site-assembly.d.ts +5 -0
  83. package/dist/site-assembly.d.ts.map +1 -1
  84. package/dist/site-assembly.js +161 -25
  85. package/dist/themes/default/sourcey.css +300 -0
  86. package/dist/utils/code-samples.d.ts +1 -0
  87. package/dist/utils/code-samples.d.ts.map +1 -1
  88. package/dist/utils/code-samples.js +30 -22
  89. package/dist/utils/html.d.ts +10 -0
  90. package/dist/utils/html.d.ts.map +1 -0
  91. package/dist/utils/html.js +152 -0
  92. package/dist/utils/http.d.ts +0 -2
  93. package/dist/utils/http.d.ts.map +1 -1
  94. package/dist/utils/http.js +19 -34
  95. package/dist/utils/markdown.d.ts.map +1 -1
  96. package/dist/utils/markdown.js +115 -39
  97. package/dist/vite-plugin.d.ts +2 -0
  98. package/dist/vite-plugin.d.ts.map +1 -1
  99. package/dist/vite-plugin.js +71 -1
  100. package/package.json +11 -5
@@ -1,5 +1,5 @@
1
1
  import { readFile } from "node:fs/promises";
2
- import { extname, posix, resolve } from "node:path";
2
+ import { extname, posix, relative, resolve } from "node:path";
3
3
  import { loadSpec } from "./core/loader.js";
4
4
  import { convertToOpenApi3 } from "./core/converter.js";
5
5
  import { parseSpec } from "./core/parser.js";
@@ -8,6 +8,7 @@ import { normalizeMcpSpec } from "./core/mcp-normalizer.js";
8
8
  import { loadDocsPage, slugFromPath } from "./core/markdown-loader.js";
9
9
  import { loadDoxygenTab } from "./core/doxygen-loader.js";
10
10
  import { loadGodocTab } from "./core/godoc-loader.js";
11
+ import { loadRustdocTab } from "./core/rustdoc-loader.js";
11
12
  import { buildNavFromSpec, buildNavFromPages } from "./core/navigation.js";
12
13
  import { generateChangelogFeeds } from "./renderer/changelog-feed.js";
13
14
  import { pageOutputPath, tabIndexOutputPath, tabPath } from "./config.js";
@@ -19,8 +20,9 @@ export async function assembleSite(config) {
19
20
  const siteTabs = [];
20
21
  const changelogDiagnostics = [];
21
22
  const godocDiagnostics = [];
23
+ const rustdocDiagnostics = [];
22
24
  for (const tab of config.tabs) {
23
- if (tab.openapi || tab.mcp) {
25
+ if (tab.source.kind === "openapi" || tab.source.kind === "mcp") {
24
26
  const spec = specsBySlug.get(tab.slug);
25
27
  const navTab = buildNavFromSpec(spec, tab.slug, config.prettyUrls);
26
28
  navTab.label = tab.label;
@@ -35,8 +37,8 @@ export async function assembleSite(config) {
35
37
  });
36
38
  continue;
37
39
  }
38
- if (tab.doxygen) {
39
- const { pages, navTab } = await loadDoxygenTab(tab.doxygen, tab.slug, tab.label);
40
+ if (tab.source.kind === "doxygen") {
41
+ const { pages, navTab } = await loadDoxygenTab(tab.source.config, tab.slug, tab.label);
40
42
  for (const [slug, page] of pages) {
41
43
  const outputPath = pageOutputPath(tab.slug, slug, config.prettyUrls);
42
44
  pageMap.set(outputPath, {
@@ -50,11 +52,11 @@ export async function assembleSite(config) {
50
52
  siteTabs.push(navTab);
51
53
  continue;
52
54
  }
53
- if (tab.godoc) {
54
- const { pages, navTab, diagnostics } = await loadGodocTab(tab.godoc, tab.slug, tab.label, {
55
+ if (tab.source.kind === "godoc") {
56
+ const { pages, navTab, diagnostics } = await loadGodocTab(tab.source.config, tab.slug, tab.label, {
55
57
  repo: config.repo,
56
58
  editBranch: config.editBranch,
57
- editBasePath: tab.godoc.sourceBasePath,
59
+ editBasePath: tab.source.config.sourceBasePath,
58
60
  });
59
61
  godocDiagnostics.push(...diagnostics);
60
62
  for (const [slug, page] of pages) {
@@ -70,15 +72,37 @@ export async function assembleSite(config) {
70
72
  siteTabs.push(navTab);
71
73
  continue;
72
74
  }
73
- if (!tab.groups)
75
+ if (tab.source.kind === "rustdoc") {
76
+ const { pages, navTab, diagnostics } = await loadRustdocTab(tab.source.config, tab.slug, tab.label, {
77
+ repo: config.repo,
78
+ editBranch: config.editBranch,
79
+ editBasePath: tab.source.config.sourceBasePath,
80
+ });
81
+ rustdocDiagnostics.push(...diagnostics);
82
+ for (const [slug, page] of pages) {
83
+ const outputPath = pageOutputPath(tab.slug, slug, config.prettyUrls);
84
+ pageMap.set(outputPath, {
85
+ outputPath,
86
+ currentPage: { kind: "markdown", markdown: page },
87
+ spec: primarySpec,
88
+ tabSlug: tab.slug,
89
+ pageSlug: slug,
90
+ });
91
+ }
92
+ siteTabs.push(navTab);
93
+ continue;
94
+ }
95
+ if (tab.source.kind !== "markdown")
74
96
  continue;
75
97
  const pagesByPath = new Map();
76
- for (const group of tab.groups) {
98
+ for (const group of tab.source.groups) {
77
99
  for (const rp of group.pages) {
78
100
  const slug = slugFromPath(rp.slug);
79
101
  const page = await loadDocsPage(rp.file, slug, {
80
102
  changelog: config.changelog.enabled,
81
103
  repoUrl: config.repo,
104
+ sourceRoot: rp.sourceRoot,
105
+ preprocess: rp.preprocess,
82
106
  });
83
107
  pagesByPath.set(rp.slug, page);
84
108
  const outputPath = pageOutputPath(tab.slug, slug, config.prettyUrls);
@@ -111,6 +135,7 @@ export async function assembleSite(config) {
111
135
  const sitePages = Array.from(pageMap.values());
112
136
  resolveInternalLinks(sitePages, config);
113
137
  const extraFiles = attachChangelogFeeds(sitePages, config);
138
+ await attachSourceAssets(extraFiles, config.tabs);
114
139
  return {
115
140
  siteTabs,
116
141
  primarySpec,
@@ -118,6 +143,7 @@ export async function assembleSite(config) {
118
143
  pageMap,
119
144
  changelogDiagnostics,
120
145
  godocDiagnostics,
146
+ rustdocDiagnostics,
121
147
  extraFiles,
122
148
  };
123
149
  }
@@ -127,16 +153,49 @@ export function formatGodocDiagnostic(diag) {
127
153
  const loc = diag.file ? ` ${diag.file}${diag.line ? `:${diag.line}` : ""}` : "";
128
154
  return `[${sev}] ${diag.code}${where}${loc} ${diag.message}`;
129
155
  }
156
+ export function enforceGodocDiagnostics(diagnostics) {
157
+ const error = diagnostics.find((diagnostic) => diagnostic.severity === "error");
158
+ if (error)
159
+ throw new Error(formatGodocFailure(error));
160
+ }
161
+ export function enforceRustdocDiagnostics(diagnostics) {
162
+ const error = diagnostics.find((diagnostic) => diagnostic.severity === "error");
163
+ if (error)
164
+ throw new Error(formatRustdocFailure(error));
165
+ }
166
+ function formatGodocFailure(diagnostic) {
167
+ const where = diagnostic.package ? ` (${diagnostic.package})` : "";
168
+ const loc = diagnostic.file
169
+ ? ` ${diagnostic.file}${diagnostic.line ? `:${diagnostic.line}` : ""}`
170
+ : "";
171
+ return `[${diagnostic.code}]${where}${loc}: ${diagnostic.message}`;
172
+ }
173
+ export function formatRustdocDiagnostic(diagnostic) {
174
+ const location = diagnostic.file
175
+ ? ` ${diagnostic.file}${diagnostic.line ? `:${diagnostic.line}` : ""}`
176
+ : "";
177
+ const cratePrefix = diagnostic.crate ? ` [${diagnostic.crate}]` : "";
178
+ return `[${diagnostic.severity.toUpperCase()}] ${diagnostic.code}${cratePrefix}${location} ${diagnostic.message}`;
179
+ }
180
+ function formatRustdocFailure(diagnostic) {
181
+ const location = diagnostic.file
182
+ ? ` ${diagnostic.file}${diagnostic.line ? `:${diagnostic.line}` : ""}`
183
+ : "";
184
+ const cratePrefix = diagnostic.crate ? ` (${diagnostic.crate})` : "";
185
+ return `[${diagnostic.code}]${cratePrefix}${location}: ${diagnostic.message}`;
186
+ }
130
187
  export function collectDocsPagesByTab(pageMap, tabs) {
131
188
  const docsPagesByTab = new Map();
132
189
  for (const tab of tabs) {
133
- if (!tab.groups && !tab.doxygen && !tab.godoc)
190
+ if (!isDocsSource(tab.source.kind))
134
191
  continue;
135
192
  const tabPages = [];
136
193
  for (const [, page] of pageMap) {
137
194
  if (page.tabSlug !== tab.slug || !isSearchableDocsSitePage(page))
138
195
  continue;
139
- tabPages.push(page.currentPage.kind === "markdown" ? page.currentPage.markdown : page.currentPage.changelog);
196
+ tabPages.push(page.currentPage.kind === "markdown"
197
+ ? page.currentPage.markdown
198
+ : page.currentPage.changelog);
140
199
  }
141
200
  docsPagesByTab.set(tab.slug, tabPages);
142
201
  }
@@ -144,10 +203,10 @@ export function collectDocsPagesByTab(pageMap, tabs) {
144
203
  }
145
204
  export function rebuildMarkdownTabNavigation(pageMap, siteTabs, tabs, tabSlug, prettyUrls) {
146
205
  const tab = tabs.find((candidate) => candidate.slug === tabSlug);
147
- if (!tab?.groups)
206
+ if (!tab || tab.source.kind !== "markdown")
148
207
  return;
149
208
  const pagesByPath = new Map();
150
- for (const group of tab.groups) {
209
+ for (const group of tab.source.groups) {
151
210
  for (const rp of group.pages) {
152
211
  const slug = slugFromPath(rp.slug);
153
212
  const entry = pageMap.get(pageOutputPath(tab.slug, slug, prettyUrls));
@@ -241,6 +300,7 @@ export function createMinimalSpec() {
241
300
  }
242
301
  export function resolveInternalLinks(pages, config) {
243
302
  const pathMap = new Map();
303
+ addSourceAssetAliases(pathMap, config.tabs ?? []);
244
304
  for (const page of pages) {
245
305
  const out = page.outputPath;
246
306
  const clean = out.replace(/\/index\.html$/, "").replace(/\.html$/, "");
@@ -253,6 +313,10 @@ export function resolveInternalLinks(pages, config) {
253
313
  .replace(/^\/+/, "")
254
314
  .replace(/\.(md|mdx)$/, "");
255
315
  pathMap.set(sourceClean, out);
316
+ const sourceRelativeClean = getSourceRootRelativePath(page);
317
+ if (sourceRelativeClean) {
318
+ pathMap.set(sourceRelativeClean, out);
319
+ }
256
320
  }
257
321
  const repoBase = config.repo?.replace(/\/$/, "");
258
322
  const branch = config.editBranch;
@@ -263,6 +327,7 @@ export function resolveInternalLinks(pages, config) {
263
327
  if (!docsSourcePath)
264
328
  continue;
265
329
  if (page.currentPage.kind === "markdown") {
330
+ page.currentPage.markdown.description = rewriteMarkdownLinks(page.currentPage.markdown.description, page.outputPath, docsSourcePath, pathMap, sourceBase, prettyUrls);
266
331
  page.currentPage.markdown.html = rewriteHtmlLinks(page.currentPage.markdown.html, page.outputPath, docsSourcePath, pathMap, sourceBase, prettyUrls);
267
332
  continue;
268
333
  }
@@ -338,6 +403,15 @@ function attachChangelogFeeds(pages, config) {
338
403
  }
339
404
  return extraFiles;
340
405
  }
406
+ async function attachSourceAssets(extraFiles, tabs) {
407
+ for (const tab of tabs) {
408
+ if (tab.source.kind !== "markdown")
409
+ continue;
410
+ for (const asset of tab.source.assets ?? []) {
411
+ extraFiles.set(tabPath(tab.slug, asset.outputPath), await readFile(asset.file));
412
+ }
413
+ }
414
+ }
341
415
  function addPermalinkPages(pageMap, prettyUrls) {
342
416
  const changelogPages = Array.from(pageMap.values()).filter(isMainChangelogSitePage);
343
417
  for (const page of changelogPages) {
@@ -364,15 +438,15 @@ function addPermalinkPages(pageMap, prettyUrls) {
364
438
  async function loadSpecs(tabs) {
365
439
  const specsBySlug = new Map();
366
440
  for (const tab of tabs) {
367
- if (tab.openapi) {
368
- const loaded = await loadSpec(tab.openapi);
441
+ if (tab.source.kind === "openapi") {
442
+ const loaded = await loadSpec(tab.source.spec);
369
443
  const parsed = await parseSpec(loaded);
370
444
  const openapi3 = await convertToOpenApi3(parsed);
371
445
  specsBySlug.set(tab.slug, normalizeSpec(openapi3));
372
446
  }
373
- else if (tab.mcp) {
447
+ else if (tab.source.kind === "mcp") {
374
448
  const { parse } = await import("mcp-parser");
375
- const mcpSpec = await parse(tab.mcp);
449
+ const mcpSpec = await parse(tab.source.spec);
376
450
  specsBySlug.set(tab.slug, normalizeMcpSpec(mcpSpec));
377
451
  }
378
452
  }
@@ -395,7 +469,10 @@ const MIME_TYPES = {
395
469
  ".ico": "image/x-icon",
396
470
  };
397
471
  async function resolveAssetUrl(pathOrUrl) {
398
- if (!pathOrUrl || pathOrUrl.startsWith("data:") || pathOrUrl.startsWith("http://") || pathOrUrl.startsWith("https://")) {
472
+ if (!pathOrUrl ||
473
+ pathOrUrl.startsWith("data:") ||
474
+ pathOrUrl.startsWith("http://") ||
475
+ pathOrUrl.startsWith("https://")) {
399
476
  return pathOrUrl;
400
477
  }
401
478
  const abs = resolve(pathOrUrl);
@@ -409,6 +486,9 @@ function isMainChangelogSitePage(page) {
409
486
  function isSearchableDocsSitePage(page) {
410
487
  return page.currentPage.kind === "markdown" || isMainChangelogSitePage(page);
411
488
  }
489
+ function isDocsSource(kind) {
490
+ return kind === "markdown" || kind === "doxygen" || kind === "godoc" || kind === "rustdoc";
491
+ }
412
492
  function createFeedLinks(atomPath, rssPath, title) {
413
493
  return [
414
494
  { href: atomPath, type: "application/atom+xml", title: `${title} Atom Feed` },
@@ -429,26 +509,74 @@ function getDocsSourcePath(page) {
429
509
  return page.currentPage.changelog.sourcePath;
430
510
  return undefined;
431
511
  }
512
+ function getSourceRootRelativePath(page) {
513
+ if (page.currentPage.kind !== "markdown")
514
+ return undefined;
515
+ const markdown = page.currentPage.markdown;
516
+ if (!markdown.sourceRoot)
517
+ return undefined;
518
+ const sourceRoot = normalizeSourcePath(markdown.sourceRoot);
519
+ const sourcePath = normalizeSourcePath(markdown.sourcePath);
520
+ if (!sourcePath.startsWith(`${sourceRoot}/`))
521
+ return undefined;
522
+ return stripMarkdownExtension(sourcePath.slice(sourceRoot.length + 1));
523
+ }
524
+ function addSourceAssetAliases(pathMap, tabs) {
525
+ for (const tab of tabs) {
526
+ if (tab.source.kind !== "markdown")
527
+ continue;
528
+ for (const asset of tab.source.assets ?? []) {
529
+ const outputPath = tabPath(tab.slug, asset.outputPath);
530
+ pathMap.set(normalizeSourcePath(asset.outputPath), outputPath);
531
+ for (const sourceRoot of sourceRootsForMarkdownSource(tab.source)) {
532
+ const sourceRootPath = normalizeSourcePath(relative(process.cwd(), sourceRoot));
533
+ pathMap.set(posix.join(sourceRootPath, normalizeSourcePath(asset.outputPath)), outputPath);
534
+ }
535
+ }
536
+ }
537
+ }
538
+ function sourceRootsForMarkdownSource(source) {
539
+ const roots = new Set();
540
+ for (const group of source.groups) {
541
+ for (const page of group.pages) {
542
+ if (page.sourceRoot)
543
+ roots.add(page.sourceRoot);
544
+ }
545
+ }
546
+ return [...roots];
547
+ }
548
+ function normalizeSourcePath(value) {
549
+ return value.replace(/\\/g, "/").replace(/^\/+/, "");
550
+ }
551
+ function stripMarkdownExtension(value) {
552
+ return value.replace(/\.(md|mdx)$/, "");
553
+ }
432
554
  function rewriteHtmlLinks(html, outputPath, docsSourcePath, pathMap, sourceBase, prettyUrls) {
433
- return html.replace(/href="([^"]+)"/g, (match, href) => {
555
+ return html.replace(/\b(href|src)="([^"]+)"/g, (match, attr, href) => {
434
556
  const resolved = resolveInternalHref(href, outputPath, docsSourcePath, pathMap, sourceBase, prettyUrls);
435
- return resolved ? `href="${resolved}"` : match;
557
+ return resolved ? `${attr}="${resolved}"` : match;
436
558
  });
437
559
  }
438
560
  function rewriteMarkdownLinks(markdown, outputPath, docsSourcePath, pathMap, sourceBase, prettyUrls) {
439
- return markdown.replace(/\[([^\]]+)\]\(([^)]+)\)/g, (match, label, href) => {
561
+ return markdown.replace(/\[((?:`[^`]*`|[^\]])+)\]\(([^)]+)\)/g, (match, label, href) => {
440
562
  const resolved = resolveInternalHref(href, outputPath, docsSourcePath, pathMap, sourceBase, prettyUrls);
441
563
  return resolved ? `[${label}](${resolved})` : match;
442
564
  });
443
565
  }
444
566
  function resolveInternalHref(href, outputPath, docsSourcePath, pathMap, sourceBase, prettyUrls) {
445
- if (href.startsWith("http://") || href.startsWith("https://") || href.startsWith("#") || href.startsWith("mailto:")) {
567
+ if (href.startsWith("http://") ||
568
+ href.startsWith("https://") ||
569
+ href.startsWith("#") ||
570
+ href.startsWith("mailto:")) {
446
571
  return null;
447
572
  }
448
573
  const [path, hash] = href.split("#", 2);
449
574
  const hashSuffix = hash ? `#${hash}` : "";
450
- const sourcePath = path.replace(/\\/g, "/");
451
- const clean = sourcePath.replace(/^\/+/, "").replace(/\/+$/, "").replace(/\.(md|mdx|html)$/, "");
575
+ const sourcePath = decodeHrefPath(path).replace(/\\/g, "/");
576
+ const clean = sourcePath
577
+ .replace(/^\/+/, "")
578
+ .replace(/\/+$/, "")
579
+ .replace(/\.(md|mdx|html)$/, "");
452
580
  const pageDir = outputPath.includes("/")
453
581
  ? outputPath.substring(0, outputPath.lastIndexOf("/"))
454
582
  : "";
@@ -490,6 +618,14 @@ function resolveInternalHref(href, outputPath, docsSourcePath, pathMap, sourceBa
490
618
  }
491
619
  return null;
492
620
  }
621
+ function decodeHrefPath(path) {
622
+ try {
623
+ return decodeURIComponent(path);
624
+ }
625
+ catch {
626
+ return path;
627
+ }
628
+ }
493
629
  /**
494
630
  * Rewrite an on-disk output path into the relative href that a user visits.
495
631
  * - `"slash"`: trims trailing `index.html`, leaves a directory-style `foo/`.
@@ -506,7 +642,7 @@ function toPrettyLink(target, prettyUrls) {
506
642
  return prettyUrls === "strip" ? withSlash.slice(0, -1) : withSlash;
507
643
  }
508
644
  if (prettyUrls === "strip" && target.endsWith(".html")) {
509
- return target.slice(0, -(".html".length));
645
+ return target.slice(0, -".html".length);
510
646
  }
511
647
  return target;
512
648
  }
@@ -1893,3 +1893,303 @@ h1[id], h2[id], h3[id], h4[id], h5[id], h6[id] {
1893
1893
  #sourcey .godoc-type .godoc-symbol {
1894
1894
  word-break: break-word;
1895
1895
  }
1896
+
1897
+ /* ── Rust API rendering (rustdoc adapter) ────────────────────────── */
1898
+ /* Shared API primitives (api-*) introduced for rustdoc and reusable
1899
+ by godoc/doxygen in a follow-on retrofit. Class names mirror
1900
+ rustdoc's DOM so deep-links from a rustdoc URL line up with
1901
+ sourcey output. */
1902
+
1903
+ #sourcey .rust-item {
1904
+ border-top: 1px solid rgb(var(--color-gray-200));
1905
+ padding-top: 1rem;
1906
+ margin-top: 1.25rem;
1907
+ }
1908
+ .dark #sourcey .rust-item {
1909
+ border-top-color: rgb(var(--color-gray-800));
1910
+ }
1911
+
1912
+ #sourcey .code-header,
1913
+ #sourcey .rust-signature {
1914
+ font-family: ui-monospace, "SF Mono", "Cascadia Code", Consolas, "Liberation Mono", Menlo, monospace;
1915
+ font-size: 0.9375rem;
1916
+ color: rgb(var(--color-gray-900));
1917
+ white-space: pre-wrap;
1918
+ word-break: break-word;
1919
+ }
1920
+ .dark #sourcey .code-header,
1921
+ .dark #sourcey .rust-signature {
1922
+ color: rgb(var(--color-gray-100));
1923
+ }
1924
+
1925
+ #sourcey .rust-signature .kw {
1926
+ color: rgb(var(--color-rose-600));
1927
+ font-weight: 600;
1928
+ }
1929
+ .dark #sourcey .rust-signature .kw {
1930
+ color: rgb(var(--color-rose-300));
1931
+ }
1932
+
1933
+ #sourcey .rust-signature .ident {
1934
+ color: rgb(var(--color-indigo-600));
1935
+ }
1936
+ .dark #sourcey .rust-signature .ident {
1937
+ color: rgb(var(--color-indigo-300));
1938
+ }
1939
+
1940
+ #sourcey .rust-signature .lifetime {
1941
+ color: rgb(var(--color-amber-700));
1942
+ font-style: italic;
1943
+ }
1944
+ .dark #sourcey .rust-signature .lifetime {
1945
+ color: rgb(var(--color-amber-300));
1946
+ }
1947
+
1948
+ #sourcey .api-rightside,
1949
+ #sourcey .rightside {
1950
+ float: right;
1951
+ font-size: 0.8125rem;
1952
+ color: rgb(var(--color-gray-500));
1953
+ }
1954
+
1955
+ #sourcey .api-since,
1956
+ #sourcey .since {
1957
+ font-feature-settings: "tnum";
1958
+ }
1959
+
1960
+ #sourcey .api-srclink,
1961
+ #sourcey .srclink {
1962
+ color: rgb(var(--color-indigo-600));
1963
+ text-decoration: none;
1964
+ }
1965
+ #sourcey .api-srclink:hover,
1966
+ #sourcey .srclink:hover {
1967
+ text-decoration: underline;
1968
+ }
1969
+ .dark #sourcey .api-srclink,
1970
+ .dark #sourcey .srclink {
1971
+ color: rgb(var(--color-indigo-300));
1972
+ }
1973
+
1974
+ #sourcey .docblock,
1975
+ #sourcey .rust-doc {
1976
+ color: rgb(var(--color-gray-700));
1977
+ margin-top: 0.5rem;
1978
+ }
1979
+ .dark #sourcey .docblock,
1980
+ .dark #sourcey .rust-doc {
1981
+ color: rgb(var(--color-gray-300));
1982
+ }
1983
+
1984
+ #sourcey .stab,
1985
+ #sourcey .api-stab {
1986
+ display: block;
1987
+ margin: 0.5rem 0;
1988
+ padding: 0.5rem 0.75rem;
1989
+ border-radius: 0.375rem;
1990
+ font-size: 0.875rem;
1991
+ border: 1px solid transparent;
1992
+ }
1993
+ #sourcey .stab.unstable,
1994
+ #sourcey .api-stab-unstable {
1995
+ background-color: rgb(var(--color-amber-50));
1996
+ border-color: rgb(var(--color-amber-200));
1997
+ color: rgb(var(--color-amber-900));
1998
+ }
1999
+ #sourcey .stab.deprecated,
2000
+ #sourcey .api-stab-deprecated {
2001
+ background-color: rgb(var(--color-rose-50));
2002
+ border-color: rgb(var(--color-rose-200));
2003
+ color: rgb(var(--color-rose-900));
2004
+ }
2005
+ #sourcey .stab.portability,
2006
+ #sourcey .api-stab-portability {
2007
+ background-color: rgb(var(--color-emerald-50));
2008
+ border-color: rgb(var(--color-emerald-200));
2009
+ color: rgb(var(--color-emerald-900));
2010
+ }
2011
+ .dark #sourcey .stab.unstable,
2012
+ .dark #sourcey .api-stab-unstable {
2013
+ background-color: rgba(251, 191, 36, 0.08);
2014
+ border-color: rgba(251, 191, 36, 0.25);
2015
+ color: rgb(var(--color-amber-200));
2016
+ }
2017
+ .dark #sourcey .stab.deprecated,
2018
+ .dark #sourcey .api-stab-deprecated {
2019
+ background-color: rgba(244, 63, 94, 0.08);
2020
+ border-color: rgba(244, 63, 94, 0.25);
2021
+ color: rgb(var(--color-rose-200));
2022
+ }
2023
+ .dark #sourcey .stab.portability,
2024
+ .dark #sourcey .api-stab-portability {
2025
+ background-color: rgba(16, 185, 129, 0.08);
2026
+ border-color: rgba(16, 185, 129, 0.25);
2027
+ color: rgb(var(--color-emerald-200));
2028
+ }
2029
+
2030
+ /* Collapsible trait-impl block. Chevron pattern mirrors .godoc-example so the
2031
+ disclosure reads the same across adapters. */
2032
+ #sourcey .api-toggle {
2033
+ margin: 0.75rem 0;
2034
+ border: 1px solid rgb(var(--color-gray-200));
2035
+ border-radius: 6px;
2036
+ background: rgba(var(--color-gray-50), 0.4);
2037
+ }
2038
+ .dark #sourcey .api-toggle {
2039
+ border-color: rgb(var(--color-gray-800));
2040
+ }
2041
+ #sourcey .api-toggle > summary {
2042
+ cursor: pointer;
2043
+ padding: 0.5rem 0.875rem;
2044
+ list-style: none;
2045
+ font-family: var(--font-mono);
2046
+ font-size: 0.8125rem;
2047
+ color: rgb(var(--color-gray-700));
2048
+ }
2049
+ .dark #sourcey .api-toggle > summary {
2050
+ color: rgb(var(--color-gray-300));
2051
+ }
2052
+ #sourcey .api-toggle > summary::-webkit-details-marker {
2053
+ display: none;
2054
+ }
2055
+ #sourcey .api-toggle > summary::before {
2056
+ content: "▸ ";
2057
+ color: rgb(var(--color-gray-400));
2058
+ margin-right: 0.25rem;
2059
+ }
2060
+ #sourcey .api-toggle[open] > summary::before {
2061
+ content: "▾ ";
2062
+ }
2063
+ /* The impl header lives inside the summary; keep it inline with the chevron
2064
+ and strip heading margins. */
2065
+ #sourcey .api-toggle > summary .rust-impl-header {
2066
+ display: inline;
2067
+ margin: 0;
2068
+ font-size: inherit;
2069
+ }
2070
+ #sourcey .api-toggle-body {
2071
+ padding: 0.25rem 0.875rem 0.5rem;
2072
+ border-top: 1px solid rgb(var(--color-gray-200));
2073
+ }
2074
+ .dark #sourcey .api-toggle-body {
2075
+ border-top-color: rgb(var(--color-gray-800));
2076
+ }
2077
+ /* Marker / auto / blanket trait impls: a quiet list, not expandable boxes. */
2078
+ #sourcey .rust-impl-rows {
2079
+ margin: 0.5rem 0 0.75rem;
2080
+ }
2081
+ #sourcey .rust-impl-row {
2082
+ padding: 0.2rem 0;
2083
+ }
2084
+ #sourcey .rust-impl-row .rust-impl-header {
2085
+ display: inline;
2086
+ margin: 0;
2087
+ font-size: 0.8125rem;
2088
+ font-weight: 400;
2089
+ color: rgb(var(--color-gray-600));
2090
+ }
2091
+ .dark #sourcey .rust-impl-row .rust-impl-header {
2092
+ color: rgb(var(--color-gray-400));
2093
+ }
2094
+
2095
+ #sourcey .anchor,
2096
+ #sourcey .api-anchor {
2097
+ margin-left: 0.4rem;
2098
+ color: rgb(var(--color-gray-400));
2099
+ text-decoration: none;
2100
+ opacity: 0;
2101
+ transition: opacity 0.1s ease;
2102
+ }
2103
+ #sourcey .section-header:hover .anchor,
2104
+ #sourcey .section-header:hover .api-anchor,
2105
+ #sourcey .code-header:hover .anchor,
2106
+ #sourcey .code-header:hover .api-anchor {
2107
+ opacity: 1;
2108
+ }
2109
+
2110
+ /* Inherent-impl methods and trait members render inline as their own sections.
2111
+ Code (signatures, doctests) flows through the shared Shiki code block, so no
2112
+ bespoke code styling lives here. */
2113
+ #sourcey .rust-member {
2114
+ margin: 0.75rem 0;
2115
+ }
2116
+ #sourcey .rust-method-header {
2117
+ margin: 0.5rem 0 0.25rem;
2118
+ }
2119
+ #sourcey .rust-doctest {
2120
+ margin: 0.75rem 0;
2121
+ }
2122
+
2123
+ #sourcey .rust-doctest-badges {
2124
+ display: flex;
2125
+ flex-wrap: wrap;
2126
+ gap: 0.25rem;
2127
+ margin-bottom: 0.25rem;
2128
+ }
2129
+ #sourcey .rust-doctest-badge {
2130
+ display: inline-block;
2131
+ padding: 0.05rem 0.4rem;
2132
+ font-size: 0.6875rem;
2133
+ font-weight: 600;
2134
+ border-radius: 0.25rem;
2135
+ background-color: rgb(var(--color-gray-100));
2136
+ color: rgb(var(--color-gray-700));
2137
+ text-transform: lowercase;
2138
+ letter-spacing: 0.02em;
2139
+ }
2140
+ .dark #sourcey .rust-doctest-badge {
2141
+ background-color: rgb(var(--color-gray-800));
2142
+ color: rgb(var(--color-gray-300));
2143
+ }
2144
+ #sourcey .rust-doctest-badge-should_panic,
2145
+ #sourcey .rust-doctest-badge-compile_fail {
2146
+ background-color: rgb(var(--color-rose-100));
2147
+ color: rgb(var(--color-rose-800));
2148
+ }
2149
+ #sourcey .rust-doctest-badge-no_run,
2150
+ #sourcey .rust-doctest-badge-ignore {
2151
+ background-color: rgb(var(--color-amber-100));
2152
+ color: rgb(var(--color-amber-800));
2153
+ }
2154
+
2155
+ #sourcey .rust-doctest-controls {
2156
+ display: flex;
2157
+ gap: 0.5rem;
2158
+ margin-top: 0.5rem;
2159
+ font-size: 0.8125rem;
2160
+ }
2161
+ #sourcey .rust-doctest-run,
2162
+ #sourcey .rust-doctest-toggle-hidden {
2163
+ cursor: pointer;
2164
+ padding: 0.2rem 0.6rem;
2165
+ border-radius: 0.375rem;
2166
+ border: 1px solid rgb(var(--color-gray-300));
2167
+ background-color: rgb(var(--color-white));
2168
+ color: rgb(var(--color-gray-800));
2169
+ text-decoration: none;
2170
+ line-height: 1;
2171
+ }
2172
+ #sourcey .rust-doctest-run:hover,
2173
+ #sourcey .rust-doctest-toggle-hidden:hover {
2174
+ background-color: rgb(var(--color-gray-50));
2175
+ }
2176
+ .dark #sourcey .rust-doctest-run,
2177
+ .dark #sourcey .rust-doctest-toggle-hidden {
2178
+ background-color: rgb(var(--color-gray-900));
2179
+ border-color: rgb(var(--color-gray-700));
2180
+ color: rgb(var(--color-gray-200));
2181
+ }
2182
+
2183
+ /* On the aggregated doctests page the code block already renders as a card, so
2184
+ the section itself stays borderless. Just space the entries and accent the
2185
+ heading so they don't read as cards within cards. */
2186
+ #sourcey .rust-doctests-index .rust-doctest {
2187
+ margin: 1.5rem 0;
2188
+ }
2189
+ #sourcey .rust-doctests-index .rust-doctest > h3 {
2190
+ padding-left: 0.625rem;
2191
+ border-left: 3px solid rgb(var(--color-emerald-500));
2192
+ }
2193
+ .dark #sourcey .rust-doctests-index .rust-doctest > h3 {
2194
+ border-left-color: rgb(var(--color-emerald-400));
2195
+ }
@@ -2,6 +2,7 @@ import type { NormalizedOperation } from "../core/types.js";
2
2
  import type { CodeSample } from "../core/types.js";
3
3
  /** Default languages when none configured. */
4
4
  export declare const DEFAULT_CODE_SAMPLE_LANGS: string[];
5
+ export declare const SUPPORTED_CODE_SAMPLE_LANGS: readonly string[];
5
6
  /**
6
7
  * Generate code samples for an operation.
7
8
  *
@@ -1 +1 @@
1
- {"version":3,"file":"code-samples.d.ts","sourceRoot":"","sources":["../../src/utils/code-samples.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,kBAAkB,CAAC;AAC5D,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAuMnD,8CAA8C;AAC9C,eAAO,MAAM,yBAAyB,UAAmC,CAAC;AAM1E;;;;;;GAMG;AACH,wBAAgB,mBAAmB,CACjC,EAAE,EAAE,mBAAmB,EACvB,SAAS,EAAE,MAAM,EACjB,KAAK,GAAE,MAAM,EAA8B,GAC1C,UAAU,EAAE,CAmBd"}
1
+ {"version":3,"file":"code-samples.d.ts","sourceRoot":"","sources":["../../src/utils/code-samples.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,kBAAkB,CAAC;AAC5D,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAoNnD,8CAA8C;AAC9C,eAAO,MAAM,yBAAyB,UAAmC,CAAC;AAC1E,eAAO,MAAM,2BAA2B,mBAAyC,CAAC;AAMlF;;;;;;GAMG;AACH,wBAAgB,mBAAmB,CACjC,EAAE,EAAE,mBAAmB,EACvB,SAAS,EAAE,MAAM,EACjB,KAAK,GAAE,MAAM,EAA8B,GAC1C,UAAU,EAAE,CAiBd"}