sourcey 3.6.2 → 3.6.4

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/README.md CHANGED
@@ -2,18 +2,14 @@
2
2
 
3
3
  > Docs straight from the source.
4
4
 
5
- Sourcey tells the whole product story. It turns specs, code, rich guides, changelog, roadmap pages, examples, and portable context files from your project into static HTML you can deploy anywhere.
5
+ Sourcey turns OpenAPI, MCP, Doxygen XML, godoc, rustdoc, and Markdown into one static HTML site you own: reference, guides, changelog, roadmap, and llms.txt from the same build, deployable anywhere.
6
6
 
7
- No dashboard. No runtime. No API calls to render your own documentation.
7
+ Everything renders at build time: no dashboard, no runtime, no API calls to render your own documentation.
8
8
 
9
9
  [![npm](https://img.shields.io/npm/v/sourcey)](https://www.npmjs.com/package/sourcey)
10
10
  [![build](https://img.shields.io/github/actions/workflow/status/sourcey/sourcey/ci.yml?branch=main)](https://github.com/sourcey/sourcey/actions)
11
11
  [![node](https://img.shields.io/node/v/sourcey)](https://nodejs.org)
12
12
  [![license](https://img.shields.io/npm/l/sourcey)](LICENSE)
13
- [![Go Reference](https://pkg.go.dev/badge/github.com/sourcey/sourcey/go/sourcey-godoc.svg)](https://pkg.go.dev/github.com/sourcey/sourcey/go/sourcey-godoc)
14
- [![Go Report Card](https://goreportcard.com/badge/github.com/sourcey/sourcey/go/sourcey-godoc)](https://goreportcard.com/report/github.com/sourcey/sourcey/go/sourcey-godoc)
15
- [![crates.io](https://img.shields.io/crates/v/sourcey.svg)](https://crates.io/crates/sourcey)
16
- [![docs.rs](https://docs.rs/sourcey/badge.svg)](https://docs.rs/sourcey)
17
13
 
18
14
  ```bash
19
15
  npx sourcey init
@@ -60,6 +56,9 @@ Then `sourcey init` to scaffold a new project, or `sourcey build` against an exi
60
56
 
61
57
  ### Standalone Go docs generator
62
58
 
59
+ [![Go Reference](https://pkg.go.dev/badge/github.com/sourcey/sourcey/go/sourcey-godoc.svg)](https://pkg.go.dev/github.com/sourcey/sourcey/go/sourcey-godoc)
60
+ [![Go Report Card](https://goreportcard.com/badge/github.com/sourcey/sourcey/go/sourcey-godoc)](https://goreportcard.com/report/github.com/sourcey/sourcey/go/sourcey-godoc)
61
+
63
62
  For Go-only consumers without a JavaScript toolchain, `sourcey-godoc` ships as a separate native binary. It produces static Go docs sites or portable `godoc.json` snapshots.
64
63
 
65
64
  | Path | Command |
@@ -68,19 +67,31 @@ For Go-only consumers without a JavaScript toolchain, `sourcey-godoc` ships as a
68
67
  | Homebrew | `brew install sourcey/tap/sourcey-godoc` |
69
68
  | Scoop | `scoop bucket add sourcey https://github.com/sourcey/scoop-bucket && scoop install sourcey-godoc` |
70
69
 
70
+ ### Rust snapshot converter
71
+
72
+ [![crates.io](https://img.shields.io/crates/v/sourcey.svg)](https://crates.io/crates/sourcey)
73
+ [![docs.rs](https://docs.rs/sourcey/badge.svg)](https://docs.rs/sourcey)
74
+
75
+ The `rustdoc()` adapter consumes nightly rustdoc JSON through the [`sourcey`](https://crates.io/crates/sourcey) companion crate and its `sourcey-rustdoc` converter, so CI can build Rust API docs from a committed snapshot on a stable toolchain. See [docs/adapters/rustdoc.md](docs/adapters/rustdoc.md).
76
+
71
77
  ## Quick start
72
78
 
73
79
  ```bash
74
- # From a single OpenAPI spec
75
- sourcey build api.yaml -o docs/
76
-
77
- # Multi-page site (reads sourcey.config.ts)
78
- sourcey build -o docs/
80
+ # Scaffold a project (detects OpenAPI specs and Doxyfiles)
81
+ npx sourcey init
79
82
 
80
83
  # Dev server with hot reload
81
84
  sourcey dev
85
+
86
+ # Build static HTML to dist/
87
+ sourcey build
88
+
89
+ # Quick build from a single OpenAPI spec
90
+ sourcey build api.yaml -o dist/
82
91
  ```
83
92
 
93
+ In CI, the [`sourcey/build-docs`](https://github.com/sourcey/build-docs) GitHub Action runs the build and deploys to GitHub Pages; see [deploying](https://sourcey.com/docs/deploying).
94
+
84
95
  ## Configuration
85
96
 
86
97
  Create `sourcey.config.ts` in your project root:
@@ -168,8 +179,8 @@ cd sourcey && npm install
168
179
  npm run build && npm test
169
180
  cd go/sourcey-godoc && go test ./... && go vet ./...
170
181
 
171
- # Run the demo site
172
- cd demo && npx tsx ../src/cli.ts dev
182
+ # Run the in-repo docs site against your working copy
183
+ cd docs && npx tsx ../src/cli.ts dev
173
184
  ```
174
185
 
175
186
  ## License
@@ -1,12 +1,12 @@
1
1
  // Scroll tracker — highlights the active sidebar nav link as user scrolls.
2
2
  //
3
3
  // Observes [data-traverse-target] section anchors and toggles .active
4
- // on the matching #nav .nav-link. All visual styling is in sourcey.css;
4
+ // on the matching #nav fragment link. All visual styling is in sourcey.css;
5
5
  // this file only manages the class.
6
6
  (function () {
7
7
  function init() {
8
8
  var navbar = document.getElementById('navbar');
9
- var navLinks = document.querySelectorAll('#nav .nav-link');
9
+ var navLinks = document.querySelectorAll('#nav .nav-link, #nav .nav-group-link');
10
10
  var targets = document.querySelectorAll('[data-traverse-target]');
11
11
 
12
12
  // For prose pages, also track heading elements that TOC links point to
@@ -113,7 +113,7 @@
113
113
  if (tocEl) tocEl.addEventListener('click', function (e) { handleAnchorClick(e, '.toc-item'); });
114
114
 
115
115
  var navEl = document.getElementById('nav');
116
- if (navEl) navEl.addEventListener('click', function (e) { handleAnchorClick(e, '.nav-link'); });
116
+ if (navEl) navEl.addEventListener('click', function (e) { handleAnchorClick(e, '.nav-link, .nav-group-link'); });
117
117
 
118
118
  // Use scroll event for reliable activation at all positions.
119
119
  function onScroll() {
@@ -34,13 +34,13 @@
34
34
  trigger.setAttribute('aria-expanded', isOpen ? 'false' : 'true');
35
35
  });
36
36
 
37
- // Select a language from the dropdown
37
+ // Select a language / example variant from the dropdown
38
38
  document.addEventListener('click', function (e) {
39
39
  var option = e.target.closest('.code-lang-option');
40
40
  if (!option) return;
41
41
 
42
- var container = option.closest('.code-group');
43
- var index = option.getAttribute('data-lang-index');
42
+ var dropdown = option.closest('.code-lang-dropdown');
43
+ var canSync = !!(dropdown && dropdown.hasAttribute('data-lang-sync'));
44
44
  var lang = option.textContent.trim();
45
45
 
46
46
  // Close the menu
@@ -52,17 +52,16 @@
52
52
  }
53
53
 
54
54
  var scrollY = window.scrollY;
55
- activateLang(container, index);
55
+ activateLang(option);
56
56
 
57
- // Sync language across all code-sample groups
58
- if (lang !== selectedLang) {
57
+ // Sync language across code-sample groups only (not example variants).
58
+ if (canSync && lang !== selectedLang) {
59
59
  selectedLang = lang;
60
+ var ownGroup = option.closest('.code-group');
60
61
  document.querySelectorAll('.code-group').forEach(function (group) {
61
- if (group === container || !group.querySelector('.code-lang-dropdown')) return;
62
+ if (group === ownGroup || !group.querySelector('.code-lang-dropdown[data-lang-sync]')) return;
62
63
  group.querySelectorAll('.code-lang-option').forEach(function (opt) {
63
- if (opt.textContent.trim() === lang) {
64
- activateLang(group, opt.getAttribute('data-lang-index'));
65
- }
64
+ if (opt.textContent.trim() === lang) activateLang(opt);
66
65
  });
67
66
  });
68
67
  }
@@ -70,32 +69,51 @@
70
69
  window.scrollTo(0, scrollY);
71
70
  });
72
71
 
73
- function activateLang(container, index) {
74
- // Update dropdown label
75
- var options = container.querySelectorAll('.code-lang-option');
76
- var label = container.querySelector('.code-lang-label');
77
- options.forEach(function (opt) {
78
- var isActive = opt.getAttribute('data-lang-index') === index;
79
- opt.setAttribute('aria-selected', isActive ? 'true' : 'false');
80
- opt.className = opt.className.replace(/(dark:)?text-\[rgb\([^\]]+\)\]/g, '').trim();
81
- if (isActive) {
82
- opt.classList.add('text-[rgb(var(--color-primary))]', 'dark:text-[rgb(var(--color-primary-light))]');
83
- if (label) label.textContent = opt.textContent.trim();
84
- // Update trigger icon to match selected language
85
- var triggerIcon = container.querySelector('.code-lang-trigger .code-lang-icon');
86
- var optIcon = opt.querySelector('.lang-icon');
87
- if (triggerIcon && optIcon) {
88
- triggerIcon.innerHTML = optIcon.outerHTML;
72
+ // The panels a dropdown controls. A header variant dropdown (in the shared
73
+ // card header) drives the matching response status panel; otherwise panels
74
+ // live in the dropdown's own `.code-lang-scope` or `.code-group`.
75
+ function panelScopeForOption(option) {
76
+ var headerWrap = option.closest('[data-response-dropdown]');
77
+ if (headerWrap) {
78
+ var idx = headerWrap.getAttribute('data-response-dropdown');
79
+ var group = headerWrap.closest('.code-group');
80
+ var panel = group && group.querySelector('.response-panel[data-response-panel="' + idx + '"]');
81
+ if (panel) return panel;
82
+ }
83
+ return option.closest('.code-lang-scope') || option.closest('.code-group');
84
+ }
85
+
86
+ function activateLang(option) {
87
+ var dropdown = option.closest('.code-lang-dropdown');
88
+ var scope = panelScopeForOption(option);
89
+ var index = option.getAttribute('data-lang-index');
90
+
91
+ // Update the dropdown label + options (label/trigger live in the dropdown,
92
+ // which for a header variant switcher is separate from the panel scope).
93
+ if (dropdown) {
94
+ var label = dropdown.querySelector('.code-lang-label');
95
+ dropdown.querySelectorAll('.code-lang-option').forEach(function (opt) {
96
+ var isActive = opt.getAttribute('data-lang-index') === index;
97
+ opt.setAttribute('aria-selected', isActive ? 'true' : 'false');
98
+ opt.className = opt.className.replace(/(dark:)?text-\[rgb\([^\]]+\)\]/g, '').trim();
99
+ if (isActive) {
100
+ opt.classList.add('text-[rgb(var(--color-primary))]', 'dark:text-[rgb(var(--color-primary-light))]');
101
+ if (label) label.textContent = opt.textContent.trim();
102
+ var triggerIcon = dropdown.querySelector('.code-lang-trigger .code-lang-icon');
103
+ var optIcon = opt.querySelector('.lang-icon');
104
+ if (triggerIcon && optIcon) triggerIcon.innerHTML = optIcon.outerHTML;
105
+ } else {
106
+ opt.classList.add('text-[rgb(var(--color-stone-600))]', 'dark:text-[rgb(var(--color-stone-400))]');
89
107
  }
90
- } else {
91
- opt.classList.add('text-[rgb(var(--color-stone-600))]', 'dark:text-[rgb(var(--color-stone-400))]');
92
- }
93
- });
108
+ });
109
+ }
94
110
 
95
- // Update panels
96
- container.querySelectorAll('.code-lang-panel').forEach(function (p) {
97
- p.classList.toggle('active', p.getAttribute('data-lang-panel') === index);
98
- });
111
+ // Update panels within the controlled scope.
112
+ if (scope) {
113
+ scope.querySelectorAll('.code-lang-panel').forEach(function (p) {
114
+ p.classList.toggle('active', p.getAttribute('data-lang-panel') === index);
115
+ });
116
+ }
99
117
  }
100
118
 
101
119
  // ── Response Tabs ──────────────────────────────────────────────────
@@ -120,6 +138,11 @@
120
138
  p.classList.toggle('active', p.getAttribute('data-response-panel') === index);
121
139
  });
122
140
 
141
+ // Show only the active status's variant switcher in the header.
142
+ container.querySelectorAll('[data-response-dropdown]').forEach(function (d) {
143
+ d.classList.toggle('hidden', d.getAttribute('data-response-dropdown') !== index);
144
+ });
145
+
123
146
  window.scrollTo(0, scrollY);
124
147
  });
125
148
 
@@ -132,8 +155,11 @@
132
155
  var container = btn.closest('.code-group') || btn.closest('.prose-code-block');
133
156
  if (!container) return;
134
157
 
135
- // Find the active panel's code, or the nearest code element
136
- var activePanel = container.querySelector('.code-lang-panel.active, .response-panel.active');
158
+ // Find the active panel's code, or the nearest code element. With nested
159
+ // panels (a variant switcher inside a response tab), prefer the deepest
160
+ // active panel, which is last in document order.
161
+ var activePanels = container.querySelectorAll('.code-lang-panel.active, .response-panel.active');
162
+ var activePanel = activePanels.length ? activePanels[activePanels.length - 1] : null;
137
163
  var codeEl = activePanel
138
164
  ? activePanel.querySelector('code, .code-block, .font-mono')
139
165
  : container.querySelector('code, .code-block, .font-mono');
@@ -1 +1 @@
1
- {"version":3,"file":"Sidebar.d.ts","sourceRoot":"","sources":["../../../src/components/layout/Sidebar.tsx"],"names":[],"mappings":"AA+FA;;GAEG;AACH,wBAAgB,OAAO,wCA2GtB"}
1
+ {"version":3,"file":"Sidebar.d.ts","sourceRoot":"","sources":["../../../src/components/layout/Sidebar.tsx"],"names":[],"mappings":"AAsGA;;GAEG;AACH,wBAAgB,OAAO,wCA2GtB"}
@@ -32,7 +32,7 @@ function MethodPill({ method }) {
32
32
  * Shared nav group rendering used by both desktop sidebar and mobile drawer.
33
33
  */
34
34
  function NavGroups({ groups, activePageSlug, base }) {
35
- return (_jsx(_Fragment, { children: groups.map((group, gi) => (_jsxs("div", { class: gi > 0 ? "mt-6 lg:mt-8" : "", children: [group.label && (_jsx("h5", { class: "nav-group-label", children: group.label })), _jsx("ul", { class: "space-y-px", children: group.items.map((item) => {
35
+ return (_jsx(_Fragment, { children: groups.map((group, gi) => (_jsxs("div", { class: gi > 0 ? "mt-6 lg:mt-8" : "", children: [group.label && (group.href ? (_jsx("a", { href: `${base}${group.href}`, class: `nav-group-label nav-group-link${group.id === activePageSlug ? " active" : ""}`, children: group.label })) : (_jsx("h5", { class: "nav-group-label", children: group.label }))), _jsx("ul", { class: "space-y-px", children: group.items.map((item) => {
36
36
  const isActive = item.id === activePageSlug;
37
37
  return (_jsx("li", { children: _jsxs("a", { href: `${base}${item.href}`, class: `nav-link${isActive ? " active" : ""}`, children: [item.method && _jsx(MethodPill, { method: item.method }), _jsx("span", { class: "flex-1 break-words [word-break:break-word]", children: item.label })] }) }, item.id));
38
38
  }) })] }, group.label))) }));
@@ -1 +1 @@
1
- {"version":3,"file":"CodeSamples.d.ts","sourceRoot":"","sources":["../../../src/components/openapi/CodeSamples.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,qBAAqB,CAAC;AAM/D,UAAU,gBAAgB;IACxB,SAAS,EAAE,mBAAmB,CAAC;IAC/B,SAAS,EAAE,MAAM,CAAC;IAClB,eAAe,CAAC,EAAE,MAAM,EAAE,CAAC;CAC5B;AAED;;;GAGG;AACH,wBAAgB,mBAAmB,CAAC,EAAE,SAAS,EAAE,SAAS,EAAE,eAAe,EAAE,EAAE,gBAAgB,uCAuE9F"}
1
+ {"version":3,"file":"CodeSamples.d.ts","sourceRoot":"","sources":["../../../src/components/openapi/CodeSamples.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,qBAAqB,CAAC;AAM/D,UAAU,gBAAgB;IACxB,SAAS,EAAE,mBAAmB,CAAC;IAC/B,SAAS,EAAE,MAAM,CAAC;IAClB,eAAe,CAAC,EAAE,MAAM,EAAE,CAAC;CAC5B;AAED;;;GAGG;AACH,wBAAgB,mBAAmB,CAAC,EAAE,SAAS,EAAE,SAAS,EAAE,eAAe,EAAE,EAAE,gBAAgB,uCAyE9F"}
@@ -12,8 +12,8 @@ export function CodeSamplesExamples({ operation, serverUrl, codeSampleLangs }) {
12
12
  if (!samples.length)
13
13
  return null;
14
14
  const title = operation.summary ?? `${operation.method.toUpperCase()} ${operation.path}`;
15
- return (_jsxs("div", { class: "code-group not-prose", children: [_jsxs("div", { class: "relative flex items-center justify-between gap-2 px-3", children: [_jsx("div", { class: "flex min-w-0 items-center gap-1.5 font-medium text-xs leading-6 my-1 mb-1.5", children: _jsx("span", { class: "truncate text-[rgb(var(--color-stone-950))] dark:text-[rgb(var(--color-stone-50))]", children: title }) }), _jsxs("div", { class: "flex shrink-0 items-center justify-end gap-1.5", children: [_jsxs("div", { class: "code-lang-dropdown relative", children: [_jsx("button", { type: "button", class: "code-lang-trigger group relative my-1 mb-1.5 flex items-center whitespace-nowrap font-medium leading-6 outline-0 text-[rgb(var(--color-stone-500))] dark:text-[rgb(var(--color-stone-400))] cursor-pointer text-xs", "aria-expanded": "false", "aria-haspopup": "listbox", children: _jsxs("div", { class: "z-10 flex items-center gap-1 rounded-lg px-1.5 group-hover:bg-[rgb(var(--color-stone-200)/0.5)] group-hover:text-[rgb(var(--color-primary))] dark:group-hover:bg-[rgb(var(--color-stone-700)/0.7)] dark:group-hover:text-[rgb(var(--color-primary-light))]", children: [_jsx("span", { class: "code-lang-icon", dangerouslySetInnerHTML: { __html: langIcon(samples[0].lang) } }), _jsx("span", { class: "code-lang-label truncate", children: samples[0].label ?? samples[0].lang }), _jsxs("svg", { xmlns: "http://www.w3.org/2000/svg", width: "14", height: "14", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", class: "size-3.5 shrink-0", children: [_jsx("path", { d: "m7 15 5 5 5-5" }), _jsx("path", { d: "m7 9 5-5 5 5" })] })] }) }), _jsx("div", { class: "code-lang-menu hidden absolute right-0 top-full mt-1 z-20 min-w-[120px] rounded-lg border border-[rgb(var(--color-stone-200))] dark:border-[rgb(255_255_255/0.1)] bg-[rgb(var(--color-background-light))] dark:bg-[rgb(var(--color-stone-900))] shadow-lg py-1", role: "listbox", children: samples.map((sample, i) => (_jsxs("button", { role: "option", "aria-selected": i === 0 ? "true" : "false", class: `code-lang-option w-full text-left px-3 py-1.5 text-xs font-medium flex items-center gap-1.5 hover:bg-[rgb(var(--color-stone-100))] dark:hover:bg-[rgb(255_255_255/0.05)] ${i === 0 ? "text-[rgb(var(--color-primary))] dark:text-[rgb(var(--color-primary-light))]" : "text-[rgb(var(--color-stone-600))] dark:text-[rgb(var(--color-stone-400))]"}`, "data-lang-index": String(i), children: [_jsx("span", { dangerouslySetInnerHTML: { __html: langIcon(sample.lang) } }), sample.label ?? sample.lang] }, i))) })] }), _jsx(CopyButton, {})] })] }), samples.map((sample, i) => {
16
- const html = highlightCode(sample.source, sample.lang);
17
- return (_jsx("div", { class: `code-lang-panel${i === 0 ? " active" : ""}`, "data-lang-panel": String(i), children: _jsx("div", { class: "relative w-full px-4 py-3.5 text-sm leading-6 bg-[rgb(var(--color-code-block-light))] dark:bg-[rgb(var(--color-code-block-dark))] overflow-x-auto", style: "font-variant-ligatures: none", children: _jsx("div", { class: "font-mono whitespace-pre text-xs leading-[1.35rem] code-block", dangerouslySetInnerHTML: { __html: html } }) }) }, i));
18
- })] }));
15
+ return (_jsxs("div", { class: "code-group not-prose", children: [_jsxs("div", { class: "relative flex items-center justify-between gap-2 px-3", children: [_jsx("div", { class: "flex min-w-0 items-center gap-1.5 font-medium text-xs leading-6 my-1 mb-1.5", children: _jsx("span", { class: "truncate text-[rgb(var(--color-stone-950))] dark:text-[rgb(var(--color-stone-50))]", children: title }) }), _jsxs("div", { class: "flex shrink-0 items-center justify-end gap-1.5", children: [_jsxs("div", { class: "code-lang-dropdown relative", "data-lang-sync": true, children: [_jsx("button", { type: "button", class: "code-lang-trigger group relative my-1 mb-1.5 flex items-center whitespace-nowrap font-medium leading-6 outline-0 text-[rgb(var(--color-stone-500))] dark:text-[rgb(var(--color-stone-400))] cursor-pointer text-xs", "aria-expanded": "false", "aria-haspopup": "listbox", children: _jsxs("div", { class: "z-10 flex items-center gap-1 rounded-lg px-1.5 group-hover:bg-[rgb(var(--color-stone-200)/0.5)] group-hover:text-[rgb(var(--color-primary))] dark:group-hover:bg-[rgb(var(--color-stone-700)/0.7)] dark:group-hover:text-[rgb(var(--color-primary-light))]", children: [_jsx("span", { class: "code-lang-icon", dangerouslySetInnerHTML: { __html: langIcon(samples[0].lang) } }), _jsx("span", { class: "code-lang-label truncate", children: samples[0].label ?? samples[0].lang }), _jsxs("svg", { xmlns: "http://www.w3.org/2000/svg", width: "14", height: "14", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", class: "size-3.5 shrink-0", children: [_jsx("path", { d: "m7 15 5 5 5-5" }), _jsx("path", { d: "m7 9 5-5 5 5" })] })] }) }), _jsx("div", { class: "code-lang-menu hidden absolute right-0 top-full mt-1 z-20 min-w-[120px] rounded-lg border border-[rgb(var(--color-stone-200))] dark:border-[rgb(255_255_255/0.1)] bg-[rgb(var(--color-background-light))] dark:bg-[rgb(var(--color-stone-900))] shadow-lg py-1", role: "listbox", children: samples.map((sample, i) => (_jsxs("button", { role: "option", "aria-selected": i === 0 ? "true" : "false", class: `code-lang-option w-full text-left px-3 py-1.5 text-xs font-medium flex items-center gap-1.5 hover:bg-[rgb(var(--color-stone-100))] dark:hover:bg-[rgb(255_255_255/0.05)] ${i === 0 ? "text-[rgb(var(--color-primary))] dark:text-[rgb(var(--color-primary-light))]" : "text-[rgb(var(--color-stone-600))] dark:text-[rgb(var(--color-stone-400))]"}`, "data-lang-index": String(i), children: [_jsx("span", { dangerouslySetInnerHTML: { __html: langIcon(sample.lang) } }), sample.label ?? sample.lang] }, i))) })] }), _jsx(CopyButton, {})] })] }), _jsx("div", { class: "code-card-body", children: samples.map((sample, i) => {
16
+ const html = highlightCode(sample.source, sample.lang);
17
+ return (_jsx("div", { class: `code-lang-panel${i === 0 ? " active" : ""}`, "data-lang-panel": String(i), children: _jsx("div", { class: "relative w-full px-4 py-3.5 text-sm leading-6 bg-[rgb(var(--color-code-block-light))] dark:bg-[rgb(var(--color-code-block-dark))] overflow-x-auto", style: "font-variant-ligatures: none", children: _jsx("div", { class: "font-mono whitespace-pre text-xs leading-[1.35rem] code-block", dangerouslySetInnerHTML: { __html: html } }) }) }, i));
18
+ }) })] }));
19
19
  }
@@ -26,7 +26,7 @@ export function Operation({ operation: op, serverUrl }) {
26
26
  const hasParams = op.parameters.length > 0;
27
27
  const hasBody = !!op.requestBody;
28
28
  const mcp = op.mcpExtras;
29
- return (_jsxs("div", { id: id, class: "py-8 border-t border-[rgb(var(--color-gray-100))] dark:border-[rgb(var(--color-gray-800))]", "data-traverse-target": id, children: [_jsx("header", { class: "mb-6", children: op.summary && (_jsxs("div", { class: "flex items-baseline gap-2 flex-wrap", children: [_jsx("h2", { class: "text-2xl sm:text-3xl text-[rgb(var(--color-gray-900))] dark:text-[rgb(var(--color-gray-200))] tracking-tight font-bold mb-2", children: _jsx(Markdown, { content: op.summary, inline: true }) }), op.deprecated && _jsx(DeprecatedBadge, {}), mcp?.annotations && _jsx(AnnotationBadges, { annotations: mcp.annotations })] })) }), mcp ? (_jsx(McpEndpointBar, { method: op.method, path: op.path })) : (_jsx(EndpointBar, { method: op.method, path: op.path, serverUrl: serverUrl })), _jsxs("div", { class: `flex flex-col ${apiFirst ? "lg:flex-row" : "xl:flex-row"} gap-8`, children: [_jsxs("div", { class: "flex-1 min-w-0", children: [op.description && (_jsx(Markdown, { content: op.description, class: "mb-6" })), hasBody && (_jsxs("div", { class: "mt-6", children: [_jsx(SectionLabel, { meta: bodyMediaType(op.requestBody), children: "Body" }), _jsx(RequestBody, { body: op.requestBody })] })), hasParams && (_jsxs("div", { class: "mt-6", children: [_jsx(SectionLabel, { children: "Parameters" }), _jsx(Parameters, { parameters: op.parameters })] })), mcp ? (_jsx(McpReturnsCopy, { schema: mcp.outputSchema })) : (op.responses.length > 0 && (_jsxs("div", { class: "mt-6", children: [_jsx(SectionLabel, { children: "Response" }), _jsx(ResponsesCopy, { responses: op.responses })] }))), !mcp && _jsx(SecurityCopy, { security: op.security })] }), _jsxs("aside", { class: `hidden ${apiFirst ? "lg:block" : "xl:block"} w-[28rem] shrink-0 sticky self-start overflow-y-auto space-y-4`, style: "top: calc(var(--header-height) + 2.5rem); max-height: calc(100vh - var(--header-height) - 5rem)", children: [_jsx(CodeSamplesExamples, { operation: op, serverUrl: serverUrl, codeSampleLangs: site.codeSamples }), hasBody && _jsx(RequestBodyExample, { body: op.requestBody }), mcp ? (_jsx(McpReturnsExample, { schema: mcp.outputSchema })) : (_jsx(ResponsesExamples, { responses: op.responses }))] })] }), _jsxs("div", { class: `${apiFirst ? "lg:hidden" : "xl:hidden"} mt-8 space-y-4`, children: [_jsx(CodeSamplesExamples, { operation: op, serverUrl: serverUrl, codeSampleLangs: site.codeSamples }), hasBody && _jsx(RequestBodyExample, { body: op.requestBody }), mcp ? (_jsx(McpReturnsExample, { schema: mcp.outputSchema })) : (_jsx(ResponsesExamples, { responses: op.responses }))] })] }));
29
+ return (_jsxs("div", { id: id, class: "py-8 border-t border-[rgb(var(--color-gray-100))] dark:border-[rgb(var(--color-gray-800))]", "data-traverse-target": id, children: [_jsx("header", { class: "mb-6", children: op.summary && (_jsxs("div", { class: "flex items-baseline gap-2 flex-wrap", children: [_jsx("h2", { class: "text-2xl sm:text-3xl text-[rgb(var(--color-gray-900))] dark:text-[rgb(var(--color-gray-200))] tracking-tight font-bold mb-2", children: _jsx(Markdown, { content: op.summary, inline: true }) }), op.deprecated && _jsx(DeprecatedBadge, {}), mcp?.annotations && _jsx(AnnotationBadges, { annotations: mcp.annotations })] })) }), mcp ? (_jsx(McpEndpointBar, { method: op.method, path: op.path })) : (_jsx(EndpointBar, { method: op.method, path: op.path, serverUrl: serverUrl })), _jsxs("div", { class: `flex flex-col ${apiFirst ? "lg:flex-row" : "xl:flex-row"} gap-8`, children: [_jsxs("div", { class: "flex-1 min-w-0", children: [op.description && (_jsx(Markdown, { content: op.description, class: "mb-6 max-w-none" })), hasBody && (_jsxs("div", { class: "mt-6", children: [_jsx(SectionLabel, { meta: bodyMediaType(op.requestBody), children: "Body" }), _jsx(RequestBody, { body: op.requestBody })] })), hasParams && (_jsxs("div", { class: "mt-6", children: [_jsx(SectionLabel, { children: "Parameters" }), _jsx(Parameters, { parameters: op.parameters })] })), mcp ? (_jsx(McpReturnsCopy, { schema: mcp.outputSchema })) : (op.responses.length > 0 && (_jsxs("div", { class: "mt-6", children: [_jsx(SectionLabel, { children: "Response" }), _jsx(ResponsesCopy, { responses: op.responses })] }))), !mcp && _jsx(SecurityCopy, { security: op.security })] }), _jsxs("aside", { class: `hidden ${apiFirst ? "lg:block" : "xl:block"} w-[28rem] shrink-0 sticky self-start overflow-y-auto space-y-4`, style: "top: calc(var(--header-height) + 2.5rem); max-height: calc(100vh - var(--header-height) - 5rem)", children: [_jsx(CodeSamplesExamples, { operation: op, serverUrl: serverUrl, codeSampleLangs: site.codeSamples }), hasBody && _jsx(RequestBodyExample, { body: op.requestBody }), mcp ? (_jsx(McpReturnsExample, { schema: mcp.outputSchema })) : (_jsx(ResponsesExamples, { responses: op.responses }))] })] }), _jsxs("div", { class: `${apiFirst ? "lg:hidden" : "xl:hidden"} mt-8 space-y-4`, children: [_jsx(CodeSamplesExamples, { operation: op, serverUrl: serverUrl, codeSampleLangs: site.codeSamples }), hasBody && _jsx(RequestBodyExample, { body: op.requestBody }), mcp ? (_jsx(McpReturnsExample, { schema: mcp.outputSchema })) : (_jsx(ResponsesExamples, { responses: op.responses }))] })] }));
30
30
  }
31
31
  function bodyMediaType(body) {
32
32
  const types = Object.keys(body.content);
@@ -1 +1 @@
1
- {"version":3,"file":"RequestBody.d.ts","sourceRoot":"","sources":["../../../src/components/openapi/RequestBody.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,qBAAqB,CAAC;AAMjE,UAAU,gBAAgB;IACxB,IAAI,EAAE,qBAAqB,CAAC;CAC7B;AAED;;GAEG;AACH,wBAAgB,WAAW,CAAC,EAAE,IAAI,EAAE,EAAE,gBAAgB,uCA4BrD;AAED,UAAU,uBAAuB;IAC/B,IAAI,EAAE,qBAAqB,CAAC;CAC7B;AAED;;GAEG;AACH,wBAAgB,kBAAkB,CAAC,EAAE,IAAI,EAAE,EAAE,uBAAuB,uCAQnE"}
1
+ {"version":3,"file":"RequestBody.d.ts","sourceRoot":"","sources":["../../../src/components/openapi/RequestBody.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,qBAAqB,CAAC;AAOjE,UAAU,gBAAgB;IACxB,IAAI,EAAE,qBAAqB,CAAC;CAC7B;AAED;;GAEG;AACH,wBAAgB,WAAW,CAAC,EAAE,IAAI,EAAE,EAAE,gBAAgB,uCA4BrD;AAED,UAAU,uBAAuB;IAC/B,IAAI,EAAE,qBAAqB,CAAC;CAC7B;AAED;;GAEG;AACH,wBAAgB,kBAAkB,CAAC,EAAE,IAAI,EAAE,EAAE,uBAAuB,uCAKnE"}
@@ -1,8 +1,9 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "preact/jsx-runtime";
2
2
  import { SchemaView } from "../schema/SchemaView.js";
3
- import { ExampleView } from "../schema/ExampleView.js";
3
+ import { ExampleVariantsCard } from "../schema/ExampleView.js";
4
4
  import { Markdown } from "../ui/Markdown.js";
5
5
  import { MediaTypeDetails } from "./MediaTypeDetails.js";
6
+ import { buildExampleVariants } from "../../utils/example-variants.js";
6
7
  /**
7
8
  * Request body content (rendered in the main content column).
8
9
  */
@@ -19,11 +20,8 @@ export function RequestBody({ body }) {
19
20
  * Request body example (rendered in the sticky code panel).
20
21
  */
21
22
  export function RequestBodyExample({ body }) {
22
- const mediaTypes = Object.entries(body.content);
23
- if (!mediaTypes.length)
24
- return null;
25
- const [, content] = mediaTypes[0];
26
- if (!content.schema)
23
+ const variants = buildExampleVariants(body.content);
24
+ if (!variants.length)
27
25
  return null;
28
- return _jsx(ExampleView, { schema: content.schema, title: "Request Body" });
26
+ return _jsx(ExampleVariantsCard, { variants: variants, title: "Request Body" });
29
27
  }
@@ -1 +1 @@
1
- {"version":3,"file":"Responses.d.ts","sourceRoot":"","sources":["../../../src/components/openapi/Responses.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,qBAAqB,CAAC;AAS9D,UAAU,cAAc;IACtB,SAAS,EAAE,kBAAkB,EAAE,CAAC;CACjC;AAUD;;GAEG;AACH,wBAAgB,aAAa,CAAC,EAAE,SAAS,EAAE,EAAE,cAAc,uCAoC1D;AAED;;;GAGG;AACH,wBAAgB,iBAAiB,CAAC,EAAE,SAAS,EAAE,EAAE,cAAc,uCAiE9D"}
1
+ {"version":3,"file":"Responses.d.ts","sourceRoot":"","sources":["../../../src/components/openapi/Responses.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,qBAAqB,CAAC;AAQ9D,UAAU,cAAc;IACtB,SAAS,EAAE,kBAAkB,EAAE,CAAC;CACjC;AAUD;;GAEG;AACH,wBAAgB,aAAa,CAAC,EAAE,SAAS,EAAE,EAAE,cAAc,uCAoC1D;AAED;;;GAGG;AACH,wBAAgB,iBAAiB,CAAC,EAAE,SAAS,EAAE,EAAE,cAAc,uCAkF9D"}
@@ -1,11 +1,10 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "preact/jsx-runtime";
2
2
  import { httpStatusText } from "../../utils/http.js";
3
3
  import { SchemaDatatype } from "../schema/SchemaDatatype.js";
4
- import { ExampleView } from "../schema/ExampleView.js";
4
+ import { ExampleVariantsCard, VariantBody, VariantDropdown } from "../schema/ExampleView.js";
5
5
  import { Markdown } from "../ui/Markdown.js";
6
6
  import { CopyButton } from "../ui/CopyButton.js";
7
- import { generateExample } from "../../utils/example-generator.js";
8
- import { highlightCode } from "../../utils/highlighter.js";
7
+ import { buildExampleVariants } from "../../utils/example-variants.js";
9
8
  function statusColorClass(code) {
10
9
  if (code.startsWith("2"))
11
10
  return "bg-green-100 text-green-800 dark:bg-green-400/20 dark:text-green-300";
@@ -32,29 +31,20 @@ export function ResponsesCopy({ responses }) {
32
31
  export function ResponsesExamples({ responses }) {
33
32
  if (!responses.length)
34
33
  return null;
35
- const examples = responses
36
- .map((r) => {
37
- const schema = getResponseSchema(r);
38
- if (!schema)
39
- return null;
40
- const example = schema.example ?? generateExample(schema);
41
- if (example === undefined)
42
- return null;
43
- const json = JSON.stringify(example, null, 2);
44
- const html = highlightCode(json, "json");
45
- return { statusCode: r.statusCode, html };
46
- })
47
- .filter(Boolean);
48
- if (!examples.length)
34
+ const groups = responses
35
+ .map((r) => ({ statusCode: r.statusCode, variants: buildExampleVariants(r.content) }))
36
+ .filter((g) => g.variants.length > 0);
37
+ if (!groups.length)
49
38
  return null;
50
- // Single response: use plain ExampleView
51
- if (examples.length === 1) {
52
- const r = responses.find((r) => r.statusCode === examples[0].statusCode);
53
- const schema = getResponseSchema(r);
54
- return _jsx(ExampleView, { schema: schema, title: `${examples[0].statusCode}` });
39
+ // Single status code: a standalone card, with a variant switcher when the
40
+ // response carries more than one example (named examples / content types).
41
+ if (groups.length === 1) {
42
+ return _jsx(ExampleVariantsCard, { variants: groups[0].variants, title: groups[0].statusCode });
55
43
  }
56
- // Multiple responses: tabbed code block with status code tabs in the header
57
- return (_jsxs("div", { class: "response-tabs code-group not-prose", children: [_jsxs("div", { class: "relative flex items-center justify-between gap-2 px-3", children: [_jsx("div", { class: "response-tab-list flex gap-1 overflow-x-auto text-xs leading-6", role: "tablist", children: examples.map((ex, i) => (_jsx("button", { type: "button", role: "tab", "aria-selected": i === 0 ? "true" : "false", class: `response-tab group relative my-1 mb-1.5 flex items-center gap-1.5 whitespace-nowrap font-medium outline-0${i === 0 ? " active" : ""}`, "data-response-index": String(i), children: _jsx("div", { class: "z-10 flex items-center gap-1.5 rounded-lg px-1.5 group-hover:bg-[rgb(var(--color-stone-200)/0.5)] group-hover:text-[rgb(var(--color-primary))] dark:group-hover:bg-[rgb(var(--color-stone-700)/0.7)] dark:group-hover:text-[rgb(var(--color-primary-light))]", children: ex.statusCode }) }, ex.statusCode))) }), _jsx("div", { class: "flex shrink-0 items-center justify-end gap-1.5", children: _jsx(CopyButton, {}) })] }), examples.map((ex, i) => (_jsx("div", { class: `response-panel${i === 0 ? " active" : ""}`, role: "tabpanel", "data-response-panel": String(i), children: _jsx("div", { class: "relative w-full px-4 py-3.5 text-sm leading-6 bg-[rgb(var(--color-code-block-light))] dark:bg-[rgb(var(--color-code-block-dark))] overflow-x-auto", style: "font-variant-ligatures: none", children: _jsx("div", { class: "font-mono whitespace-pre text-xs leading-[1.35rem]", dangerouslySetInnerHTML: { __html: ex.html } }) }) }, ex.statusCode)))] }));
44
+ // Multiple status codes: status tabs on the left of the header; on the right,
45
+ // a per-status variant switcher (shown for the active status) next to copy,
46
+ // matching the language select. Panels live in the clipped card body.
47
+ return (_jsxs("div", { class: "response-tabs code-group not-prose", children: [_jsxs("div", { class: "relative flex items-center justify-between gap-2 px-3", children: [_jsx("div", { class: "response-tab-list flex gap-1 overflow-x-auto text-xs leading-6", role: "tablist", children: groups.map((g, i) => (_jsx("button", { type: "button", role: "tab", "aria-selected": i === 0 ? "true" : "false", class: `response-tab group relative my-1 mb-1.5 flex items-center gap-1.5 whitespace-nowrap font-medium outline-0${i === 0 ? " active" : ""}`, "data-response-index": String(i), children: _jsx("div", { class: "z-10 flex items-center gap-1.5 rounded-lg px-1.5 group-hover:bg-[rgb(var(--color-stone-200)/0.5)] group-hover:text-[rgb(var(--color-primary))] dark:group-hover:bg-[rgb(var(--color-stone-700)/0.7)] dark:group-hover:text-[rgb(var(--color-primary-light))]", children: g.statusCode }) }, g.statusCode))) }), _jsxs("div", { class: "flex shrink-0 items-center justify-end gap-1.5", children: [groups.map((g, i) => g.variants.length > 1 ? (_jsx("div", { "data-response-dropdown": String(i), class: i === 0 ? undefined : "hidden", children: _jsx(VariantDropdown, { variants: g.variants }) }, g.statusCode)) : null), _jsx(CopyButton, {})] })] }), _jsx("div", { class: "code-card-body", children: groups.map((g, i) => (_jsx("div", { class: `response-panel${i === 0 ? " active" : ""}`, role: "tabpanel", "data-response-panel": String(i), children: g.variants.length > 1 ? (g.variants.map((v, j) => (_jsx("div", { class: `code-lang-panel${j === 0 ? " active" : ""}`, "data-lang-panel": String(j), children: _jsx(VariantBody, { variant: v }) }, j)))) : (_jsx(VariantBody, { variant: g.variants[0] })) }, g.statusCode))) })] }));
58
48
  }
59
49
  function renderResponseType(r) {
60
50
  const schema = getResponseSchema(r);
@@ -1,10 +1,33 @@
1
1
  import type { NormalizedSchema } from "../../core/types.js";
2
+ import type { ExampleVariant } from "../../utils/example-variants.js";
3
+ /** Render one example body (highlighted JSON or an external-example link). */
4
+ export declare function VariantBody({ variant, showCopy }: {
5
+ variant: ExampleVariant;
6
+ showCopy?: boolean;
7
+ }): import("preact").JSX.Element;
8
+ /**
9
+ * Variant switcher reusing the code-sample language dropdown markup/JS, minus
10
+ * the cross-group language sync (no `data-lang-sync`). Sits in the card header
11
+ * to the left of the copy button, identical to the language select.
12
+ */
13
+ export declare function VariantDropdown({ variants }: {
14
+ variants: ExampleVariant[];
15
+ }): import("preact").JSX.Element;
16
+ interface ExampleVariantsCardProps {
17
+ variants: ExampleVariant[];
18
+ title?: string;
19
+ }
20
+ /**
21
+ * Standalone example card: a `.code-group` with an optional title, a variant
22
+ * switcher when more than one example exists, and a copy button.
23
+ */
24
+ export declare function ExampleVariantsCard({ variants, title }: ExampleVariantsCardProps): import("preact").JSX.Element | null;
2
25
  interface ExampleViewProps {
3
26
  schema: NormalizedSchema;
4
27
  title?: string;
5
28
  }
6
29
  /**
7
- * Renders a JSON example in a stone-themed code card with copy button.
30
+ * Renders a schema-derived JSON example in a stone-themed code card.
8
31
  */
9
32
  export declare function ExampleView({ schema, title }: ExampleViewProps): import("preact").JSX.Element | null;
10
33
  export {};
@@ -1 +1 @@
1
- {"version":3,"file":"ExampleView.d.ts","sourceRoot":"","sources":["../../../src/components/schema/ExampleView.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,qBAAqB,CAAC;AAK5D,UAAU,gBAAgB;IACxB,MAAM,EAAE,gBAAgB,CAAC;IACzB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED;;GAEG;AACH,wBAAgB,WAAW,CAAC,EAAE,MAAM,EAAE,KAAK,EAAE,EAAE,gBAAgB,uCA8B9D"}
1
+ {"version":3,"file":"ExampleView.d.ts","sourceRoot":"","sources":["../../../src/components/schema/ExampleView.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,qBAAqB,CAAC;AAC5D,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,iCAAiC,CAAC;AAQtE,8EAA8E;AAC9E,wBAAgB,WAAW,CAAC,EAAE,OAAO,EAAE,QAAQ,EAAE,EAAE;IAAE,OAAO,EAAE,cAAc,CAAC;IAAC,QAAQ,CAAC,EAAE,OAAO,CAAA;CAAE,gCA8BjG;AAED;;;;GAIG;AACH,wBAAgB,eAAe,CAAC,EAAE,QAAQ,EAAE,EAAE;IAAE,QAAQ,EAAE,cAAc,EAAE,CAAA;CAAE,gCAgC3E;AAED,UAAU,wBAAwB;IAChC,QAAQ,EAAE,cAAc,EAAE,CAAC;IAC3B,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED;;;GAGG;AACH,wBAAgB,mBAAmB,CAAC,EAAE,QAAQ,EAAE,KAAK,EAAE,EAAE,wBAAwB,uCAsChF;AAED,UAAU,gBAAgB;IACxB,MAAM,EAAE,gBAAgB,CAAC;IACzB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED;;GAEG;AACH,wBAAgB,WAAW,CAAC,EAAE,MAAM,EAAE,KAAK,EAAE,EAAE,gBAAgB,uCAI9D"}
@@ -1,15 +1,36 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "preact/jsx-runtime";
2
- import { generateExample } from "../../utils/example-generator.js";
3
- import { highlightCode } from "../../utils/highlighter.js";
2
+ import { variantFromSchema } from "../../utils/example-variants.js";
4
3
  import { CopyButton } from "../ui/CopyButton.js";
4
+ const BODY_WRAP = "relative w-full px-4 py-3.5 text-sm leading-6 bg-[rgb(var(--color-code-block-light))] dark:bg-[rgb(var(--color-code-block-dark))] overflow-x-auto";
5
+ const BODY_CODE = "font-mono whitespace-pre text-xs leading-[1.35rem]";
6
+ /** Render one example body (highlighted JSON or an external-example link). */
7
+ export function VariantBody({ variant, showCopy }) {
8
+ return (_jsxs("div", { class: BODY_WRAP, style: "font-variant-ligatures: none", children: [showCopy && (_jsx("div", { class: "absolute top-3 right-4 z-10", children: _jsx(CopyButton, {}) })), variant.summary && (_jsx("p", { class: "mb-2 text-xs text-[rgb(var(--color-gray-500))] dark:text-[rgb(var(--color-gray-400))]", children: variant.summary })), variant.externalValue ? (_jsxs("p", { class: "text-xs", children: [_jsx("span", { class: "text-[rgb(var(--color-gray-500))]", children: "External example: " }), _jsx("a", { href: variant.externalValue, target: "_blank", rel: "noopener noreferrer", class: "break-all text-[rgb(var(--color-primary))] hover:underline", children: variant.externalValue })] })) : (_jsx("div", { class: BODY_CODE, dangerouslySetInnerHTML: { __html: variant.html } }))] }));
9
+ }
10
+ /**
11
+ * Variant switcher reusing the code-sample language dropdown markup/JS, minus
12
+ * the cross-group language sync (no `data-lang-sync`). Sits in the card header
13
+ * to the left of the copy button, identical to the language select.
14
+ */
15
+ export function VariantDropdown({ variants }) {
16
+ return (_jsxs("div", { class: "code-lang-dropdown relative", children: [_jsx("button", { type: "button", class: "code-lang-trigger group relative my-1 mb-1.5 flex items-center whitespace-nowrap font-medium leading-6 outline-0 text-[rgb(var(--color-stone-500))] dark:text-[rgb(var(--color-stone-400))] cursor-pointer text-xs", "aria-expanded": "false", "aria-haspopup": "listbox", children: _jsxs("div", { class: "z-10 flex items-center gap-1 rounded-lg px-1.5 group-hover:bg-[rgb(var(--color-stone-200)/0.5)] group-hover:text-[rgb(var(--color-primary))] dark:group-hover:bg-[rgb(var(--color-stone-700)/0.7)] dark:group-hover:text-[rgb(var(--color-primary-light))]", children: [_jsx("span", { class: "code-lang-label truncate", children: variants[0].label }), _jsxs("svg", { xmlns: "http://www.w3.org/2000/svg", width: "14", height: "14", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", class: "size-3.5 shrink-0", children: [_jsx("path", { d: "m7 15 5 5 5-5" }), _jsx("path", { d: "m7 9 5-5 5 5" })] })] }) }), _jsx("div", { class: "code-lang-menu hidden absolute right-0 top-full mt-1 z-20 min-w-[140px] rounded-lg border border-[rgb(var(--color-stone-200))] dark:border-[rgb(255_255_255/0.1)] bg-[rgb(var(--color-background-light))] dark:bg-[rgb(var(--color-stone-900))] shadow-lg py-1", role: "listbox", children: variants.map((v, i) => (_jsx("button", { role: "option", "aria-selected": i === 0 ? "true" : "false", class: `code-lang-option w-full text-left px-3 py-1.5 text-xs font-medium flex items-center gap-1.5 hover:bg-[rgb(var(--color-stone-100))] dark:hover:bg-[rgb(255_255_255/0.05)] ${i === 0 ? "text-[rgb(var(--color-primary))] dark:text-[rgb(var(--color-primary-light))]" : "text-[rgb(var(--color-stone-600))] dark:text-[rgb(var(--color-stone-400))]"}`, "data-lang-index": String(i), children: v.label }, i))) })] }));
17
+ }
18
+ /**
19
+ * Standalone example card: a `.code-group` with an optional title, a variant
20
+ * switcher when more than one example exists, and a copy button.
21
+ */
22
+ export function ExampleVariantsCard({ variants, title }) {
23
+ if (!variants.length)
24
+ return null;
25
+ const multi = variants.length > 1;
26
+ return (_jsxs("div", { class: "code-group not-prose", children: [(title || multi) && (_jsxs("div", { class: "relative flex items-center justify-between gap-2 px-3", children: [_jsx("div", { class: "flex min-w-0 items-center gap-1.5 font-medium text-xs leading-6 my-1 mb-1.5", children: title && (_jsx("span", { class: "truncate text-[rgb(var(--color-stone-950))] dark:text-[rgb(var(--color-stone-50))]", children: title })) }), _jsxs("div", { class: "flex shrink-0 items-center justify-end gap-1.5", children: [multi && _jsx(VariantDropdown, { variants: variants }), _jsx(CopyButton, {})] })] })), _jsx("div", { class: "code-card-body", children: multi ? (variants.map((v, i) => (_jsx("div", { class: `code-lang-panel${i === 0 ? " active" : ""}`, "data-lang-panel": String(i), children: _jsx(VariantBody, { variant: v }) }, i)))) : (_jsx(VariantBody, { variant: variants[0], showCopy: !title })) })] }));
27
+ }
5
28
  /**
6
- * Renders a JSON example in a stone-themed code card with copy button.
29
+ * Renders a schema-derived JSON example in a stone-themed code card.
7
30
  */
8
31
  export function ExampleView({ schema, title }) {
9
- const example = schema.example ?? generateExample(schema);
10
- if (example === undefined)
32
+ const variant = variantFromSchema(schema, title ?? "Example");
33
+ if (!variant)
11
34
  return null;
12
- const json = JSON.stringify(example, null, 2);
13
- const html = highlightCode(json, "json");
14
- return (_jsxs("div", { class: "code-group not-prose", children: [title && (_jsxs("div", { class: "relative flex items-center justify-between gap-2 px-3", children: [_jsx("div", { class: "flex min-w-0 items-center gap-1.5 font-medium text-xs leading-6 my-1 mb-1.5", children: _jsx("span", { class: "truncate text-[rgb(var(--color-stone-950))] dark:text-[rgb(var(--color-stone-50))]", children: title }) }), _jsx("div", { class: "flex shrink-0 items-center justify-end gap-1.5", children: _jsx(CopyButton, {}) })] })), _jsxs("div", { class: "relative w-full px-4 py-3.5 text-sm leading-6 bg-[rgb(var(--color-code-block-light))] dark:bg-[rgb(var(--color-code-block-dark))] overflow-x-auto", style: "font-variant-ligatures: none", children: [!title && (_jsx("div", { class: "absolute top-3 right-4 z-10", children: _jsx(CopyButton, {}) })), _jsx("div", { class: "font-mono whitespace-pre text-xs leading-[1.35rem]", dangerouslySetInnerHTML: { __html: html } })] })] }));
35
+ return _jsx(ExampleVariantsCard, { variants: [variant], title: title });
15
36
  }
@@ -17,6 +17,10 @@ export interface SiteTab {
17
17
  }
18
18
  export interface SiteNavGroup {
19
19
  label: string;
20
+ /** Optional section link for generated groups such as OpenAPI tag intros. */
21
+ href?: string;
22
+ /** Unique ID for section-level active-state matching. */
23
+ id?: string;
20
24
  items: SiteNavItem[];
21
25
  }
22
26
  export interface SiteNavItem {
@@ -1 +1 @@
1
- {"version":3,"file":"navigation.d.ts","sourceRoot":"","sources":["../../src/core/navigation.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC;AAGjD,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,cAAc,CAAC;AAChD,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,sBAAsB,CAAC;AAErD,OAAO,EAAgB,KAAK,UAAU,EAAE,MAAM,gBAAgB,CAAC;AAgB/D,MAAM,WAAW,cAAc;IAC7B,IAAI,EAAE,OAAO,EAAE,CAAC;IAChB,aAAa,EAAE,MAAM,CAAC;IACtB,cAAc,EAAE,MAAM,CAAC;CACxB;AAED,MAAM,WAAW,OAAO;IACtB,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,MAAM,CAAC;IACb,6DAA6D;IAC7D,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,GAAG,MAAM,CAAC;IACtB,MAAM,EAAE,YAAY,EAAE,CAAC;CACxB;AAED,MAAM,WAAW,YAAY;IAC3B,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,WAAW,EAAE,CAAC;CACtB;AAED,MAAM,WAAW,WAAW;IAC1B,KAAK,EAAE,MAAM,CAAC;IACd,iDAAiD;IACjD,IAAI,EAAE,MAAM,CAAC;IACb,0CAA0C;IAC1C,EAAE,EAAE,MAAM,CAAC;IACX,kEAAkE;IAClE,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAMD;;;;;GAKG;AACH,wBAAgB,gBAAgB,CAC9B,IAAI,EAAE,cAAc,EACpB,OAAO,EAAE,MAAM,EACf,UAAU,GAAE,UAAkB,GAC7B,OAAO,CAiDT;AAED;;;GAGG;AACH,wBAAgB,iBAAiB,CAC/B,GAAG,EAAE,WAAW,EAChB,WAAW,EAAE,GAAG,CAAC,MAAM,EAAE,QAAQ,CAAC,EAClC,UAAU,GAAE,UAAkB,GAC7B,OAAO,CA8BT;AAED;;;GAGG;AACH,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,OAAO,EAAE,GAAG,cAAc,CASnE;AAED;;GAEG;AACH,wBAAgB,cAAc,CAC5B,GAAG,EAAE,cAAc,EACnB,OAAO,EAAE,MAAM,EACf,QAAQ,EAAE,MAAM,GACf,cAAc,CAEhB"}
1
+ {"version":3,"file":"navigation.d.ts","sourceRoot":"","sources":["../../src/core/navigation.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC;AAGjD,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,cAAc,CAAC;AAChD,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,sBAAsB,CAAC;AAErD,OAAO,EAAgB,KAAK,UAAU,EAAE,MAAM,gBAAgB,CAAC;AAgB/D,MAAM,WAAW,cAAc;IAC7B,IAAI,EAAE,OAAO,EAAE,CAAC;IAChB,aAAa,EAAE,MAAM,CAAC;IACtB,cAAc,EAAE,MAAM,CAAC;CACxB;AAED,MAAM,WAAW,OAAO;IACtB,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,MAAM,CAAC;IACb,6DAA6D;IAC7D,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,GAAG,MAAM,CAAC;IACtB,MAAM,EAAE,YAAY,EAAE,CAAC;CACxB;AAED,MAAM,WAAW,YAAY;IAC3B,KAAK,EAAE,MAAM,CAAC;IACd,6EAA6E;IAC7E,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,yDAAyD;IACzD,EAAE,CAAC,EAAE,MAAM,CAAC;IACZ,KAAK,EAAE,WAAW,EAAE,CAAC;CACtB;AAED,MAAM,WAAW,WAAW;IAC1B,KAAK,EAAE,MAAM,CAAC;IACd,iDAAiD;IACjD,IAAI,EAAE,MAAM,CAAC;IACb,0CAA0C;IAC1C,EAAE,EAAE,MAAM,CAAC;IACX,kEAAkE;IAClE,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAMD;;;;;GAKG;AACH,wBAAgB,gBAAgB,CAC9B,IAAI,EAAE,cAAc,EACpB,OAAO,EAAE,MAAM,EACf,UAAU,GAAE,UAAkB,GAC7B,OAAO,CAuDT;AAED;;;GAGG;AACH,wBAAgB,iBAAiB,CAC/B,GAAG,EAAE,WAAW,EAChB,WAAW,EAAE,GAAG,CAAC,MAAM,EAAE,QAAQ,CAAC,EAClC,UAAU,GAAE,UAAkB,GAC7B,OAAO,CA8BT;AAED;;;GAGG;AACH,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,OAAO,EAAE,GAAG,cAAc,CASnE;AAED;;GAEG;AACH,wBAAgB,cAAc,CAC5B,GAAG,EAAE,cAAc,EACnB,OAAO,EAAE,MAAM,EACf,QAAQ,EAAE,MAAM,GACf,cAAc,CAEhB"}
@@ -40,6 +40,7 @@ export function buildNavFromSpec(spec, tabSlug, prettyUrls = false) {
40
40
  for (const tag of spec.tags) {
41
41
  if (tag.hidden)
42
42
  continue;
43
+ const tagId = `tag-${htmlId(tag.name)}`;
43
44
  const items = tag.operations.map((op) => ({
44
45
  label: op.summary ?? `${op.method.toUpperCase()} ${op.path}`,
45
46
  href: `${basePath}#operation-${htmlId(op.path)}-${htmlId(op.method)}`,
@@ -48,7 +49,12 @@ export function buildNavFromSpec(spec, tabSlug, prettyUrls = false) {
48
49
  }));
49
50
  if (!items.length)
50
51
  continue;
51
- groups.push({ label: tagNavigationLabel(tag, tagsByName), items });
52
+ groups.push({
53
+ label: tagNavigationLabel(tag, tagsByName),
54
+ href: `${basePath}#${tagId}`,
55
+ id: tagId,
56
+ items,
57
+ });
52
58
  }
53
59
  // Models group
54
60
  const schemaNames = Object.keys(spec.schemas);
@@ -66,15 +66,15 @@ checksum = "7f202df86484c868dbad7eaa557ef785d5c66295e41b460ef922eca0723b842c"
66
66
 
67
67
  [[package]]
68
68
  name = "bitflags"
69
- version = "2.11.1"
69
+ version = "2.13.0"
70
70
  source = "registry+https://github.com/rust-lang/crates.io-index"
71
- checksum = "c4512299f36f043ab09a583e57bceb5a5aab7a73db1805848e8fef3c9e8c78b3"
71
+ checksum = "b4388bee8683e3d04af747c73422af53102d2bd24d9eadb6cbc100baef4b43f8"
72
72
 
73
73
  [[package]]
74
74
  name = "camino"
75
- version = "1.2.2"
75
+ version = "1.2.3"
76
76
  source = "registry+https://github.com/rust-lang/crates.io-index"
77
- checksum = "e629a66d692cb9ff1a1c664e41771b3dcaf961985a9774c0eb0bd1b51cf60a48"
77
+ checksum = "b4ce8d3bd5823c7504d3f579f13e7b2f3da252fcb938c594d5680ee508bf846f"
78
78
  dependencies = [
79
79
  "serde_core",
80
80
  ]
@@ -180,9 +180,6 @@ name = "deranged"
180
180
  version = "0.5.8"
181
181
  source = "registry+https://github.com/rust-lang/crates.io-index"
182
182
  checksum = "7cd812cc2bc1d69d4764bd80df88b4317eaef9e773c75226407d9bc0876b211c"
183
- dependencies = [
184
- "powerfmt",
185
- ]
186
183
 
187
184
  [[package]]
188
185
  name = "equivalent"
@@ -245,9 +242,9 @@ checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682"
245
242
 
246
243
  [[package]]
247
244
  name = "memchr"
248
- version = "2.8.1"
245
+ version = "2.8.2"
249
246
  source = "registry+https://github.com/rust-lang/crates.io-index"
250
- checksum = "6b947ae49db0d222b1dbc6b113ce7248a3fc3a6ca21b696717bfc000ba4484d8"
247
+ checksum = "88904434abc2901f197fe8cc55f0445e7ded921dba5911dad2e2b39b48e663c4"
251
248
 
252
249
  [[package]]
253
250
  name = "miniz_oxide"
@@ -452,9 +449,9 @@ checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f"
452
449
 
453
450
  [[package]]
454
451
  name = "syn"
455
- version = "2.0.117"
452
+ version = "2.0.118"
456
453
  source = "registry+https://github.com/rust-lang/crates.io-index"
457
- checksum = "e665b8803e7b1d2a727f4023456bbbbe74da67099c585258af0ad9c5013b9b99"
454
+ checksum = "1b9ae57f904213ebb649ce6895b8a66c66f0203b9319718f69a5612a065b1422"
458
455
  dependencies = [
459
456
  "proc-macro2",
460
457
  "quote",
@@ -503,12 +500,11 @@ dependencies = [
503
500
 
504
501
  [[package]]
505
502
  name = "time"
506
- version = "0.3.47"
503
+ version = "0.3.49"
507
504
  source = "registry+https://github.com/rust-lang/crates.io-index"
508
- checksum = "743bd48c283afc0388f9b8827b976905fb217ad9e647fae3a379a9283c4def2c"
505
+ checksum = "711a53c2d47bbd818258c498c8dbfe186a2526c631495cfe7e078567f86b8469"
509
506
  dependencies = [
510
507
  "deranged",
511
- "itoa",
512
508
  "num-conv",
513
509
  "powerfmt",
514
510
  "serde_core",
@@ -518,15 +514,15 @@ dependencies = [
518
514
 
519
515
  [[package]]
520
516
  name = "time-core"
521
- version = "0.1.8"
517
+ version = "0.1.9"
522
518
  source = "registry+https://github.com/rust-lang/crates.io-index"
523
- checksum = "7694e1cfe791f8d31026952abf09c69ca6f6fa4e1a1229e18988f06a04a12dca"
519
+ checksum = "9e1c906769ad99c88eaa54e728060edef082f8e358ff32030cb7c7d315e81109"
524
520
 
525
521
  [[package]]
526
522
  name = "time-macros"
527
- version = "0.2.27"
523
+ version = "0.2.29"
528
524
  source = "registry+https://github.com/rust-lang/crates.io-index"
529
- checksum = "2e70e4c5a0e0a8a4823ad65dfe1a6930e4f4d756dcd9dd7939022b5e8c501215"
525
+ checksum = "71c652a3727a9cbb9a02f707f530b618ce00d0ccd762009c8c23bd191df3c17d"
530
526
  dependencies = [
531
527
  "num-conv",
532
528
  "time-core",
@@ -6,7 +6,7 @@ description = "Convert nightly rustdoc JSON into a stable RustdocSpec snapshot.
6
6
  license = "AGPL-3.0-only"
7
7
  repository = "https://github.com/sourcey/sourcey"
8
8
  homepage = "https://sourcey.com"
9
- documentation = "https://sourcey.com/docs/adapters/rustdoc"
9
+ documentation = "https://sourcey.com/docs/rustdoc-integration"
10
10
  readme = "README.md"
11
11
  keywords = ["rustdoc", "documentation", "json", "rust-api"]
12
12
  categories = ["development-tools", "command-line-utilities"]
@@ -16,7 +16,7 @@ this crate directly. It is published for:
16
16
  - CI tools that drive snapshot generation from Rust
17
17
 
18
18
  If you just want sourcey for your project, see
19
- [sourcey.com/docs/adapters/rustdoc](https://sourcey.com/docs/adapters/rustdoc).
19
+ [sourcey.com/docs/rustdoc-integration](https://sourcey.com/docs/rustdoc-integration).
20
20
 
21
21
  ## CLI
22
22
 
@@ -218,6 +218,7 @@ h1[id], h2[id], h3[id], h4[id], h5[id], h6[id] {
218
218
  /* ── Nav Links ────────────────────────────────────────────────────── */
219
219
 
220
220
  #sourcey .nav-group-label {
221
+ display: block;
221
222
  padding: 0 0.75rem 0.25rem 1rem;
222
223
  margin-bottom: 0.625rem;
223
224
  font-size: inherit;
@@ -227,6 +228,24 @@ h1[id], h2[id], h3[id], h4[id], h5[id], h6[id] {
227
228
  .dark #sourcey .nav-group-label {
228
229
  color: rgb(var(--color-gray-200));
229
230
  }
231
+ #sourcey .nav-group-link {
232
+ cursor: pointer;
233
+ border-radius: 0.75rem;
234
+ text-decoration: none;
235
+ transition: color 0.15s, background-color 0.15s;
236
+ }
237
+ #sourcey .nav-group-link:hover {
238
+ color: rgb(var(--color-primary-ink));
239
+ }
240
+ .dark #sourcey .nav-group-link:hover {
241
+ color: rgb(var(--color-primary-light));
242
+ }
243
+ #sourcey .nav-group-link.active {
244
+ color: rgb(var(--color-primary-ink));
245
+ }
246
+ .dark #sourcey .nav-group-link.active {
247
+ color: rgb(var(--color-primary-light));
248
+ }
230
249
 
231
250
  #sourcey .nav-tab-label {
232
251
  display: block;
@@ -1425,7 +1444,9 @@ h1[id], h2[id], h3[id], h4[id], h5[id], h6[id] {
1425
1444
  position: relative;
1426
1445
  display: flex;
1427
1446
  flex-direction: column;
1428
- overflow: hidden;
1447
+ /* Visible so an open header dropdown menu is not clipped by the card; the
1448
+ code body's rounded corners are clipped by .code-card-body instead. */
1449
+ overflow: visible;
1429
1450
  border-radius: var(--radius);
1430
1451
  border: 1px solid rgb(var(--color-stone-950) / 0.1);
1431
1452
  }
@@ -1435,11 +1456,21 @@ h1[id], h2[id], h3[id], h4[id], h5[id], h6[id] {
1435
1456
 
1436
1457
  #sourcey .code-group > div:first-child {
1437
1458
  background: rgb(var(--color-stone-50));
1459
+ border-top-left-radius: var(--radius);
1460
+ border-top-right-radius: var(--radius);
1438
1461
  }
1439
1462
  .dark #sourcey .code-group > div:first-child {
1440
1463
  background: rgb(255 255 255 / 0.03);
1441
1464
  }
1442
1465
 
1466
+ /* Clips the code panels' rounded bottom corners now that the card itself does
1467
+ not clip. Horizontal code scrolling stays on the inner body element. */
1468
+ #sourcey .code-card-body {
1469
+ overflow: hidden;
1470
+ border-bottom-left-radius: var(--radius);
1471
+ border-bottom-right-radius: var(--radius);
1472
+ }
1473
+
1443
1474
  /* ── Selection ─────────────────────────────────────────────────────── */
1444
1475
 
1445
1476
  #sourcey ::selection { background: rgb(var(--color-primary) / 0.12); }
@@ -0,0 +1,31 @@
1
+ import type { MediaTypeContent, NormalizedSchema } from "../core/types.js";
2
+ /**
3
+ * A single, render-ready example. One media type can contribute several
4
+ * (named `examples`); each becomes its own variant so the UI can offer a
5
+ * switcher between them.
6
+ */
7
+ export interface ExampleVariant {
8
+ /** Label shown in the variant switcher (example name, media type, or "Example"). */
9
+ label: string;
10
+ /** Optional caption from a named example's `summary`/`description`. */
11
+ summary?: string;
12
+ /** Media type the example came from (e.g. `application/json`). */
13
+ mediaType: string;
14
+ /** Syntax-highlighted HTML body, empty when the example is external-only. */
15
+ html: string;
16
+ /** Raw text body (for copy + tests), or the URL for an external example. */
17
+ raw: string;
18
+ /** Set when the example is referenced via `externalValue` rather than inline. */
19
+ externalValue?: string;
20
+ }
21
+ /** Build a single variant from a schema, falling back to a generated example. */
22
+ export declare function variantFromSchema(schema: NormalizedSchema, label?: string, mediaType?: string): ExampleVariant | null;
23
+ /**
24
+ * Collect every render-ready example for a content map (a response, request
25
+ * body, or parameter). Provided examples win over the schema: named `examples`
26
+ * first, then a single `example`, and only then a schema-generated fallback.
27
+ * Every media type contributes, so multi-content-type responses surface them
28
+ * all rather than just the first.
29
+ */
30
+ export declare function buildExampleVariants(content?: Record<string, MediaTypeContent>): ExampleVariant[];
31
+ //# sourceMappingURL=example-variants.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"example-variants.d.ts","sourceRoot":"","sources":["../../src/utils/example-variants.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAiB,gBAAgB,EAAE,gBAAgB,EAAE,MAAM,kBAAkB,CAAC;AAI1F;;;;GAIG;AACH,MAAM,WAAW,cAAc;IAC7B,oFAAoF;IACpF,KAAK,EAAE,MAAM,CAAC;IACd,uEAAuE;IACvE,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,kEAAkE;IAClE,SAAS,EAAE,MAAM,CAAC;IAClB,6EAA6E;IAC7E,IAAI,EAAE,MAAM,CAAC;IACb,4EAA4E;IAC5E,GAAG,EAAE,MAAM,CAAC;IACZ,iFAAiF;IACjF,aAAa,CAAC,EAAE,MAAM,CAAC;CACxB;AA2DD,iFAAiF;AACjF,wBAAgB,iBAAiB,CAC/B,MAAM,EAAE,gBAAgB,EACxB,KAAK,SAAY,EACjB,SAAS,SAAqB,GAC7B,cAAc,GAAG,IAAI,CAIvB;AAED;;;;;;GAMG;AACH,wBAAgB,oBAAoB,CAClC,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,gBAAgB,CAAC,GACzC,cAAc,EAAE,CAiClB"}
@@ -0,0 +1,99 @@
1
+ import { generateExample } from "./example-generator.js";
2
+ import { highlightCode } from "./highlighter.js";
3
+ /** Map a media type to a highlighter language, defaulting to JSON. */
4
+ function langForMediaType(mediaType) {
5
+ const m = mediaType.toLowerCase();
6
+ if (m.includes("xml"))
7
+ return "xml";
8
+ if (m.includes("yaml") || m.includes("yml"))
9
+ return "yaml";
10
+ if (m.includes("html"))
11
+ return "html";
12
+ if (m.includes("json"))
13
+ return "json";
14
+ if (m.startsWith("text/"))
15
+ return "text";
16
+ return "json";
17
+ }
18
+ /** Build a variant from a concrete example value. */
19
+ function variantFromValue(value, opts) {
20
+ const lang = langForMediaType(opts.mediaType);
21
+ // JSON media encodes any value (a bare string "hi" renders as "hi"); other
22
+ // media types keep a provided string body verbatim (pre-formatted XML/text).
23
+ const raw = typeof value === "string" && lang !== "json"
24
+ ? value
25
+ : JSON.stringify(value, null, 2);
26
+ return {
27
+ label: opts.label,
28
+ summary: opts.summary,
29
+ mediaType: opts.mediaType,
30
+ raw,
31
+ html: highlightCode(raw, lang),
32
+ };
33
+ }
34
+ /** Build a variant from a named OpenAPI example object. */
35
+ function variantFromExample(name, example, mediaType) {
36
+ if (example.value !== undefined) {
37
+ return variantFromValue(example.value, {
38
+ label: example.summary ?? name,
39
+ summary: example.description,
40
+ mediaType,
41
+ });
42
+ }
43
+ if (example.externalValue) {
44
+ return {
45
+ label: example.summary ?? name,
46
+ summary: example.description,
47
+ mediaType,
48
+ raw: example.externalValue,
49
+ html: "",
50
+ externalValue: example.externalValue,
51
+ };
52
+ }
53
+ return null;
54
+ }
55
+ /** Build a single variant from a schema, falling back to a generated example. */
56
+ export function variantFromSchema(schema, label = "Example", mediaType = "application/json") {
57
+ const value = schema.example ?? generateExample(schema);
58
+ if (value === undefined)
59
+ return null;
60
+ return variantFromValue(value, { label, mediaType });
61
+ }
62
+ /**
63
+ * Collect every render-ready example for a content map (a response, request
64
+ * body, or parameter). Provided examples win over the schema: named `examples`
65
+ * first, then a single `example`, and only then a schema-generated fallback.
66
+ * Every media type contributes, so multi-content-type responses surface them
67
+ * all rather than just the first.
68
+ */
69
+ export function buildExampleVariants(content) {
70
+ if (!content)
71
+ return [];
72
+ const entries = Object.entries(content);
73
+ const multiMedia = entries.length > 1;
74
+ const variants = [];
75
+ for (const [mediaType, media] of entries) {
76
+ const named = media.examples ? Object.entries(media.examples) : [];
77
+ if (named.length) {
78
+ for (const [name, example] of named) {
79
+ const variant = variantFromExample(name, example, mediaType);
80
+ if (variant)
81
+ variants.push(variant);
82
+ }
83
+ continue;
84
+ }
85
+ if (media.example !== undefined) {
86
+ variants.push(variantFromValue(media.example, {
87
+ label: multiMedia ? mediaType : "Example",
88
+ mediaType,
89
+ }));
90
+ continue;
91
+ }
92
+ if (media.schema) {
93
+ const variant = variantFromSchema(media.schema, multiMedia ? mediaType : "Example", mediaType);
94
+ if (variant)
95
+ variants.push(variant);
96
+ }
97
+ }
98
+ return variants;
99
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "sourcey",
3
- "version": "3.6.2",
3
+ "version": "3.6.4",
4
4
  "description": "Precision documentation from OpenAPI, MCP, Doxygen, godoc, rustdoc, and Markdown. Static HTML you own.",
5
5
  "type": "module",
6
6
  "engines": {