@aravindc26/velu 0.11.0 → 0.11.3

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 (60) hide show
  1. package/package.json +15 -6
  2. package/schema/velu.schema.json +1251 -115
  3. package/src/build.ts +1121 -304
  4. package/src/cli.ts +90 -26
  5. package/src/engine/_server.mjs +1684 -277
  6. package/src/engine/app/(docs)/[...slug]/layout.tsx +371 -0
  7. package/src/engine/app/(docs)/[...slug]/page.tsx +926 -0
  8. package/src/engine/app/api/proxy/route.ts +23 -0
  9. package/src/engine/app/copy-page.css +59 -1
  10. package/src/engine/app/global.css +3157 -3
  11. package/src/engine/app/layout.tsx +56 -1
  12. package/src/engine/app/llms-file/route.ts +87 -0
  13. package/src/engine/app/llms-full-file/route.ts +62 -0
  14. package/src/engine/app/md-file/[...slug]/route.ts +409 -0
  15. package/src/engine/app/page.tsx +45 -0
  16. package/src/engine/app/robots.txt/route.ts +63 -0
  17. package/src/engine/app/rss-file/[...slug]/route.ts +169 -0
  18. package/src/engine/app/sitemap.xml/route.ts +82 -0
  19. package/src/engine/components/assistant.tsx +16 -5
  20. package/src/engine/components/changelog-filters.tsx +114 -0
  21. package/src/engine/components/code-group.tsx +383 -0
  22. package/src/engine/components/color.tsx +118 -0
  23. package/src/engine/components/expandable.tsx +77 -0
  24. package/src/engine/components/icon.tsx +136 -0
  25. package/src/engine/components/image-zoom-fallback.tsx +147 -0
  26. package/src/engine/components/image.tsx +111 -0
  27. package/src/engine/components/manual-api-playground.tsx +154 -0
  28. package/src/engine/components/mermaid.tsx +142 -0
  29. package/src/engine/components/openapi-toc-sync.tsx +59 -0
  30. package/src/engine/components/openapi.tsx +1682 -0
  31. package/src/engine/components/page-feedback.tsx +153 -0
  32. package/src/engine/components/product-switcher.tsx +27 -3
  33. package/src/engine/components/prompt.tsx +90 -0
  34. package/src/engine/components/providers.tsx +1 -6
  35. package/src/engine/components/search.tsx +4 -0
  36. package/src/engine/components/sidebar-links.tsx +13 -15
  37. package/src/engine/components/synced-tabs.tsx +57 -0
  38. package/src/engine/components/toc-examples.tsx +110 -0
  39. package/src/engine/components/view.tsx +344 -0
  40. package/src/engine/generated/redirects.ts +3 -0
  41. package/src/engine/lib/changelog.ts +246 -0
  42. package/src/engine/lib/layout.shared.ts +30 -2
  43. package/src/engine/lib/llms.ts +444 -0
  44. package/src/engine/lib/navigation-normalize.mjs +481 -412
  45. package/src/engine/lib/navigation-normalize.ts +261 -54
  46. package/src/engine/lib/redirects.ts +194 -0
  47. package/src/engine/lib/source.ts +107 -4
  48. package/src/engine/lib/velu.ts +368 -2
  49. package/src/engine/mdx-components.tsx +648 -0
  50. package/src/engine/middleware.ts +66 -0
  51. package/src/engine/public/icons/cursor-dark.svg +12 -0
  52. package/src/engine/public/icons/cursor-light.svg +12 -0
  53. package/src/engine/source.config.ts +98 -1
  54. package/src/engine/src/components/PageTitle.astro +16 -5
  55. package/src/engine/src/lib/velu.ts +11 -3
  56. package/src/navigation-normalize.ts +252 -54
  57. package/src/themes.ts +6 -6
  58. package/src/validate.ts +119 -6
  59. package/src/engine/app/(docs)/[[...slug]]/layout.tsx +0 -87
  60. package/src/engine/app/(docs)/[[...slug]]/page.tsx +0 -146
@@ -6,16 +6,21 @@ interface NavLink {
6
6
  href: string;
7
7
  label: string;
8
8
  icon?: string;
9
+ iconType?: string;
9
10
  }
10
11
 
11
12
  interface NavGroup {
12
13
  group: string;
13
14
  slug: string;
14
15
  icon?: string;
16
+ iconType?: string;
17
+ version?: string;
15
18
  tag?: string;
16
19
  expanded?: boolean;
17
20
  description?: string;
18
21
  hidden?: boolean;
22
+ openapi?: OpenApiValue;
23
+ asyncapi?: OpenApiValue;
19
24
  pages: NavEntry[];
20
25
  }
21
26
 
@@ -23,12 +28,17 @@ interface NavTab {
23
28
  tab: string;
24
29
  slug: string;
25
30
  icon?: string;
31
+ iconType?: string;
32
+ version?: string;
26
33
  href?: string;
34
+ openapi?: OpenApiValue;
35
+ asyncapi?: OpenApiValue;
27
36
  pages?: Array<string | NavSeparator | NavLink>;
28
37
  groups?: NavGroup[];
29
38
  }
30
39
 
31
40
  type NavEntry = string | NavGroup | NavSeparator | NavLink;
41
+ type OpenApiValue = string | string[] | { source: string | string[]; directory?: string };
32
42
 
33
43
  function isObject(value: unknown): value is Record<string, unknown> {
34
44
  return typeof value === "object" && value !== null;
@@ -89,10 +99,45 @@ function uniqueSlug(base: string, used: Set<string>): string {
89
99
  return candidate;
90
100
  }
91
101
 
102
+ function normalizeOpenApiValue(value: unknown): OpenApiValue | undefined {
103
+ if (typeof value === "string") {
104
+ const trimmed = value.trim();
105
+ return trimmed.length > 0 ? trimmed : undefined;
106
+ }
107
+ if (Array.isArray(value)) {
108
+ const items = value
109
+ .filter((entry): entry is string => typeof entry === "string")
110
+ .map((entry) => entry.trim())
111
+ .filter((entry) => entry.length > 0);
112
+ return items.length > 0 ? items : undefined;
113
+ }
114
+ if (isObject(value)) {
115
+ const source = normalizeOpenApiValue(value.source);
116
+ if (source === undefined || typeof source === "object" && !Array.isArray(source)) return undefined;
117
+ const rawDirectory = typeof value.directory === "string" ? value.directory.trim() : "";
118
+ const directory = rawDirectory ? rawDirectory.replace(/\\/g, "/").replace(/^\/+/, "").replace(/\/+$/, "") : undefined;
119
+ return directory ? { source, directory } : { source };
120
+ }
121
+ return undefined;
122
+ }
123
+
124
+ function resolveOpenApiValue(local: unknown, inherited?: OpenApiValue): OpenApiValue | undefined {
125
+ return normalizeOpenApiValue(local) ?? inherited;
126
+ }
127
+
128
+ function resolveVersionValue(local: unknown, inherited?: string): string | undefined {
129
+ if (typeof local === "string") {
130
+ const trimmed = local.trim();
131
+ if (trimmed.length > 0) return trimmed;
132
+ }
133
+ return inherited;
134
+ }
135
+
92
136
 
93
137
  function normalizeLink(value: NavLink): NavLink {
94
138
  const out: NavLink = { href: value.href, label: value.label };
95
139
  if (typeof value.icon === "string" && value.icon.length > 0) out.icon = value.icon;
140
+ if (typeof value.iconType === "string" && value.iconType.length > 0) out.iconType = value.iconType;
96
141
  return out;
97
142
  }
98
143
 
@@ -100,7 +145,11 @@ function normalizeAnchorLink(value: Record<string, unknown>): NavLink {
100
145
  const href = typeof value.href === "string" ? value.href : "#";
101
146
  const label = typeof value.anchor === "string" ? value.anchor : "Link";
102
147
  const icon = typeof value.icon === "string" ? value.icon : undefined;
103
- return icon ? { href, label, icon } : { href, label };
148
+ const iconType = typeof value.iconType === "string" ? value.iconType : undefined;
149
+ const out: NavLink = { href, label };
150
+ if (icon) out.icon = icon;
151
+ if (iconType) out.iconType = iconType;
152
+ return out;
104
153
  }
105
154
 
106
155
  function hasContent(value: Record<string, unknown>): boolean {
@@ -109,7 +158,9 @@ function hasContent(value: Record<string, unknown>): boolean {
109
158
  (Array.isArray(value.groups) && value.groups.length > 0) ||
110
159
  (Array.isArray(value.menu) && value.menu.length > 0) ||
111
160
  (Array.isArray(value.tabs) && value.tabs.length > 0) ||
112
- (Array.isArray(value.dropdowns) && value.dropdowns.length > 0);
161
+ (Array.isArray(value.dropdowns) && value.dropdowns.length > 0) ||
162
+ normalizeOpenApiValue(value.openapi) !== undefined ||
163
+ normalizeOpenApiValue(value.asyncapi) !== undefined;
113
164
 
114
165
  const hasAnchors =
115
166
  Array.isArray(value.anchors) &&
@@ -127,57 +178,105 @@ function hasContent(value: Record<string, unknown>): boolean {
127
178
  return hasSimple || hasAnchors;
128
179
  }
129
180
 
130
- function normalizeGroup(rawGroup: Record<string, unknown>, usedGroupSlugs: Set<string>): NavGroup {
181
+ function normalizeGroup(
182
+ rawGroup: Record<string, unknown>,
183
+ usedGroupSlugs: Set<string>,
184
+ inheritedOpenApi?: OpenApiValue,
185
+ inheritedVersion?: string,
186
+ ): NavGroup {
131
187
  const groupName = typeof rawGroup.group === "string" ? rawGroup.group : "Group";
132
188
  const rawSlug = typeof rawGroup.slug === "string" ? rawGroup.slug : groupName;
133
189
  const groupSlug = uniqueSlug(slugify(rawSlug, "group"), usedGroupSlugs);
190
+ const openapi = resolveOpenApiValue(rawGroup.openapi, inheritedOpenApi);
191
+ const asyncapi = normalizeOpenApiValue(rawGroup.asyncapi);
192
+ const version = resolveVersionValue(rawGroup.version, inheritedVersion);
134
193
 
135
194
  const childUsedSlugs = new Set<string>();
136
- const pages = collectEntries(rawGroup, childUsedSlugs);
195
+ const pages = collectEntries(rawGroup, childUsedSlugs, openapi, version);
137
196
 
138
197
  const out: NavGroup = { group: groupName, slug: groupSlug, pages };
139
198
  if (typeof rawGroup.icon === "string") out.icon = rawGroup.icon;
199
+ if (typeof rawGroup.iconType === "string") out.iconType = rawGroup.iconType;
200
+ if (version !== undefined) out.version = version;
140
201
  if (typeof rawGroup.tag === "string") out.tag = rawGroup.tag;
141
202
  if (typeof rawGroup.expanded === "boolean") out.expanded = rawGroup.expanded;
142
203
  if (typeof rawGroup.description === "string") out.description = rawGroup.description;
143
204
  if (typeof rawGroup.hidden === "boolean") out.hidden = rawGroup.hidden;
205
+ if (openapi !== undefined) out.openapi = openapi;
206
+ if (asyncapi !== undefined) out.asyncapi = asyncapi;
144
207
  return out;
145
208
  }
146
209
 
147
- function normalizeMenuItem(rawItem: Record<string, unknown>, usedGroupSlugs: Set<string>): NavGroup {
210
+ function normalizeMenuItem(
211
+ rawItem: Record<string, unknown>,
212
+ usedGroupSlugs: Set<string>,
213
+ inheritedOpenApi?: OpenApiValue,
214
+ inheritedVersion?: string,
215
+ ): NavGroup {
148
216
  const name = typeof rawItem.item === "string" ? rawItem.item : "Menu";
149
217
  const rawSlug = typeof rawItem.slug === "string" ? rawItem.slug : name;
150
218
  const slug = uniqueSlug(slugify(rawSlug, "menu"), usedGroupSlugs);
219
+ const openapi = resolveOpenApiValue(rawItem.openapi, inheritedOpenApi);
220
+ const asyncapi = normalizeOpenApiValue(rawItem.asyncapi);
221
+ const version = resolveVersionValue(rawItem.version, inheritedVersion);
151
222
 
152
223
  const nestedGroupSlugs = new Set<string>();
153
- const pages = collectEntries(rawItem, nestedGroupSlugs);
224
+ const pages = collectEntries(rawItem, nestedGroupSlugs, openapi, version);
154
225
  const out: NavGroup = { group: name, slug, pages };
155
226
  if (typeof rawItem.icon === "string") out.icon = rawItem.icon;
227
+ if (typeof rawItem.iconType === "string") out.iconType = rawItem.iconType;
228
+ if (version !== undefined) out.version = version;
229
+ if (openapi !== undefined) out.openapi = openapi;
230
+ if (asyncapi !== undefined) out.asyncapi = asyncapi;
156
231
  return out;
157
232
  }
158
233
 
159
- function normalizeTabAsGroup(rawTab: Record<string, unknown>, usedGroupSlugs: Set<string>): NavGroup {
234
+ function normalizeTabAsGroup(
235
+ rawTab: Record<string, unknown>,
236
+ usedGroupSlugs: Set<string>,
237
+ inheritedOpenApi?: OpenApiValue,
238
+ inheritedVersion?: string,
239
+ ): NavGroup {
160
240
  const tabName = typeof rawTab.tab === "string" ? rawTab.tab : "Tab";
161
241
  const rawSlug = typeof rawTab.slug === "string" ? rawTab.slug : tabName;
162
242
  const slug = uniqueSlug(slugify(rawSlug, "tab"), usedGroupSlugs);
243
+ const openapi = resolveOpenApiValue(rawTab.openapi, inheritedOpenApi);
244
+ const asyncapi = normalizeOpenApiValue(rawTab.asyncapi);
245
+ const version = resolveVersionValue(rawTab.version, inheritedVersion);
163
246
  const nestedGroupSlugs = new Set<string>();
164
- const pages = collectEntries(rawTab, nestedGroupSlugs);
247
+ const pages = collectEntries(rawTab, nestedGroupSlugs, openapi, version);
165
248
 
166
249
  if (typeof rawTab.href === "string" && rawTab.href.length > 0 && !hasContent(rawTab)) {
167
- pages.push({ href: rawTab.href, label: tabName, ...(typeof rawTab.icon === "string" ? { icon: rawTab.icon } : {}) });
250
+ pages.push({
251
+ href: rawTab.href,
252
+ label: tabName,
253
+ ...(typeof rawTab.icon === "string" ? { icon: rawTab.icon } : {}),
254
+ ...(typeof rawTab.iconType === "string" ? { iconType: rawTab.iconType } : {}),
255
+ });
168
256
  }
169
257
 
170
258
  const out: NavGroup = { group: tabName, slug, pages };
171
259
  if (typeof rawTab.icon === "string") out.icon = rawTab.icon;
260
+ if (typeof rawTab.iconType === "string") out.iconType = rawTab.iconType;
261
+ if (version !== undefined) out.version = version;
262
+ if (openapi !== undefined) out.openapi = openapi;
263
+ if (asyncapi !== undefined) out.asyncapi = asyncapi;
172
264
  return out;
173
265
  }
174
266
 
175
- function normalizeDropdownAsGroup(rawDropdown: Record<string, unknown>, usedGroupSlugs: Set<string>): NavGroup {
267
+ function normalizeDropdownAsGroup(
268
+ rawDropdown: Record<string, unknown>,
269
+ usedGroupSlugs: Set<string>,
270
+ inheritedOpenApi?: OpenApiValue,
271
+ inheritedVersion?: string,
272
+ ): NavGroup {
176
273
  return normalizeTabAsGroup(
177
274
  {
178
275
  tab: rawDropdown.dropdown,
179
276
  slug: rawDropdown.slug,
180
277
  icon: rawDropdown.icon,
278
+ iconType: rawDropdown.iconType,
279
+ version: rawDropdown.version,
181
280
  href: rawDropdown.href,
182
281
  groups: rawDropdown.groups,
183
282
  pages: rawDropdown.pages,
@@ -185,39 +284,60 @@ function normalizeDropdownAsGroup(rawDropdown: Record<string, unknown>, usedGrou
185
284
  anchors: rawDropdown.anchors,
186
285
  dropdowns: rawDropdown.dropdowns,
187
286
  tabs: rawDropdown.tabs,
287
+ openapi: rawDropdown.openapi,
288
+ asyncapi: rawDropdown.asyncapi,
188
289
  },
189
- usedGroupSlugs
290
+ usedGroupSlugs,
291
+ inheritedOpenApi,
292
+ inheritedVersion
190
293
  );
191
294
  }
192
295
 
193
- function normalizeAnchorAsGroup(rawAnchor: Record<string, unknown>, usedGroupSlugs: Set<string>): NavGroup {
296
+ function normalizeAnchorAsGroup(
297
+ rawAnchor: Record<string, unknown>,
298
+ usedGroupSlugs: Set<string>,
299
+ inheritedOpenApi?: OpenApiValue,
300
+ inheritedVersion?: string,
301
+ ): NavGroup {
194
302
  const anchorName = typeof rawAnchor.anchor === "string" ? rawAnchor.anchor : "Anchor";
195
303
  const rawSlug = typeof rawAnchor.slug === "string" ? rawAnchor.slug : anchorName;
196
304
  const slug = uniqueSlug(slugify(rawSlug, "anchor"), usedGroupSlugs);
305
+ const openapi = resolveOpenApiValue(rawAnchor.openapi, inheritedOpenApi);
306
+ const asyncapi = normalizeOpenApiValue(rawAnchor.asyncapi);
307
+ const version = resolveVersionValue(rawAnchor.version, inheritedVersion);
197
308
  const nestedGroupSlugs = new Set<string>();
198
- const pages = collectEntries(rawAnchor, nestedGroupSlugs);
309
+ const pages = collectEntries(rawAnchor, nestedGroupSlugs, openapi, version);
199
310
 
200
311
  const out: NavGroup = { group: anchorName, slug, pages };
201
312
  if (typeof rawAnchor.icon === "string") out.icon = rawAnchor.icon;
313
+ if (typeof rawAnchor.iconType === "string") out.iconType = rawAnchor.iconType;
314
+ if (version !== undefined) out.version = version;
315
+ if (openapi !== undefined) out.openapi = openapi;
316
+ if (asyncapi !== undefined) out.asyncapi = asyncapi;
202
317
  return out;
203
318
  }
204
319
 
205
- function collectEntries(rawSection: Record<string, unknown>, usedGroupSlugs: Set<string>): NavEntry[] {
320
+ function collectEntries(
321
+ rawSection: Record<string, unknown>,
322
+ usedGroupSlugs: Set<string>,
323
+ inheritedOpenApi?: OpenApiValue,
324
+ inheritedVersion?: string,
325
+ ): NavEntry[] {
206
326
  const entries: NavEntry[] = [];
207
327
 
208
328
  for (const item of Array.isArray(rawSection.menu) ? rawSection.menu : []) {
209
- if (isMenuItem(item)) entries.push(normalizeMenuItem(item, usedGroupSlugs));
329
+ if (isMenuItem(item)) entries.push(normalizeMenuItem(item, usedGroupSlugs, inheritedOpenApi, inheritedVersion));
210
330
  }
211
331
 
212
332
  for (const group of Array.isArray(rawSection.groups) ? rawSection.groups : []) {
213
- if (isGroupLike(group)) entries.push(normalizeGroup(group, usedGroupSlugs));
333
+ if (isGroupLike(group)) entries.push(normalizeGroup(group, usedGroupSlugs, inheritedOpenApi, inheritedVersion));
214
334
  }
215
335
 
216
336
  for (const item of Array.isArray(rawSection.pages) ? rawSection.pages : []) {
217
337
  if (typeof item === "string") entries.push(item);
218
338
  else if (isSeparator(item)) entries.push({ separator: item.separator });
219
339
  else if (isLink(item)) entries.push(normalizeLink(item));
220
- else if (isGroupLike(item)) entries.push(normalizeGroup(item, usedGroupSlugs));
340
+ else if (isGroupLike(item)) entries.push(normalizeGroup(item, usedGroupSlugs, inheritedOpenApi, inheritedVersion));
221
341
  }
222
342
 
223
343
  for (const anchor of Array.isArray(rawSection.anchors) ? rawSection.anchors : []) {
@@ -225,29 +345,42 @@ function collectEntries(rawSection: Record<string, unknown>, usedGroupSlugs: Set
225
345
 
226
346
  const hrefOnly = typeof anchor.href === "string" && anchor.href.length > 0 && !hasContent(anchor);
227
347
  if (hrefOnly) entries.push(normalizeAnchorLink(anchor));
228
- else entries.push(normalizeAnchorAsGroup(anchor, usedGroupSlugs));
348
+ else entries.push(normalizeAnchorAsGroup(anchor, usedGroupSlugs, inheritedOpenApi, inheritedVersion));
229
349
  }
230
350
 
231
351
  for (const dropdown of Array.isArray(rawSection.dropdowns) ? rawSection.dropdowns : []) {
232
- if (isDropdownLike(dropdown)) entries.push(normalizeDropdownAsGroup(dropdown, usedGroupSlugs));
352
+ if (isDropdownLike(dropdown)) entries.push(normalizeDropdownAsGroup(dropdown, usedGroupSlugs, inheritedOpenApi, inheritedVersion));
233
353
  }
234
354
 
235
355
  for (const tab of Array.isArray(rawSection.tabs) ? rawSection.tabs : []) {
236
- if (isTabLike(tab)) entries.push(normalizeTabAsGroup(tab, usedGroupSlugs));
356
+ if (isTabLike(tab)) entries.push(normalizeTabAsGroup(tab, usedGroupSlugs, inheritedOpenApi, inheritedVersion));
237
357
  }
238
358
 
239
359
  return entries;
240
360
  }
241
361
 
242
- function normalizeTab(rawTab: Record<string, unknown>, usedTabSlugs: Set<string>, slugPrefix: string): NavTab {
362
+ function normalizeTab(
363
+ rawTab: Record<string, unknown>,
364
+ usedTabSlugs: Set<string>,
365
+ slugPrefix: string,
366
+ inheritedOpenApi?: OpenApiValue,
367
+ inheritedVersion?: string,
368
+ ): NavTab {
243
369
  const tabName = typeof rawTab.tab === "string" ? rawTab.tab : "Tab";
244
370
  const rawSlug = typeof rawTab.slug === "string" ? rawTab.slug : tabName;
245
371
  const tabSlugPart = slugify(rawSlug, "tab");
246
372
  const fullSlug = slugPrefix ? `${slugPrefix}/${tabSlugPart}` : tabSlugPart;
247
373
  const slug = uniqueSlug(fullSlug, usedTabSlugs);
374
+ const openapi = resolveOpenApiValue(rawTab.openapi, inheritedOpenApi);
375
+ const asyncapi = normalizeOpenApiValue(rawTab.asyncapi);
376
+ const version = resolveVersionValue(rawTab.version, inheritedVersion);
248
377
 
249
378
  const out: NavTab = { tab: tabName, slug };
250
379
  if (typeof rawTab.icon === "string") out.icon = rawTab.icon;
380
+ if (typeof rawTab.iconType === "string") out.iconType = rawTab.iconType;
381
+ if (version !== undefined) out.version = version;
382
+ if (openapi !== undefined) out.openapi = openapi;
383
+ if (asyncapi !== undefined) out.asyncapi = asyncapi;
251
384
 
252
385
  if (typeof rawTab.href === "string" && rawTab.href.length > 0 && !hasContent(rawTab)) {
253
386
  out.href = rawTab.href;
@@ -255,7 +388,7 @@ function normalizeTab(rawTab: Record<string, unknown>, usedTabSlugs: Set<string>
255
388
  }
256
389
 
257
390
  const groupSlugSet = new Set<string>();
258
- const entries = collectEntries(rawTab, groupSlugSet);
391
+ const entries = collectEntries(rawTab, groupSlugSet, openapi, version);
259
392
  const groups: NavGroup[] = [];
260
393
  const pages: Array<string | NavSeparator | NavLink> = [];
261
394
 
@@ -269,57 +402,92 @@ function normalizeTab(rawTab: Record<string, unknown>, usedTabSlugs: Set<string>
269
402
  return out;
270
403
  }
271
404
 
272
- function normalizeDropdownToTab(rawDropdown: Record<string, unknown>, usedTabSlugs: Set<string>, slugPrefix: string): NavTab {
405
+ function normalizeDropdownToTab(
406
+ rawDropdown: Record<string, unknown>,
407
+ usedTabSlugs: Set<string>,
408
+ slugPrefix: string,
409
+ inheritedOpenApi?: OpenApiValue,
410
+ inheritedVersion?: string,
411
+ ): NavTab {
273
412
  return normalizeTab(
274
413
  {
275
414
  tab: rawDropdown.dropdown,
276
415
  slug: rawDropdown.slug,
277
416
  icon: rawDropdown.icon,
417
+ iconType: rawDropdown.iconType,
418
+ version: rawDropdown.version,
278
419
  href: rawDropdown.href,
279
420
  groups: rawDropdown.groups,
280
421
  pages: rawDropdown.pages,
281
422
  menu: rawDropdown.menu,
282
423
  anchors: rawDropdown.anchors,
283
424
  dropdowns: rawDropdown.dropdowns,
284
- tabs: rawDropdown.tabs,
285
- },
425
+ tabs: rawDropdown.tabs,
426
+ openapi: rawDropdown.openapi,
427
+ asyncapi: rawDropdown.asyncapi,
428
+ },
286
429
  usedTabSlugs,
287
- slugPrefix
430
+ slugPrefix,
431
+ inheritedOpenApi,
432
+ inheritedVersion
288
433
  );
289
434
  }
290
435
 
291
- function normalizeTabList(rawTabs: unknown[], usedTabSlugs: Set<string>, slugPrefix = ""): NavTab[] {
436
+ function normalizeTabList(
437
+ rawTabs: unknown[],
438
+ usedTabSlugs: Set<string>,
439
+ slugPrefix = "",
440
+ inheritedOpenApi?: OpenApiValue,
441
+ inheritedVersion?: string,
442
+ ): NavTab[] {
292
443
  const tabs: NavTab[] = [];
293
444
  for (const item of rawTabs) {
294
- if (isTabLike(item)) tabs.push(normalizeTab(item, usedTabSlugs, slugPrefix));
445
+ if (isTabLike(item)) tabs.push(normalizeTab(item, usedTabSlugs, slugPrefix, inheritedOpenApi, inheritedVersion));
295
446
  }
296
447
  return tabs;
297
448
  }
298
449
 
299
- function normalizeDropdownList(rawDropdowns: unknown[], usedTabSlugs: Set<string>, slugPrefix = ""): NavTab[] {
450
+ function normalizeDropdownList(
451
+ rawDropdowns: unknown[],
452
+ usedTabSlugs: Set<string>,
453
+ slugPrefix = "",
454
+ inheritedOpenApi?: OpenApiValue,
455
+ inheritedVersion?: string,
456
+ ): NavTab[] {
300
457
  const tabs: NavTab[] = [];
301
458
  for (const item of rawDropdowns) {
302
- if (isDropdownLike(item)) tabs.push(normalizeDropdownToTab(item, usedTabSlugs, slugPrefix));
459
+ if (isDropdownLike(item)) tabs.push(normalizeDropdownToTab(item, usedTabSlugs, slugPrefix, inheritedOpenApi, inheritedVersion));
303
460
  }
304
- return tabs;
461
+ if (inheritedOpenApi === undefined && inheritedVersion === undefined) return tabs;
462
+ return tabs.map((tab) => ({
463
+ ...tab,
464
+ openapi: tab.openapi ?? inheritedOpenApi,
465
+ version: tab.version ?? inheritedVersion,
466
+ }));
305
467
  }
306
468
 
307
- function normalizeNavigationTabs(navigation: unknown, usedTabSlugs = new Set<string>()): NavTab[] {
469
+ function normalizeNavigationTabs(
470
+ navigation: unknown,
471
+ usedTabSlugs = new Set<string>(),
472
+ inheritedOpenApi?: OpenApiValue,
473
+ ): NavTab[] {
308
474
  if (!isObject(navigation)) return [];
309
475
 
476
+ const sectionOpenApi = resolveOpenApiValue(navigation.openapi, inheritedOpenApi);
310
477
  const tabs: NavTab[] = [];
311
478
 
312
- tabs.push(...normalizeTabList(Array.isArray(navigation.tabs) ? navigation.tabs : [], usedTabSlugs));
313
- tabs.push(...normalizeDropdownList(Array.isArray(navigation.dropdowns) ? navigation.dropdowns : [], usedTabSlugs));
479
+ tabs.push(...normalizeTabList(Array.isArray(navigation.tabs) ? navigation.tabs : [], usedTabSlugs, "", sectionOpenApi));
480
+ tabs.push(...normalizeDropdownList(Array.isArray(navigation.dropdowns) ? navigation.dropdowns : [], usedTabSlugs, "", sectionOpenApi));
314
481
 
315
482
  if (Array.isArray(navigation.products)) {
316
483
  navigation.products.forEach((product, index) => {
317
484
  if (!isObject(product)) return;
318
485
  const productName = typeof product.product === "string" ? product.product : `Product ${index + 1}`;
319
486
  const prefix = slugify(productName, `product-${index + 1}`);
487
+ const productOpenApi = resolveOpenApiValue(product.openapi, sectionOpenApi);
320
488
 
321
- tabs.push(...normalizeTabList(Array.isArray(product.tabs) ? product.tabs : [], usedTabSlugs, prefix));
322
- tabs.push(...normalizeDropdownList(Array.isArray(product.dropdowns) ? product.dropdowns : [], usedTabSlugs, prefix));
489
+ tabs.push(...normalizeTabList(Array.isArray(product.tabs) ? product.tabs : [], usedTabSlugs, prefix, productOpenApi));
490
+ tabs.push(...normalizeDropdownList(Array.isArray(product.dropdowns) ? product.dropdowns : [], usedTabSlugs, prefix, productOpenApi));
323
491
 
324
492
  if (!Array.isArray(product.tabs) && !Array.isArray(product.dropdowns)) {
325
493
  if (hasContent(product)) {
@@ -329,15 +497,19 @@ function normalizeNavigationTabs(navigation: unknown, usedTabSlugs = new Set<str
329
497
  tab: productName,
330
498
  slug: prefix,
331
499
  icon: product.icon,
500
+ iconType: product.iconType,
332
501
  groups: product.groups,
333
502
  pages: product.pages,
334
503
  menu: product.menu,
335
504
  anchors: product.anchors,
336
505
  dropdowns: product.dropdowns,
337
506
  tabs: product.tabs,
338
- },
507
+ openapi: productOpenApi,
508
+ asyncapi: normalizeOpenApiValue(product.asyncapi),
509
+ },
339
510
  usedTabSlugs,
340
- ""
511
+ "",
512
+ productOpenApi
341
513
  )
342
514
  );
343
515
  } else if (typeof product.href === "string" && product.href.length > 0) {
@@ -347,10 +519,14 @@ function normalizeNavigationTabs(navigation: unknown, usedTabSlugs = new Set<str
347
519
  tab: productName,
348
520
  slug: prefix,
349
521
  icon: product.icon,
522
+ iconType: product.iconType,
350
523
  href: product.href,
351
- },
524
+ openapi: productOpenApi,
525
+ asyncapi: normalizeOpenApiValue(product.asyncapi),
526
+ },
352
527
  usedTabSlugs,
353
- ""
528
+ "",
529
+ productOpenApi
354
530
  )
355
531
  );
356
532
  }
@@ -363,9 +539,10 @@ function normalizeNavigationTabs(navigation: unknown, usedTabSlugs = new Set<str
363
539
  if (!isObject(version)) return;
364
540
  const versionName = typeof version.version === "string" ? version.version : `Version ${index + 1}`;
365
541
  const prefix = slugify(versionName, `version-${index + 1}`);
542
+ const versionOpenApi = resolveOpenApiValue(version.openapi, sectionOpenApi);
366
543
 
367
- tabs.push(...normalizeTabList(Array.isArray(version.tabs) ? version.tabs : [], usedTabSlugs, prefix));
368
- tabs.push(...normalizeDropdownList(Array.isArray(version.dropdowns) ? version.dropdowns : [], usedTabSlugs, prefix));
544
+ tabs.push(...normalizeTabList(Array.isArray(version.tabs) ? version.tabs : [], usedTabSlugs, prefix, versionOpenApi));
545
+ tabs.push(...normalizeDropdownList(Array.isArray(version.dropdowns) ? version.dropdowns : [], usedTabSlugs, prefix, versionOpenApi));
369
546
 
370
547
  if (!Array.isArray(version.tabs) && !Array.isArray(version.dropdowns)) {
371
548
  if (hasContent(version)) {
@@ -380,9 +557,12 @@ function normalizeNavigationTabs(navigation: unknown, usedTabSlugs = new Set<str
380
557
  anchors: version.anchors,
381
558
  dropdowns: version.dropdowns,
382
559
  tabs: version.tabs,
383
- },
560
+ openapi: versionOpenApi,
561
+ asyncapi: normalizeOpenApiValue(version.asyncapi),
562
+ },
384
563
  usedTabSlugs,
385
- ""
564
+ "",
565
+ versionOpenApi
386
566
  )
387
567
  );
388
568
  } else if (typeof version.href === "string" && version.href.length > 0) {
@@ -392,9 +572,12 @@ function normalizeNavigationTabs(navigation: unknown, usedTabSlugs = new Set<str
392
572
  tab: versionName,
393
573
  slug: prefix,
394
574
  href: version.href,
395
- },
575
+ openapi: versionOpenApi,
576
+ asyncapi: normalizeOpenApiValue(version.asyncapi),
577
+ },
396
578
  usedTabSlugs,
397
- ""
579
+ "",
580
+ versionOpenApi
398
581
  )
399
582
  );
400
583
  }
@@ -408,9 +591,11 @@ function normalizeNavigationTabs(navigation: unknown, usedTabSlugs = new Set<str
408
591
 
409
592
  const anchorName = typeof anchor.anchor === "string" ? anchor.anchor : `Anchor ${index + 1}`;
410
593
  const prefix = slugify(anchorName, `anchor-${index + 1}`);
594
+ const anchorOpenApi = resolveOpenApiValue(anchor.openapi, sectionOpenApi);
595
+ const anchorVersion = resolveVersionValue(anchor.version);
411
596
 
412
597
  if (Array.isArray(anchor.tabs)) {
413
- tabs.push(...normalizeTabList(anchor.tabs, usedTabSlugs, prefix));
598
+ tabs.push(...normalizeTabList(anchor.tabs, usedTabSlugs, prefix, anchorOpenApi, anchorVersion));
414
599
  } else if (hasContent(anchor)) {
415
600
  tabs.push(
416
601
  normalizeTab(
@@ -418,15 +603,21 @@ function normalizeNavigationTabs(navigation: unknown, usedTabSlugs = new Set<str
418
603
  tab: anchorName,
419
604
  slug: prefix,
420
605
  icon: anchor.icon,
606
+ iconType: anchor.iconType,
607
+ version: anchorVersion,
421
608
  groups: anchor.groups,
422
609
  pages: anchor.pages,
423
610
  menu: anchor.menu,
424
611
  anchors: anchor.anchors,
425
612
  dropdowns: anchor.dropdowns,
426
613
  tabs: anchor.tabs,
614
+ openapi: anchorOpenApi,
615
+ asyncapi: normalizeOpenApiValue(anchor.asyncapi),
427
616
  },
428
617
  usedTabSlugs,
429
- ""
618
+ "",
619
+ anchorOpenApi,
620
+ anchorVersion
430
621
  )
431
622
  );
432
623
  }
@@ -449,38 +640,45 @@ function normalizeNavigationTabs(navigation: unknown, usedTabSlugs = new Set<str
449
640
  anchors: navigation.anchors,
450
641
  dropdowns: navigation.dropdowns,
451
642
  tabs: navigation.tabs,
643
+ openapi: sectionOpenApi,
644
+ asyncapi: normalizeOpenApiValue(navigation.asyncapi),
452
645
  },
453
646
  usedTabSlugs,
454
- ""
647
+ "",
648
+ sectionOpenApi
455
649
  )
456
650
  );
457
651
  }
458
652
 
459
653
  return tabs;
460
654
  }
461
-
462
- function normalizeLanguageEntries(languages: unknown): Array<Record<string, unknown>> {
655
+ function normalizeLanguageEntries(languages: unknown, inheritedOpenApi?: OpenApiValue): Array<Record<string, unknown>> {
463
656
  if (!Array.isArray(languages)) return [];
464
657
 
465
658
  return languages
466
659
  .filter(isObject)
467
660
  .map((entry) => {
468
661
  const usedTabSlugs = new Set<string>();
662
+ const languageOpenApi = resolveOpenApiValue(entry.openapi, inheritedOpenApi);
469
663
  return {
470
664
  ...entry,
471
- tabs: normalizeNavigationTabs(entry, usedTabSlugs),
665
+ tabs: normalizeNavigationTabs(entry, usedTabSlugs, languageOpenApi),
472
666
  };
473
667
  });
474
668
  }
475
669
 
476
670
  export function normalizeConfigNavigation<T extends { navigation?: unknown }>(config: T): T {
477
671
  const nav = isObject(config.navigation) ? config.navigation : {};
672
+ const navOpenApi = resolveOpenApiValue((nav as Record<string, unknown>).openapi);
673
+ const navAsyncApi = normalizeOpenApiValue((nav as Record<string, unknown>).asyncapi);
478
674
  return {
479
675
  ...config,
480
676
  navigation: {
481
677
  ...nav,
482
- tabs: normalizeNavigationTabs(nav),
483
- languages: normalizeLanguageEntries(nav.languages),
678
+ ...(navOpenApi !== undefined ? { openapi: navOpenApi } : {}),
679
+ ...(navAsyncApi !== undefined ? { asyncapi: navAsyncApi } : {}),
680
+ tabs: normalizeNavigationTabs(nav, new Set<string>(), navOpenApi),
681
+ languages: normalizeLanguageEntries(nav.languages, navOpenApi),
484
682
  products: Array.isArray(nav.products) ? nav.products : [],
485
683
  versions: Array.isArray(nav.versions) ? nav.versions : [],
486
684
  },
package/src/themes.ts CHANGED
@@ -129,11 +129,11 @@ function generateThemeCss(config: ThemeConfig): string {
129
129
  if (lightAccent) {
130
130
  const palette = deriveAccentPalette(lightAccent);
131
131
  lines.push(":root {");
132
- lines.push(` --color-fd-primary: ${palette.light.accent};`);
133
- lines.push(` --color-fd-primary-foreground: ${textColorFor(palette.light.accent)};`);
132
+ lines.push(` --color-fd-primary: ${lightAccent};`);
133
+ lines.push(` --color-fd-primary-foreground: ${textColorFor(lightAccent)};`);
134
134
  lines.push(` --color-fd-accent: ${palette.light.accentLow};`);
135
135
  lines.push(` --color-fd-accent-foreground: ${textColorFor(palette.light.accentLow)};`);
136
- lines.push(` --color-fd-ring: ${palette.light.accent};`);
136
+ lines.push(` --color-fd-ring: ${lightAccent};`);
137
137
  lines.push("}");
138
138
  lines.push("");
139
139
  }
@@ -141,11 +141,11 @@ function generateThemeCss(config: ThemeConfig): string {
141
141
  if (darkAccent) {
142
142
  const palette = deriveAccentPalette(darkAccent);
143
143
  lines.push(".dark {");
144
- lines.push(` --color-fd-primary: ${palette.dark.accent};`);
145
- lines.push(` --color-fd-primary-foreground: ${textColorFor(palette.dark.accent)};`);
144
+ lines.push(` --color-fd-primary: ${darkAccent};`);
145
+ lines.push(` --color-fd-primary-foreground: ${textColorFor(darkAccent)};`);
146
146
  lines.push(` --color-fd-accent: ${palette.dark.accentLow};`);
147
147
  lines.push(` --color-fd-accent-foreground: ${textColorFor(palette.dark.accentLow)};`);
148
- lines.push(` --color-fd-ring: ${palette.dark.accent};`);
148
+ lines.push(` --color-fd-ring: ${darkAccent};`);
149
149
  lines.push("}");
150
150
  lines.push("");
151
151
  }