@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,69 @@ 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[] = [];
327
+ const hasNonMenuContent =
328
+ (Array.isArray(rawSection.groups) && rawSection.groups.length > 0) ||
329
+ (Array.isArray(rawSection.pages) && rawSection.pages.length > 0) ||
330
+ (Array.isArray(rawSection.anchors) && rawSection.anchors.length > 0) ||
331
+ (Array.isArray(rawSection.dropdowns) && rawSection.dropdowns.length > 0) ||
332
+ (Array.isArray(rawSection.tabs) && rawSection.tabs.length > 0);
207
333
 
208
334
  for (const item of Array.isArray(rawSection.menu) ? rawSection.menu : []) {
209
- if (isMenuItem(item)) entries.push(normalizeMenuItem(item, usedGroupSlugs));
335
+ // Menu is primarily for topbar dropdown navigation.
336
+ // Only materialize it into page-tree groups when this section has no normal content.
337
+ if (hasNonMenuContent) break;
338
+ if (isMenuItem(item)) entries.push(normalizeMenuItem(item, usedGroupSlugs, inheritedOpenApi, inheritedVersion));
210
339
  }
211
340
 
212
341
  for (const group of Array.isArray(rawSection.groups) ? rawSection.groups : []) {
213
- if (isGroupLike(group)) entries.push(normalizeGroup(group, usedGroupSlugs));
342
+ if (isGroupLike(group)) entries.push(normalizeGroup(group, usedGroupSlugs, inheritedOpenApi, inheritedVersion));
214
343
  }
215
344
 
216
345
  for (const item of Array.isArray(rawSection.pages) ? rawSection.pages : []) {
217
346
  if (typeof item === "string") entries.push(item);
218
347
  else if (isSeparator(item)) entries.push({ separator: item.separator });
219
348
  else if (isLink(item)) entries.push(normalizeLink(item));
220
- else if (isGroupLike(item)) entries.push(normalizeGroup(item, usedGroupSlugs));
349
+ else if (isGroupLike(item)) entries.push(normalizeGroup(item, usedGroupSlugs, inheritedOpenApi, inheritedVersion));
221
350
  }
222
351
 
223
352
  for (const anchor of Array.isArray(rawSection.anchors) ? rawSection.anchors : []) {
@@ -225,29 +354,42 @@ function collectEntries(rawSection: Record<string, unknown>, usedGroupSlugs: Set
225
354
 
226
355
  const hrefOnly = typeof anchor.href === "string" && anchor.href.length > 0 && !hasContent(anchor);
227
356
  if (hrefOnly) entries.push(normalizeAnchorLink(anchor));
228
- else entries.push(normalizeAnchorAsGroup(anchor, usedGroupSlugs));
357
+ else entries.push(normalizeAnchorAsGroup(anchor, usedGroupSlugs, inheritedOpenApi, inheritedVersion));
229
358
  }
230
359
 
231
360
  for (const dropdown of Array.isArray(rawSection.dropdowns) ? rawSection.dropdowns : []) {
232
- if (isDropdownLike(dropdown)) entries.push(normalizeDropdownAsGroup(dropdown, usedGroupSlugs));
361
+ if (isDropdownLike(dropdown)) entries.push(normalizeDropdownAsGroup(dropdown, usedGroupSlugs, inheritedOpenApi, inheritedVersion));
233
362
  }
234
363
 
235
364
  for (const tab of Array.isArray(rawSection.tabs) ? rawSection.tabs : []) {
236
- if (isTabLike(tab)) entries.push(normalizeTabAsGroup(tab, usedGroupSlugs));
365
+ if (isTabLike(tab)) entries.push(normalizeTabAsGroup(tab, usedGroupSlugs, inheritedOpenApi, inheritedVersion));
237
366
  }
238
367
 
239
368
  return entries;
240
369
  }
241
370
 
242
- function normalizeTab(rawTab: Record<string, unknown>, usedTabSlugs: Set<string>, slugPrefix: string): NavTab {
371
+ function normalizeTab(
372
+ rawTab: Record<string, unknown>,
373
+ usedTabSlugs: Set<string>,
374
+ slugPrefix: string,
375
+ inheritedOpenApi?: OpenApiValue,
376
+ inheritedVersion?: string,
377
+ ): NavTab {
243
378
  const tabName = typeof rawTab.tab === "string" ? rawTab.tab : "Tab";
244
379
  const rawSlug = typeof rawTab.slug === "string" ? rawTab.slug : tabName;
245
380
  const tabSlugPart = slugify(rawSlug, "tab");
246
381
  const fullSlug = slugPrefix ? `${slugPrefix}/${tabSlugPart}` : tabSlugPart;
247
382
  const slug = uniqueSlug(fullSlug, usedTabSlugs);
383
+ const openapi = resolveOpenApiValue(rawTab.openapi, inheritedOpenApi);
384
+ const asyncapi = normalizeOpenApiValue(rawTab.asyncapi);
385
+ const version = resolveVersionValue(rawTab.version, inheritedVersion);
248
386
 
249
387
  const out: NavTab = { tab: tabName, slug };
250
388
  if (typeof rawTab.icon === "string") out.icon = rawTab.icon;
389
+ if (typeof rawTab.iconType === "string") out.iconType = rawTab.iconType;
390
+ if (version !== undefined) out.version = version;
391
+ if (openapi !== undefined) out.openapi = openapi;
392
+ if (asyncapi !== undefined) out.asyncapi = asyncapi;
251
393
 
252
394
  if (typeof rawTab.href === "string" && rawTab.href.length > 0 && !hasContent(rawTab)) {
253
395
  out.href = rawTab.href;
@@ -255,7 +397,7 @@ function normalizeTab(rawTab: Record<string, unknown>, usedTabSlugs: Set<string>
255
397
  }
256
398
 
257
399
  const groupSlugSet = new Set<string>();
258
- const entries = collectEntries(rawTab, groupSlugSet);
400
+ const entries = collectEntries(rawTab, groupSlugSet, openapi, version);
259
401
  const groups: NavGroup[] = [];
260
402
  const pages: Array<string | NavSeparator | NavLink> = [];
261
403
 
@@ -269,57 +411,92 @@ function normalizeTab(rawTab: Record<string, unknown>, usedTabSlugs: Set<string>
269
411
  return out;
270
412
  }
271
413
 
272
- function normalizeDropdownToTab(rawDropdown: Record<string, unknown>, usedTabSlugs: Set<string>, slugPrefix: string): NavTab {
414
+ function normalizeDropdownToTab(
415
+ rawDropdown: Record<string, unknown>,
416
+ usedTabSlugs: Set<string>,
417
+ slugPrefix: string,
418
+ inheritedOpenApi?: OpenApiValue,
419
+ inheritedVersion?: string,
420
+ ): NavTab {
273
421
  return normalizeTab(
274
422
  {
275
423
  tab: rawDropdown.dropdown,
276
424
  slug: rawDropdown.slug,
277
425
  icon: rawDropdown.icon,
426
+ iconType: rawDropdown.iconType,
427
+ version: rawDropdown.version,
278
428
  href: rawDropdown.href,
279
429
  groups: rawDropdown.groups,
280
430
  pages: rawDropdown.pages,
281
431
  menu: rawDropdown.menu,
282
432
  anchors: rawDropdown.anchors,
283
433
  dropdowns: rawDropdown.dropdowns,
284
- tabs: rawDropdown.tabs,
285
- },
434
+ tabs: rawDropdown.tabs,
435
+ openapi: rawDropdown.openapi,
436
+ asyncapi: rawDropdown.asyncapi,
437
+ },
286
438
  usedTabSlugs,
287
- slugPrefix
439
+ slugPrefix,
440
+ inheritedOpenApi,
441
+ inheritedVersion
288
442
  );
289
443
  }
290
444
 
291
- function normalizeTabList(rawTabs: unknown[], usedTabSlugs: Set<string>, slugPrefix = ""): NavTab[] {
445
+ function normalizeTabList(
446
+ rawTabs: unknown[],
447
+ usedTabSlugs: Set<string>,
448
+ slugPrefix = "",
449
+ inheritedOpenApi?: OpenApiValue,
450
+ inheritedVersion?: string,
451
+ ): NavTab[] {
292
452
  const tabs: NavTab[] = [];
293
453
  for (const item of rawTabs) {
294
- if (isTabLike(item)) tabs.push(normalizeTab(item, usedTabSlugs, slugPrefix));
454
+ if (isTabLike(item)) tabs.push(normalizeTab(item, usedTabSlugs, slugPrefix, inheritedOpenApi, inheritedVersion));
295
455
  }
296
456
  return tabs;
297
457
  }
298
458
 
299
- function normalizeDropdownList(rawDropdowns: unknown[], usedTabSlugs: Set<string>, slugPrefix = ""): NavTab[] {
459
+ function normalizeDropdownList(
460
+ rawDropdowns: unknown[],
461
+ usedTabSlugs: Set<string>,
462
+ slugPrefix = "",
463
+ inheritedOpenApi?: OpenApiValue,
464
+ inheritedVersion?: string,
465
+ ): NavTab[] {
300
466
  const tabs: NavTab[] = [];
301
467
  for (const item of rawDropdowns) {
302
- if (isDropdownLike(item)) tabs.push(normalizeDropdownToTab(item, usedTabSlugs, slugPrefix));
468
+ if (isDropdownLike(item)) tabs.push(normalizeDropdownToTab(item, usedTabSlugs, slugPrefix, inheritedOpenApi, inheritedVersion));
303
469
  }
304
- return tabs;
470
+ if (inheritedOpenApi === undefined && inheritedVersion === undefined) return tabs;
471
+ return tabs.map((tab) => ({
472
+ ...tab,
473
+ openapi: tab.openapi ?? inheritedOpenApi,
474
+ version: tab.version ?? inheritedVersion,
475
+ }));
305
476
  }
306
477
 
307
- function normalizeNavigationTabs(navigation: unknown, usedTabSlugs = new Set<string>()): NavTab[] {
478
+ function normalizeNavigationTabs(
479
+ navigation: unknown,
480
+ usedTabSlugs = new Set<string>(),
481
+ inheritedOpenApi?: OpenApiValue,
482
+ ): NavTab[] {
308
483
  if (!isObject(navigation)) return [];
309
484
 
485
+ const sectionOpenApi = resolveOpenApiValue(navigation.openapi, inheritedOpenApi);
310
486
  const tabs: NavTab[] = [];
311
487
 
312
- tabs.push(...normalizeTabList(Array.isArray(navigation.tabs) ? navigation.tabs : [], usedTabSlugs));
313
- tabs.push(...normalizeDropdownList(Array.isArray(navigation.dropdowns) ? navigation.dropdowns : [], usedTabSlugs));
488
+ tabs.push(...normalizeTabList(Array.isArray(navigation.tabs) ? navigation.tabs : [], usedTabSlugs, "", sectionOpenApi));
489
+ tabs.push(...normalizeDropdownList(Array.isArray(navigation.dropdowns) ? navigation.dropdowns : [], usedTabSlugs, "", sectionOpenApi));
314
490
 
315
491
  if (Array.isArray(navigation.products)) {
316
492
  navigation.products.forEach((product, index) => {
317
493
  if (!isObject(product)) return;
318
494
  const productName = typeof product.product === "string" ? product.product : `Product ${index + 1}`;
319
495
  const prefix = slugify(productName, `product-${index + 1}`);
496
+ const productOpenApi = resolveOpenApiValue(product.openapi, sectionOpenApi);
320
497
 
321
- tabs.push(...normalizeTabList(Array.isArray(product.tabs) ? product.tabs : [], usedTabSlugs, prefix));
322
- tabs.push(...normalizeDropdownList(Array.isArray(product.dropdowns) ? product.dropdowns : [], usedTabSlugs, prefix));
498
+ tabs.push(...normalizeTabList(Array.isArray(product.tabs) ? product.tabs : [], usedTabSlugs, prefix, productOpenApi));
499
+ tabs.push(...normalizeDropdownList(Array.isArray(product.dropdowns) ? product.dropdowns : [], usedTabSlugs, prefix, productOpenApi));
323
500
 
324
501
  if (!Array.isArray(product.tabs) && !Array.isArray(product.dropdowns)) {
325
502
  if (hasContent(product)) {
@@ -329,15 +506,19 @@ function normalizeNavigationTabs(navigation: unknown, usedTabSlugs = new Set<str
329
506
  tab: productName,
330
507
  slug: prefix,
331
508
  icon: product.icon,
509
+ iconType: product.iconType,
332
510
  groups: product.groups,
333
511
  pages: product.pages,
334
512
  menu: product.menu,
335
513
  anchors: product.anchors,
336
514
  dropdowns: product.dropdowns,
337
515
  tabs: product.tabs,
338
- },
516
+ openapi: productOpenApi,
517
+ asyncapi: normalizeOpenApiValue(product.asyncapi),
518
+ },
339
519
  usedTabSlugs,
340
- ""
520
+ "",
521
+ productOpenApi
341
522
  )
342
523
  );
343
524
  } else if (typeof product.href === "string" && product.href.length > 0) {
@@ -347,10 +528,14 @@ function normalizeNavigationTabs(navigation: unknown, usedTabSlugs = new Set<str
347
528
  tab: productName,
348
529
  slug: prefix,
349
530
  icon: product.icon,
531
+ iconType: product.iconType,
350
532
  href: product.href,
351
- },
533
+ openapi: productOpenApi,
534
+ asyncapi: normalizeOpenApiValue(product.asyncapi),
535
+ },
352
536
  usedTabSlugs,
353
- ""
537
+ "",
538
+ productOpenApi
354
539
  )
355
540
  );
356
541
  }
@@ -363,9 +548,10 @@ function normalizeNavigationTabs(navigation: unknown, usedTabSlugs = new Set<str
363
548
  if (!isObject(version)) return;
364
549
  const versionName = typeof version.version === "string" ? version.version : `Version ${index + 1}`;
365
550
  const prefix = slugify(versionName, `version-${index + 1}`);
551
+ const versionOpenApi = resolveOpenApiValue(version.openapi, sectionOpenApi);
366
552
 
367
- tabs.push(...normalizeTabList(Array.isArray(version.tabs) ? version.tabs : [], usedTabSlugs, prefix));
368
- tabs.push(...normalizeDropdownList(Array.isArray(version.dropdowns) ? version.dropdowns : [], usedTabSlugs, prefix));
553
+ tabs.push(...normalizeTabList(Array.isArray(version.tabs) ? version.tabs : [], usedTabSlugs, prefix, versionOpenApi));
554
+ tabs.push(...normalizeDropdownList(Array.isArray(version.dropdowns) ? version.dropdowns : [], usedTabSlugs, prefix, versionOpenApi));
369
555
 
370
556
  if (!Array.isArray(version.tabs) && !Array.isArray(version.dropdowns)) {
371
557
  if (hasContent(version)) {
@@ -380,9 +566,12 @@ function normalizeNavigationTabs(navigation: unknown, usedTabSlugs = new Set<str
380
566
  anchors: version.anchors,
381
567
  dropdowns: version.dropdowns,
382
568
  tabs: version.tabs,
383
- },
569
+ openapi: versionOpenApi,
570
+ asyncapi: normalizeOpenApiValue(version.asyncapi),
571
+ },
384
572
  usedTabSlugs,
385
- ""
573
+ "",
574
+ versionOpenApi
386
575
  )
387
576
  );
388
577
  } else if (typeof version.href === "string" && version.href.length > 0) {
@@ -392,9 +581,12 @@ function normalizeNavigationTabs(navigation: unknown, usedTabSlugs = new Set<str
392
581
  tab: versionName,
393
582
  slug: prefix,
394
583
  href: version.href,
395
- },
584
+ openapi: versionOpenApi,
585
+ asyncapi: normalizeOpenApiValue(version.asyncapi),
586
+ },
396
587
  usedTabSlugs,
397
- ""
588
+ "",
589
+ versionOpenApi
398
590
  )
399
591
  );
400
592
  }
@@ -408,9 +600,11 @@ function normalizeNavigationTabs(navigation: unknown, usedTabSlugs = new Set<str
408
600
 
409
601
  const anchorName = typeof anchor.anchor === "string" ? anchor.anchor : `Anchor ${index + 1}`;
410
602
  const prefix = slugify(anchorName, `anchor-${index + 1}`);
603
+ const anchorOpenApi = resolveOpenApiValue(anchor.openapi, sectionOpenApi);
604
+ const anchorVersion = resolveVersionValue(anchor.version);
411
605
 
412
606
  if (Array.isArray(anchor.tabs)) {
413
- tabs.push(...normalizeTabList(anchor.tabs, usedTabSlugs, prefix));
607
+ tabs.push(...normalizeTabList(anchor.tabs, usedTabSlugs, prefix, anchorOpenApi, anchorVersion));
414
608
  } else if (hasContent(anchor)) {
415
609
  tabs.push(
416
610
  normalizeTab(
@@ -418,15 +612,21 @@ function normalizeNavigationTabs(navigation: unknown, usedTabSlugs = new Set<str
418
612
  tab: anchorName,
419
613
  slug: prefix,
420
614
  icon: anchor.icon,
615
+ iconType: anchor.iconType,
616
+ version: anchorVersion,
421
617
  groups: anchor.groups,
422
618
  pages: anchor.pages,
423
619
  menu: anchor.menu,
424
620
  anchors: anchor.anchors,
425
621
  dropdowns: anchor.dropdowns,
426
622
  tabs: anchor.tabs,
623
+ openapi: anchorOpenApi,
624
+ asyncapi: normalizeOpenApiValue(anchor.asyncapi),
427
625
  },
428
626
  usedTabSlugs,
429
- ""
627
+ "",
628
+ anchorOpenApi,
629
+ anchorVersion
430
630
  )
431
631
  );
432
632
  }
@@ -449,38 +649,45 @@ function normalizeNavigationTabs(navigation: unknown, usedTabSlugs = new Set<str
449
649
  anchors: navigation.anchors,
450
650
  dropdowns: navigation.dropdowns,
451
651
  tabs: navigation.tabs,
652
+ openapi: sectionOpenApi,
653
+ asyncapi: normalizeOpenApiValue(navigation.asyncapi),
452
654
  },
453
655
  usedTabSlugs,
454
- ""
656
+ "",
657
+ sectionOpenApi
455
658
  )
456
659
  );
457
660
  }
458
661
 
459
662
  return tabs;
460
663
  }
461
-
462
- function normalizeLanguageEntries(languages: unknown): Array<Record<string, unknown>> {
664
+ function normalizeLanguageEntries(languages: unknown, inheritedOpenApi?: OpenApiValue): Array<Record<string, unknown>> {
463
665
  if (!Array.isArray(languages)) return [];
464
666
 
465
667
  return languages
466
668
  .filter(isObject)
467
669
  .map((entry) => {
468
670
  const usedTabSlugs = new Set<string>();
671
+ const languageOpenApi = resolveOpenApiValue(entry.openapi, inheritedOpenApi);
469
672
  return {
470
673
  ...entry,
471
- tabs: normalizeNavigationTabs(entry, usedTabSlugs),
674
+ tabs: normalizeNavigationTabs(entry, usedTabSlugs, languageOpenApi),
472
675
  };
473
676
  });
474
677
  }
475
678
 
476
679
  export function normalizeConfigNavigation<T extends { navigation?: unknown }>(config: T): T {
477
680
  const nav = isObject(config.navigation) ? config.navigation : {};
681
+ const navOpenApi = resolveOpenApiValue((nav as Record<string, unknown>).openapi);
682
+ const navAsyncApi = normalizeOpenApiValue((nav as Record<string, unknown>).asyncapi);
478
683
  return {
479
684
  ...config,
480
685
  navigation: {
481
686
  ...nav,
482
- tabs: normalizeNavigationTabs(nav),
483
- languages: normalizeLanguageEntries(nav.languages),
687
+ ...(navOpenApi !== undefined ? { openapi: navOpenApi } : {}),
688
+ ...(navAsyncApi !== undefined ? { asyncapi: navAsyncApi } : {}),
689
+ tabs: normalizeNavigationTabs(nav, new Set<string>(), navOpenApi),
690
+ languages: normalizeLanguageEntries(nav.languages, navOpenApi),
484
691
  products: Array.isArray(nav.products) ? nav.products : [],
485
692
  versions: Array.isArray(nav.versions) ? nav.versions : [],
486
693
  },