@fern-api/fdr-sdk 1.2.38-cc77d6a840 → 1.2.39-2432856e2e

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 (36) hide show
  1. package/dist/js/client/FdrClient.js +28 -7
  2. package/dist/js/client/FdrClient.js.map +1 -1
  3. package/dist/js/client/FdrClient.mjs +28 -7
  4. package/dist/js/client/FdrClient.mjs.map +1 -1
  5. package/dist/js/index.js +28 -7
  6. package/dist/js/index.js.map +1 -1
  7. package/dist/js/index.mjs +28 -7
  8. package/dist/js/index.mjs.map +1 -1
  9. package/dist/js/navigation/ledger-root-builder.js +465 -64
  10. package/dist/js/navigation/ledger-root-builder.js.map +1 -1
  11. package/dist/js/navigation/ledger-root-builder.mjs +465 -64
  12. package/dist/js/navigation/ledger-root-builder.mjs.map +1 -1
  13. package/dist/js/orpc-client.js +30 -7
  14. package/dist/js/orpc-client.js.map +1 -1
  15. package/dist/js/orpc-client.mjs +30 -7
  16. package/dist/js/orpc-client.mjs.map +1 -1
  17. package/dist/tsconfig.tsbuildinfo +1 -1
  18. package/dist/types/navigation/__test__/ledger-root-builder.apiReferenceChildren.test.d.ts +2 -0
  19. package/dist/types/navigation/__test__/ledger-root-builder.apiReferenceChildren.test.d.ts.map +1 -0
  20. package/dist/types/navigation/__test__/ledger-root-builder.landingPage.test.d.ts +2 -0
  21. package/dist/types/navigation/__test__/ledger-root-builder.landingPage.test.d.ts.map +1 -0
  22. package/dist/types/navigation/__test__/ledger-root-builder.metadataPreservation.test.d.ts +2 -0
  23. package/dist/types/navigation/__test__/ledger-root-builder.metadataPreservation.test.d.ts.map +1 -0
  24. package/dist/types/navigation/__test__/ledger-root-builder.rootSlug.test.d.ts +2 -0
  25. package/dist/types/navigation/__test__/ledger-root-builder.rootSlug.test.d.ts.map +1 -0
  26. package/dist/types/navigation/__test__/ledger-root-builder.skipSlugTabs.test.d.ts +2 -0
  27. package/dist/types/navigation/__test__/ledger-root-builder.skipSlugTabs.test.d.ts.map +1 -0
  28. package/dist/types/navigation/__test__/ledger-root-builder.unversionedPointsTo.test.d.ts +2 -0
  29. package/dist/types/navigation/__test__/ledger-root-builder.unversionedPointsTo.test.d.ts.map +1 -0
  30. package/dist/types/navigation/ledger-root-builder.d.ts +12 -0
  31. package/dist/types/navigation/ledger-root-builder.d.ts.map +1 -1
  32. package/dist/types/orpc-client/docs-ledger/contract.d.ts +2190 -170
  33. package/dist/types/orpc-client/docs-ledger/contract.d.ts.map +1 -1
  34. package/dist/types/orpc-client/docs-ledger/ledger-manifest.d.ts +3456 -104
  35. package/dist/types/orpc-client/docs-ledger/ledger-manifest.d.ts.map +1 -1
  36. package/package.json +3 -3
@@ -94,6 +94,9 @@ var AuthSchemeIdSchema = z2.string();
94
94
  var SubpackageIdSchema = z2.string();
95
95
  var WebSocketMessageIdSchema = z2.string();
96
96
  var FileIdSchema = z2.string();
97
+ function FileId(value) {
98
+ return value;
99
+ }
97
100
  var MultipleAuthTypeSchema = z2.object({
98
101
  schemes: z2.array(AuthSchemeIdSchema)
99
102
  });
@@ -657,6 +660,77 @@ function getChildren(node) {
657
660
  }
658
661
  }
659
662
 
663
+ // ../commons/core-utils/dist/assertNever.js
664
+ function assertNever(x) {
665
+ throw new Error("Unexpected value: " + JSON.stringify(x));
666
+ }
667
+
668
+ // ../commons/core-utils/dist/visitDiscriminatedUnion.js
669
+ function visitDiscriminatedUnion(item, discriminant = "type") {
670
+ return {
671
+ _visit: (visitor) => {
672
+ const visit = visitor[item[discriminant]];
673
+ if (visit != null) {
674
+ return visit(item);
675
+ } else {
676
+ if (visitor._other == null) {
677
+ assertNever(item);
678
+ }
679
+ return visitor._other(item);
680
+ }
681
+ }
682
+ };
683
+ }
684
+ var visitDiscriminatedUnion_default = visitDiscriminatedUnion;
685
+
686
+ // src/navigation/versions/latest/getPageId.ts
687
+ var RETURN_PAGEID = (node) => node.pageId;
688
+ var RETURN_OVERVIEW_PAGEID = (node) => node.overviewPageId;
689
+ var RETURN_UNDEFINED = () => void 0;
690
+ function getPageId(node) {
691
+ return visitDiscriminatedUnion_default(node)._visit({
692
+ section: RETURN_OVERVIEW_PAGEID,
693
+ apiReference: RETURN_OVERVIEW_PAGEID,
694
+ changelog: RETURN_OVERVIEW_PAGEID,
695
+ apiPackage: RETURN_OVERVIEW_PAGEID,
696
+ changelogEntry: RETURN_PAGEID,
697
+ page: RETURN_PAGEID,
698
+ landingPage: RETURN_PAGEID,
699
+ // if the following changes, make sure to update the algolia records generator
700
+ endpoint: RETURN_UNDEFINED,
701
+ webSocket: RETURN_UNDEFINED,
702
+ webhook: RETURN_UNDEFINED,
703
+ grpc: RETURN_UNDEFINED,
704
+ graphql: RETURN_UNDEFINED,
705
+ graphqlType: RETURN_UNDEFINED
706
+ // changelogYear: RETURN_UNDEFINED,
707
+ // changelogMonth: RETURN_UNDEFINED,
708
+ });
709
+ }
710
+
711
+ // src/navigation/versions/latest/NavigationNodeSection.ts
712
+ function isSection(node) {
713
+ return node.type === "section" || node.type === "apiReference" || node.type === "changelog" || node.type === "apiPackage";
714
+ }
715
+
716
+ // src/navigation/versions/latest/NavigationNodeSectionOverview.ts
717
+ function isSectionOverview(node) {
718
+ return isSection(node) && node.overviewPageId != null;
719
+ }
720
+
721
+ // src/navigation/versions/latest/NavigationNodeMarkdown.ts
722
+ function hasMarkdown(node) {
723
+ return isMarkdownLeaf(node) || isSectionOverview(node);
724
+ }
725
+
726
+ // src/navigation/versions/latest/NavigationNodePage.ts
727
+ function isPage(node) {
728
+ return isApiLeaf(node) || node.type === "changelog" || // TODO: Uncomment when changelog years and months are visitable
729
+ // node.type === "changelogYear" ||
730
+ // node.type === "changelogMonth" ||
731
+ hasMarkdown(node);
732
+ }
733
+
660
734
  // src/navigation/versions/latest/NavigationNodeWithMetadata.ts
661
735
  function hasMetadata(node) {
662
736
  return typeof node.title === "string" && typeof node.slug === "string";
@@ -794,6 +868,13 @@ function metaStrArr(metadata, key) {
794
868
  }
795
869
  return void 0;
796
870
  }
871
+ function metaFeatureFlags(metadata) {
872
+ if (metadata != null && typeof metadata === "object" && "featureFlags" in metadata) {
873
+ const v = metadata.featureFlags;
874
+ return Array.isArray(v) && v.length > 0 ? v : void 0;
875
+ }
876
+ return void 0;
877
+ }
797
878
  function toViewers(viewers) {
798
879
  if (viewers == null || viewers.length === 0) {
799
880
  return void 0;
@@ -829,17 +910,17 @@ function scopeKey(seg) {
829
910
  return [seg.product?.id ?? "", seg.version?.id ?? "", seg.variant?.id ?? "", seg.tab?.id ?? ""].join("|");
830
911
  }
831
912
  function withMetadataDefaults(base) {
832
- const { viewers, ...rest } = base;
913
+ const { viewers, featureFlags, ...rest } = base;
833
914
  return {
834
915
  ...rest,
835
916
  canonicalSlug: void 0,
836
- authed: void 0,
917
+ authed: base.authed === true ? true : void 0,
837
918
  collapsed: void 0,
838
919
  // Normalise empty arrays to undefined so unrestricted nodes don't add
839
920
  // filter-level noise; a non-empty array engages the RBAC view gate.
840
921
  viewers: viewers != null && viewers.length > 0 ? viewers : void 0,
841
- orphaned: void 0,
842
- featureFlags: void 0
922
+ orphaned: base.orphaned === true ? true : void 0,
923
+ featureFlags: featureFlags != null && featureFlags.length > 0 ? featureFlags : void 0
843
924
  };
844
925
  }
845
926
  function artifactToNode(ctx, route) {
@@ -862,7 +943,10 @@ function artifactToNode(ctx, route) {
862
943
  slug,
863
944
  icon,
864
945
  hidden: route.hidden,
865
- viewers
946
+ authed: metaBool(md, "authed"),
947
+ viewers,
948
+ orphaned: metaBool(md, "orphaned"),
949
+ featureFlags: metaFeatureFlags(md)
866
950
  }),
867
951
  pageId: PageId(pageId),
868
952
  noindex: metaBool(md, "noindex"),
@@ -878,7 +962,7 @@ function artifactToNode(ctx, route) {
878
962
  title,
879
963
  icon,
880
964
  url: Url(metaStr(md, "url") ?? ""),
881
- target: void 0
965
+ target: metaStr(md, "target")
882
966
  };
883
967
  return node;
884
968
  }
@@ -913,7 +997,10 @@ function apiLeafNodeFromNavRouteInternal(ctx, route, segmentApiDefinitionId2) {
913
997
  slug,
914
998
  icon,
915
999
  hidden: route.hidden,
916
- viewers
1000
+ authed: metaBool(md, "authed"),
1001
+ viewers,
1002
+ orphaned: metaBool(md, "orphaned"),
1003
+ featureFlags: metaFeatureFlags(md)
917
1004
  });
918
1005
  if (route.type === "rest") {
919
1006
  const endpointId = metaStr(md, "endpointId");
@@ -928,7 +1015,7 @@ function apiLeafNodeFromNavRouteInternal(ctx, route, segmentApiDefinitionId2) {
928
1015
  endpointId: EndpointId(endpointId),
929
1016
  apiDefinitionId,
930
1017
  isResponseStream: metaBool(md, "isResponseStream"),
931
- availability: void 0,
1018
+ availability: metaStr(md, "availability"),
932
1019
  playground: void 0
933
1020
  };
934
1021
  }
@@ -944,7 +1031,7 @@ function apiLeafNodeFromNavRouteInternal(ctx, route, segmentApiDefinitionId2) {
944
1031
  method,
945
1032
  webhookId: WebhookId(webhookId),
946
1033
  apiDefinitionId,
947
- availability: void 0
1034
+ availability: metaStr(md, "availability")
948
1035
  };
949
1036
  }
950
1037
  if (route.type === "asyncapi") {
@@ -957,7 +1044,7 @@ function apiLeafNodeFromNavRouteInternal(ctx, route, segmentApiDefinitionId2) {
957
1044
  ...meta,
958
1045
  webSocketId: WebSocketId(webSocketId),
959
1046
  apiDefinitionId,
960
- availability: void 0,
1047
+ availability: metaStr(md, "availability"),
961
1048
  playground: void 0
962
1049
  };
963
1050
  }
@@ -973,7 +1060,7 @@ function apiLeafNodeFromNavRouteInternal(ctx, route, segmentApiDefinitionId2) {
973
1060
  method,
974
1061
  grpcId: GrpcId(grpcId),
975
1062
  apiDefinitionId,
976
- availability: void 0
1063
+ availability: metaStr(md, "availability")
977
1064
  };
978
1065
  }
979
1066
  if (route.type === "graphql") {
@@ -989,12 +1076,69 @@ function apiLeafNodeFromNavRouteInternal(ctx, route, segmentApiDefinitionId2) {
989
1076
  graphqlOperationId: GraphQlOperationId(graphqlOperationId),
990
1077
  graphqlOperationIds: void 0,
991
1078
  apiDefinitionId,
992
- availability: void 0,
1079
+ availability: metaStr(md, "availability"),
993
1080
  playground: void 0
994
1081
  };
995
1082
  }
996
1083
  return void 0;
997
1084
  }
1085
+ function apiLeafPositionKey(node) {
1086
+ switch (node.type) {
1087
+ case "endpoint":
1088
+ return `endpoint:${node.endpointId}`;
1089
+ case "webhook":
1090
+ return `webhook:${node.webhookId}`;
1091
+ case "webSocket":
1092
+ return `webSocket:${node.webSocketId}`;
1093
+ case "grpc":
1094
+ return `grpc:${node.grpcId}`;
1095
+ case "graphql":
1096
+ return `graphql:${node.graphqlOperationId}`;
1097
+ }
1098
+ }
1099
+ function apiChildPositionKey(node) {
1100
+ switch (node.type) {
1101
+ case "endpoint":
1102
+ case "webhook":
1103
+ case "webSocket":
1104
+ case "grpc":
1105
+ case "graphql":
1106
+ return apiLeafPositionKey(node);
1107
+ default:
1108
+ return void 0;
1109
+ }
1110
+ }
1111
+ function positionedApiLeafChildren(ctx, nav, apiDefinitionId) {
1112
+ const rawLeafNodes = [];
1113
+ const leafPositions = /* @__PURE__ */ new Map();
1114
+ const nonApiLeafNodes = [];
1115
+ for (let i = 0; i < nav.length; i++) {
1116
+ const route = nav[i];
1117
+ const pos = route.displaySortOrder ?? i;
1118
+ const apiLeaf = apiLeafNodeFromNavRouteInternal(ctx, route, apiDefinitionId);
1119
+ if (apiLeaf != null) {
1120
+ rawLeafNodes.push(apiLeaf);
1121
+ const key = apiLeafPositionKey(apiLeaf);
1122
+ leafPositions.set(key, Math.min(leafPositions.get(key) ?? pos, pos));
1123
+ continue;
1124
+ }
1125
+ const node = artifactToNode(ctx, route);
1126
+ if (node?.type === "page" || node?.type === "link") {
1127
+ nonApiLeafNodes.push({ node, pos });
1128
+ }
1129
+ }
1130
+ const apiLeafNodes = mergeEndpointPairs(ctx, rawLeafNodes, nav);
1131
+ const positionedApiLeafNodes = apiLeafNodes.map((node, index) => {
1132
+ if (node.type === "endpointPair") {
1133
+ const streamPos = leafPositions.get(apiLeafPositionKey(node.stream)) ?? index;
1134
+ const nonStreamPos = leafPositions.get(apiLeafPositionKey(node.nonStream)) ?? index;
1135
+ return { node, pos: Math.min(streamPos, nonStreamPos) };
1136
+ }
1137
+ const key = apiChildPositionKey(node);
1138
+ return { node, pos: key != null ? leafPositions.get(key) ?? index : index };
1139
+ });
1140
+ return [...positionedApiLeafNodes, ...nonApiLeafNodes];
1141
+ }
998
1142
  function collectApiLeafNodesFromNav(routes, basePath, segmentApiDefinitionId2) {
999
1143
  const ctx = createBuildContext(basePath);
1000
1144
  return routes.map((route) => apiLeafNodeFromNavRouteInternal(ctx, route, segmentApiDefinitionId2)).filter((node) => node != null);
@@ -1084,8 +1228,9 @@ function sectionNode(ctx, scoped, sectionChildren, scopePrefix) {
1084
1228
  const { seg, nav } = scoped;
1085
1229
  const overview = overviewRoute(nav) ?? overviewRouteFromSegmentMeta(seg, nav);
1086
1230
  const overviewPageId = overview != null ? metaStr(overview.metadata, "pageId") : void 0;
1087
- let slug = structuralNodeSlug(ctx, overview?.fullPath, seg.section, scopePrefix);
1088
- if (seg.section === "" && overview?.fullPath == null) {
1231
+ const relSection = stripLeadingScopePrefix(seg.section, scopePrefix);
1232
+ let slug = structuralNodeSlug(ctx, overview?.fullPath, relSection, scopePrefix);
1233
+ if (relSection === "" && overview?.fullPath == null) {
1089
1234
  slug = inferSectionSlugFromNav(ctx, nav, scopePrefix) ?? slug;
1090
1235
  }
1091
1236
  const metaPointsTo = metaStr(seg.metadata, "pointsTo");
@@ -1095,17 +1240,22 @@ function sectionNode(ctx, scoped, sectionChildren, scopePrefix) {
1095
1240
  slug,
1096
1241
  icon: metaStr(seg.metadata, "icon"),
1097
1242
  hidden: seg.hidden,
1098
- viewers: mergeVariantViewers(seg)
1243
+ authed: metaBool(seg.metadata, "authed"),
1244
+ viewers: mergeVariantViewers(seg),
1245
+ orphaned: metaBool(seg.metadata, "orphaned"),
1246
+ featureFlags: metaFeatureFlags(seg.metadata)
1099
1247
  });
1100
1248
  return {
1101
1249
  type: "section",
1102
1250
  ...defaults,
1103
- collapsed: metaCollapsed(seg.metadata) ?? defaults.collapsed,
1251
+ // V2 convention: collapsed:false in docs.yml maps to "open-by-default"
1252
+ // so getInitiallyOpenByDefaultNodes picks it up and the UI starts open.
1253
+ collapsed: metaCollapsed(seg.metadata) === false ? "open-by-default" : metaCollapsed(seg.metadata) ?? defaults.collapsed,
1104
1254
  overviewPageId: overviewPageId != null ? PageId(overviewPageId) : void 0,
1105
- noindex: void 0,
1255
+ noindex: metaBool(overview?.metadata, "noindex") ?? metaBool(seg.metadata, "noindex"),
1106
1256
  collapsible: metaBool(seg.metadata, "collapsible") ?? (metaCollapsed(seg.metadata) != null ? true : void 0),
1107
1257
  collapsedByDefault: metaBool(seg.metadata, "collapsedByDefault") ?? (metaCollapsed(seg.metadata) === true ? true : void 0),
1108
- availability: void 0,
1258
+ availability: metaStr(seg.metadata, "availability"),
1109
1259
  pointsTo: metaPointsTo != null ? pointsToSlug(metaPointsTo) : void 0,
1110
1260
  children: mergeChildrenByPosition(ctx, nav, sectionChildren)
1111
1261
  };
@@ -1128,8 +1278,7 @@ function directChildApiPackages(parentSection, scopeSegs) {
1128
1278
  }
1129
1279
  function buildApiChildren(ctx, scoped, scopeSegs, apiDefinitionId, scopePrefix) {
1130
1280
  const { seg, nav } = scoped;
1131
- const rawLeafNodes = nav.map((route) => apiLeafNodeFromNavRouteInternal(ctx, route, apiDefinitionId)).filter((node) => node != null);
1132
- const leafNodes = mergeEndpointPairs(ctx, rawLeafNodes, nav);
1281
+ const leafNodes = positionedApiLeafChildren(ctx, nav, apiDefinitionId);
1133
1282
  const childPackages = directChildApiPackages(seg.section, scopeSegs);
1134
1283
  const packageNodes = childPackages.map((child) => ({
1135
1284
  node: apiPackageChildNode(
@@ -1143,18 +1292,10 @@ function buildApiChildren(ctx, scoped, scopeSegs, apiDefinitionId, scopePrefix)
1143
1292
  }));
1144
1293
  const hasChildIndex = packageNodes.some((p) => p.childIndex != null);
1145
1294
  if (!hasChildIndex) {
1146
- return [...leafNodes, ...packageNodes.map((p) => p.node)];
1147
- }
1148
- const leafPositions = [];
1149
- for (const route of nav) {
1150
- if (route.displaySortOrder != null) {
1151
- leafPositions.push(route.displaySortOrder);
1152
- }
1295
+ return [...leafNodes, ...packageNodes.map((p) => ({ node: p.node, pos: Number.MAX_SAFE_INTEGER }))].sort((a, b) => a.pos - b.pos).map((p) => p.node);
1153
1296
  }
1154
1297
  const positioned = [];
1155
- for (let i = 0; i < leafNodes.length; i++) {
1156
- positioned.push({ node: leafNodes[i], pos: leafPositions[i] ?? i });
1157
- }
1298
+ positioned.push(...leafNodes);
1158
1299
  for (const p of packageNodes) {
1159
1300
  positioned.push({ node: p.node, pos: p.childIndex ?? Number.MAX_SAFE_INTEGER });
1160
1301
  }
@@ -1166,7 +1307,12 @@ function apiPackageChildNode(ctx, scoped, scopeSegs, parentApiDefinitionId, scop
1166
1307
  const overview = overviewRoute(nav);
1167
1308
  const overviewPageId = overview != null ? metaStr(overview.metadata, "pageId") : void 0;
1168
1309
  const apiDefinitionId = metaStr(seg.metadata, "apiDefinitionId") ?? parentApiDefinitionId;
1169
- const slug = structuralNodeSlug(ctx, overview?.fullPath, seg.section, scopePrefix);
1310
+ const slug = structuralNodeSlug(
1311
+ ctx,
1312
+ overview?.fullPath,
1313
+ stripLeadingScopePrefix(seg.section, scopePrefix),
1314
+ scopePrefix
1315
+ );
1170
1316
  const metaPointsTo = metaStr(seg.metadata, "pointsTo");
1171
1317
  const children = buildApiChildren(ctx, scoped, scopeSegs, apiDefinitionId, scopePrefix);
1172
1318
  return {
@@ -1177,12 +1323,15 @@ function apiPackageChildNode(ctx, scoped, scopeSegs, parentApiDefinitionId, scop
1177
1323
  slug,
1178
1324
  icon: metaStr(seg.metadata, "icon"),
1179
1325
  hidden: seg.hidden,
1180
- viewers: toViewers(metaStrArr(seg.metadata, "viewers"))
1326
+ authed: metaBool(seg.metadata, "authed"),
1327
+ viewers: toViewers(metaStrArr(seg.metadata, "viewers")),
1328
+ orphaned: metaBool(seg.metadata, "orphaned"),
1329
+ featureFlags: metaFeatureFlags(seg.metadata)
1181
1330
  }),
1182
1331
  apiDefinitionId: ApiDefinitionId(apiDefinitionId),
1183
1332
  availability: metaStr(seg.metadata, "availability"),
1184
1333
  overviewPageId: overviewPageId != null ? PageId(overviewPageId) : void 0,
1185
- noindex: void 0,
1334
+ noindex: metaBool(overview?.metadata, "noindex") ?? metaBool(seg.metadata, "noindex"),
1186
1335
  pointsTo: metaPointsTo != null ? pointsToSlug(metaPointsTo) : void 0,
1187
1336
  children,
1188
1337
  playground: void 0
@@ -1245,7 +1394,12 @@ function apiReferenceNode(ctx, scoped, scopeSegs, scopePrefix) {
1245
1394
  const overview = overviewRoute(nav);
1246
1395
  const overviewPageId = overview != null ? metaStr(overview.metadata, "pageId") : void 0;
1247
1396
  const apiDefinitionId = metaStr(seg.metadata, "apiDefinitionId") ?? "";
1248
- const slug = structuralNodeSlug(ctx, overview?.fullPath, seg.section, scopePrefix);
1397
+ const slug = structuralNodeSlug(
1398
+ ctx,
1399
+ overview?.fullPath,
1400
+ stripLeadingScopePrefix(seg.section, scopePrefix),
1401
+ scopePrefix
1402
+ );
1249
1403
  const metaPointsTo = metaStr(seg.metadata, "pointsTo");
1250
1404
  const children = buildApiChildren(ctx, scoped, scopeSegs, apiDefinitionId, scopePrefix);
1251
1405
  const changelog = attachedApiChangelogNode(ctx, scoped, scopeSegs, apiDefinitionId, scopePrefix);
@@ -1257,12 +1411,15 @@ function apiReferenceNode(ctx, scoped, scopeSegs, scopePrefix) {
1257
1411
  slug,
1258
1412
  icon: metaStr(seg.metadata, "icon"),
1259
1413
  hidden: seg.hidden,
1260
- viewers: mergeVariantViewers(seg)
1414
+ authed: metaBool(seg.metadata, "authed"),
1415
+ viewers: mergeVariantViewers(seg),
1416
+ orphaned: metaBool(seg.metadata, "orphaned"),
1417
+ featureFlags: metaFeatureFlags(seg.metadata)
1261
1418
  }),
1262
1419
  apiDefinitionId: ApiDefinitionId(apiDefinitionId),
1263
- availability: void 0,
1420
+ availability: metaStr(seg.metadata, "availability"),
1264
1421
  overviewPageId: overviewPageId != null ? PageId(overviewPageId) : void 0,
1265
- noindex: void 0,
1422
+ noindex: metaBool(overview?.metadata, "noindex") ?? metaBool(seg.metadata, "noindex"),
1266
1423
  paginated: true,
1267
1424
  showErrors: metaBool(seg.metadata, "showErrors"),
1268
1425
  hideTitle: metaBool(seg.metadata, "hideTitle"),
@@ -1315,12 +1472,15 @@ function changelogNode(ctx, scoped, scopePrefix) {
1315
1472
  slug: ctx.contentSlug(r.fullPath ?? ""),
1316
1473
  icon: metaStr(r.metadata, "icon"),
1317
1474
  hidden: r.hidden,
1318
- viewers: toViewers(metaStrArr(r.metadata, "viewers"))
1475
+ authed: metaBool(r.metadata, "authed"),
1476
+ viewers: toViewers(metaStrArr(r.metadata, "viewers")),
1477
+ orphaned: metaBool(r.metadata, "orphaned"),
1478
+ featureFlags: metaFeatureFlags(r.metadata)
1319
1479
  }),
1320
1480
  pageId: PageId(metaStr(r.metadata, "pageId") ?? ""),
1321
- noindex: void 0,
1481
+ noindex: metaBool(r.metadata, "noindex"),
1322
1482
  date: dateStr ?? "",
1323
- tags: void 0
1483
+ tags: metaStrArr(r.metadata, "tags")
1324
1484
  };
1325
1485
  let months = byYear.get(year);
1326
1486
  if (months == null) {
@@ -1364,10 +1524,13 @@ function changelogNode(ctx, scoped, scopePrefix) {
1364
1524
  slug: baseSlug,
1365
1525
  icon: metaStr(seg.metadata, "icon"),
1366
1526
  hidden: seg.hidden,
1367
- viewers: mergeVariantViewers(seg)
1527
+ authed: metaBool(seg.metadata, "authed"),
1528
+ viewers: mergeVariantViewers(seg),
1529
+ orphaned: metaBool(seg.metadata, "orphaned"),
1530
+ featureFlags: metaFeatureFlags(seg.metadata)
1368
1531
  }),
1369
1532
  overviewPageId: overviewPageId != null ? PageId(overviewPageId) : void 0,
1370
- noindex: void 0,
1533
+ noindex: metaBool(overview?.metadata, "noindex") ?? metaBool(seg.metadata, "noindex"),
1371
1534
  children: years
1372
1535
  };
1373
1536
  }
@@ -1424,17 +1587,55 @@ function buildSectionTree(ctx, scopeSegs, parentPath, scopePrefix, changelogScop
1424
1587
  return { node, childIndex };
1425
1588
  });
1426
1589
  }
1590
+ function computeSiblingSortBoundaries(ordered) {
1591
+ const result = /* @__PURE__ */ new Map();
1592
+ const sectionPaths = new Set(ordered.map((s) => s.seg.section));
1593
+ const rootSections = [];
1594
+ for (const s of ordered) {
1595
+ const type = metaStr(s.seg.metadata, "type");
1596
+ const sec = s.seg.section;
1597
+ if (type !== "section" || sec === "") {
1598
+ continue;
1599
+ }
1600
+ const parent = parentSectionPath(sec);
1601
+ const isRootLevel = parent === "" || !sectionPaths.has(parent) && nearestExistingAncestor(sec, sectionPaths) === "";
1602
+ if (isRootLevel) {
1603
+ rootSections.push(s);
1604
+ }
1605
+ }
1606
+ const byPath = /* @__PURE__ */ new Map();
1607
+ for (const s of rootSections) {
1608
+ const group = byPath.get(s.seg.section) ?? [];
1609
+ group.push(s);
1610
+ byPath.set(s.seg.section, group);
1611
+ }
1612
+ for (const [, siblings] of byPath) {
1613
+ if (siblings.length <= 1) {
1614
+ continue;
1615
+ }
1616
+ for (let i = 0; i < siblings.length; i++) {
1617
+ const lo = siblings[i].seg.sortOrder;
1618
+ const hi = i + 1 < siblings.length ? siblings[i + 1].seg.sortOrder : Number.MAX_SAFE_INTEGER;
1619
+ result.set(siblings[i], { lo, hi });
1620
+ }
1621
+ }
1622
+ return result;
1623
+ }
1427
1624
  function buildSidebarRootChildren(ctx, scopeSegs, scopePrefix, changelogScopePrefix) {
1428
1625
  const ordered = [...scopeSegs].sort((a, b) => a.seg.sortOrder - b.seg.sortOrder);
1429
1626
  const sectionPaths = new Set(ordered.map((s) => s.seg.section));
1430
1627
  const annotated = [];
1431
1628
  const hasRootSection = ordered.some((s) => metaStr(s.seg.metadata, "type") === "section" && s.seg.section === "");
1629
+ const siblingBoundaries = computeSiblingSortBoundaries(ordered);
1432
1630
  for (const s of ordered) {
1433
1631
  const type = metaStr(s.seg.metadata, "type");
1434
1632
  const sec = s.seg.section;
1435
1633
  if (hasOwningApiReference(s, scopeSegs)) {
1436
1634
  continue;
1437
1635
  }
1636
+ if (type === "tabRoot" && metaBool(s.seg.metadata, "landingPage") === true) {
1637
+ continue;
1638
+ }
1438
1639
  if (type === "tabRoot" || sec === "" && type !== "section") {
1439
1640
  for (const r of s.nav) {
1440
1641
  const node = artifactToNode(ctx, r);
@@ -1462,14 +1663,21 @@ function buildSidebarRootChildren(ctx, scopeSegs, scopePrefix, changelogScopePre
1462
1663
  } else if (type === "changelog") {
1463
1664
  node = changelogNode(ctx, s, changelogScopePrefix ?? scopePrefix);
1464
1665
  } else {
1666
+ const bounds = siblingBoundaries.get(s);
1667
+ const ownedSegs = bounds != null ? scopeSegs.filter((seg) => seg.seg.sortOrder >= bounds.lo && seg.seg.sortOrder < bounds.hi) : scopeSegs;
1465
1668
  node = sectionNode(
1466
1669
  ctx,
1467
1670
  s,
1468
- buildSectionTree(ctx, scopeSegs, sec, scopePrefix, changelogScopePrefix),
1671
+ buildSectionTree(ctx, ownedSegs, sec, scopePrefix, changelogScopePrefix),
1469
1672
  scopePrefix
1470
1673
  );
1471
1674
  }
1472
- annotated.push({ node, pos: metaNum(s.seg.metadata, "childIndex"), isLeaf: false });
1675
+ const isSharedSection = siblingBoundaries.size > 0 && scopePrefix !== "" && sec !== "" && !sec.startsWith(scopePrefix);
1676
+ annotated.push({
1677
+ node,
1678
+ pos: isSharedSection ? void 0 : metaNum(s.seg.metadata, "childIndex"),
1679
+ isLeaf: false
1680
+ });
1473
1681
  }
1474
1682
  }
1475
1683
  const hasLeafPos = annotated.some((a) => a.isLeaf && a.pos != null);
@@ -1521,13 +1729,13 @@ function buildSidebarRoot(ctx, scopeSegs, scopePrefix, changelogScopePrefix) {
1521
1729
  };
1522
1730
  }
1523
1731
  function buildFullRootFromSegments(options) {
1524
- const { basePath, title, segments, navBySegment } = options;
1732
+ const { basePath, rootSlug: sourceRootSlug, title, segments, navBySegment, skipSharedSectionMerge } = options;
1525
1733
  const ctx = createBuildContext(basePath);
1526
1734
  const scoped = segments.filter((seg) => {
1527
1735
  const t = metaStr(seg.metadata, "type");
1528
1736
  return t !== "files" && t !== "redirects";
1529
1737
  }).map((seg) => ({ seg, nav: navBySegment.get(seg.segmentHash) ?? [] }));
1530
- const rootSlug = asSlug(basePath);
1738
+ const rootSlug = asSlug(sourceRootSlug ?? basePath);
1531
1739
  const scopesByKey = /* @__PURE__ */ new Map();
1532
1740
  for (const s of scoped) {
1533
1741
  const key = scopeKey(s.seg);
@@ -1540,9 +1748,9 @@ function buildFullRootFromSegments(options) {
1540
1748
  const tabs = uniqueDetails(scoped, "tab");
1541
1749
  let child;
1542
1750
  if (products.length > 0) {
1543
- child = buildProductGroup(ctx, products, scoped, rootSlug);
1751
+ child = buildProductGroup(ctx, products, scoped, rootSlug, skipSharedSectionMerge);
1544
1752
  } else if (versions.length > 0) {
1545
- child = buildVersioned(ctx, versions, scoped, rootSlug, void 0);
1753
+ child = buildVersioned(ctx, versions, scoped, rootSlug, void 0, skipSharedSectionMerge);
1546
1754
  } else {
1547
1755
  child = buildUnversioned(ctx, scoped, rootSlug, tabs.length > 0);
1548
1756
  }
@@ -1553,6 +1761,24 @@ function buildFullRootFromSegments(options) {
1553
1761
  } else if (child.type === "versioned") {
1554
1762
  const defaultVersion = child.children.find((v) => v.default) ?? child.children[0];
1555
1763
  rootPointsTo = defaultVersion?.pointsTo;
1764
+ } else if (child.type === "unversioned") {
1765
+ if (tabs.length > 0) {
1766
+ const firstTab = tabs[0];
1767
+ const pt = firstTab != null ? metaStr(firstTab.metadata, "pointsTo") : void 0;
1768
+ rootPointsTo = pt != null ? pointsToSlug(pt) : void 0;
1769
+ } else {
1770
+ const sorted = [...scoped].sort((a, b) => a.seg.sortOrder - b.seg.sortOrder);
1771
+ for (const s of sorted) {
1772
+ if (s.seg.hidden) {
1773
+ continue;
1774
+ }
1775
+ const pt = metaStr(s.seg.metadata, "pointsTo");
1776
+ if (pt != null) {
1777
+ rootPointsTo = pointsToSlug(pt);
1778
+ break;
1779
+ }
1780
+ }
1781
+ }
1556
1782
  }
1557
1783
  const root = {
1558
1784
  type: "root",
@@ -1565,21 +1791,22 @@ function buildFullRootFromSegments(options) {
1565
1791
  return root;
1566
1792
  }
1567
1793
  function uniqueDetails(scoped, dim) {
1568
- const bySlug = /* @__PURE__ */ new Map();
1794
+ const byKey = /* @__PURE__ */ new Map();
1569
1795
  for (const s of scoped) {
1570
1796
  const d = s.seg[dim];
1571
1797
  if (d == null) {
1572
1798
  continue;
1573
1799
  }
1574
1800
  const slug = detailSlug(d);
1575
- const existing = bySlug.get(slug);
1801
+ const key = dim === "tab" ? d.id : slug !== "" ? slug : d.id;
1802
+ const existing = byKey.get(key);
1576
1803
  if (existing == null) {
1577
- bySlug.set(slug, d);
1804
+ byKey.set(key, d);
1578
1805
  } else if (metaStr(d.metadata, "pointsTo") != null && metaStr(existing.metadata, "pointsTo") == null) {
1579
- bySlug.set(slug, d);
1806
+ byKey.set(key, d);
1580
1807
  }
1581
1808
  }
1582
- return [...bySlug.values()].sort((a, b) => a.sortOrder - b.sortOrder);
1809
+ return [...byKey.values()].sort((a, b) => a.sortOrder - b.sortOrder);
1583
1810
  }
1584
1811
  function scopeSlugPrefix(rootSlug, ...details) {
1585
1812
  const parts = [rootSlug];
@@ -1613,7 +1840,7 @@ function buildVariantedNode(ctx, variants, scopeSegs, scopePrefix) {
1613
1840
  default: detailIsDefault(variant),
1614
1841
  variantId: VariantId(String(variant.id)),
1615
1842
  subtitle: metaStr(variant.metadata, "subtitle") ?? void 0,
1616
- image: void 0,
1843
+ image: metaStr(variant.metadata, "image") != null ? FileId(metaStr(variant.metadata, "image")) : void 0,
1617
1844
  pointsTo: metaPointsTo != null ? pointsToSlug(metaPointsTo) : void 0,
1618
1845
  children
1619
1846
  };
@@ -1650,7 +1877,10 @@ function buildTabbedOrSidebar(ctx, scopeSegs, tabsPresent, scopePrefix) {
1650
1877
  const tabDetails = uniqueDetails(scopeSegs, "tab");
1651
1878
  const children = tabDetails.map((tab) => {
1652
1879
  const tabType = metaStr(tab.metadata, "tab_type");
1653
- const tabSlug = asSlug(scopeSlugPrefix(scopePrefix, tab));
1880
+ const rawTabSlug = detailSlug(tab);
1881
+ const versionDetail = scopeSegs[0]?.seg.version;
1882
+ const versionSlugStr = versionDetail != null ? detailSlug(versionDetail) : void 0;
1883
+ const tabSlug = versionSlugStr != null && rawTabSlug !== "" && (rawTabSlug === versionSlugStr || rawTabSlug.startsWith(versionSlugStr + "/")) ? asSlug(rawTabSlug) : asSlug(scopeSlugPrefix(scopePrefix, tab));
1654
1884
  if (tabType === "link") {
1655
1885
  return {
1656
1886
  type: "link",
@@ -1659,7 +1889,7 @@ function buildTabbedOrSidebar(ctx, scopeSegs, tabsPresent, scopePrefix) {
1659
1889
  title: tab.displayName,
1660
1890
  icon: tab.icon ?? void 0,
1661
1891
  url: Url(metaStr(tab.metadata, "url") ?? ""),
1662
- target: void 0
1892
+ target: metaStr(tab.metadata, "target")
1663
1893
  };
1664
1894
  }
1665
1895
  if (tabType === "changelog") {
@@ -1720,16 +1950,71 @@ function buildUnversioned(ctx, scopeSegs, scopePrefix, tabsPresent) {
1720
1950
  type: "unversioned",
1721
1951
  id: ctx.nodeId(`unversioned:${first?.seg.product?.id ?? "root"}`),
1722
1952
  collapsed: void 0,
1723
- landingPage: void 0,
1953
+ landingPage: buildLandingPage(ctx, scopeSegs),
1724
1954
  // The enclosing scope prefix (product slug, or rootSlug at the top
1725
1955
  // level) roots structural sections so they share the scope's slug
1726
1956
  // namespace.
1727
1957
  child: buildTabbedOrSidebar(ctx, scopeSegs, tabsPresent, scopePrefix)
1728
1958
  };
1729
1959
  }
1730
- function buildVersioned(ctx, versions, scoped, rootSlug, productPrefix) {
1960
+ function mergeSharedSections(defaultSegs, allScoped, defaultVersion, versionSlugs) {
1961
+ const defaultTabsByName = /* @__PURE__ */ new Map();
1962
+ for (const s of defaultSegs) {
1963
+ if (s.seg.tab != null) {
1964
+ const tabName = s.seg.tab.displayName;
1965
+ if (!defaultTabsByName.has(tabName)) {
1966
+ defaultTabsByName.set(tabName, s.seg.tab);
1967
+ }
1968
+ }
1969
+ }
1970
+ const existingSections = new Set(
1971
+ defaultSegs.filter((s) => metaStr(s.seg.metadata, "type") === "section").map((s) => s.seg.section)
1972
+ );
1973
+ const maxSortOrder = defaultSegs.reduce((max, s) => Math.max(max, s.seg.sortOrder), 0);
1974
+ let nextSortOrder = maxSortOrder + 1;
1975
+ const merged = [...defaultSegs];
1976
+ for (const s of allScoped) {
1977
+ if (s.seg.version?.id === defaultVersion.id) {
1978
+ continue;
1979
+ }
1980
+ const type = metaStr(s.seg.metadata, "type");
1981
+ if (type !== "section") {
1982
+ continue;
1983
+ }
1984
+ const sectionPath = s.seg.section;
1985
+ if (versionSlugs.some((vs) => sectionPath === vs || sectionPath.startsWith(vs + "/"))) {
1986
+ continue;
1987
+ }
1988
+ if (existingSections.has(sectionPath)) {
1989
+ continue;
1990
+ }
1991
+ const sourceTabName = s.seg.tab?.displayName ?? "";
1992
+ const targetTab = defaultTabsByName.get(sourceTabName);
1993
+ if (targetTab == null) {
1994
+ continue;
1995
+ }
1996
+ const metadata = s.seg.metadata != null ? { ...s.seg.metadata, childIndex: void 0 } : s.seg.metadata;
1997
+ merged.push({
1998
+ seg: {
1999
+ ...s.seg,
2000
+ tab: targetTab,
2001
+ version: defaultVersion,
2002
+ sortOrder: nextSortOrder++,
2003
+ metadata
2004
+ },
2005
+ nav: s.nav
2006
+ });
2007
+ existingSections.add(sectionPath);
2008
+ }
2009
+ return merged;
2010
+ }
2011
+ function buildVersioned(ctx, versions, scoped, rootSlug, productPrefix, skipSharedSectionMerge) {
2012
+ const versionSlugs = versions.map((v) => detailSlug(v)).filter((s) => s !== "");
1731
2013
  const children = versions.map((version) => {
1732
- const versionSegs = scoped.filter((s) => s.seg.version?.id === version.id);
2014
+ let versionSegs = scoped.filter((s) => s.seg.version?.id === version.id);
2015
+ if (detailIsDefault(version) && !skipSharedSectionMerge) {
2016
+ versionSegs = mergeSharedSections(versionSegs, scoped, version, versionSlugs);
2017
+ }
1733
2018
  const tabsPresent = uniqueDetails(versionSegs, "tab").length > 0;
1734
2019
  const prefix = productPrefix != null ? productPrefix : rootSlug;
1735
2020
  const versionSegment = detailSlug(version);
@@ -1747,7 +2032,7 @@ function buildVersioned(ctx, versions, scoped, rootSlug, productPrefix) {
1747
2032
  default: detailIsDefault(version),
1748
2033
  versionId: VersionId(version.id),
1749
2034
  availability: void 0,
1750
- landingPage: void 0,
2035
+ landingPage: buildLandingPage(ctx, versionSegs),
1751
2036
  announcement: void 0,
1752
2037
  pointsTo: metaStr(version.metadata, "pointsTo") != null ? pointsToSlug(metaStr(version.metadata, "pointsTo")) : void 0,
1753
2038
  // The version's full slug roots structural sections so the whole
@@ -1759,6 +2044,7 @@ function buildVersioned(ctx, versions, scoped, rootSlug, productPrefix) {
1759
2044
  }
1760
2045
  return node;
1761
2046
  });
2047
+ markCrossVersionCanonicalSlugs(children);
1762
2048
  return {
1763
2049
  type: "versioned",
1764
2050
  id: ctx.nodeId(`versioned:${productPrefix ?? "root"}`),
@@ -1784,13 +2070,125 @@ function markDefaultVersionCanonicalSlugs(node, pruneSlug, versionSlug) {
1784
2070
  return;
1785
2071
  });
1786
2072
  }
1787
- function buildProductGroup(ctx, products, scoped, rootSlug) {
2073
+ function unversionedParents(parents) {
2074
+ let versionIdx = -1;
2075
+ for (let i = parents.length - 1; i >= 0; i--) {
2076
+ if (parents[i].type === "version") {
2077
+ versionIdx = i;
2078
+ break;
2079
+ }
2080
+ }
2081
+ return versionIdx >= 0 ? parents.slice(versionIdx + 1) : parents;
2082
+ }
2083
+ function crossVersionDedupKeys(node, parents) {
2084
+ const keys = [];
2085
+ const uParents = unversionedParents(parents);
2086
+ const pid = getPageId(node);
2087
+ if (pid != null) {
2088
+ keys.push(`pid:${pid}`);
2089
+ }
2090
+ if (node.type === "endpoint" || node.type === "webSocket" || node.type === "webhook" || node.type === "grpc" || node.type === "graphql") {
2091
+ const apiParentTitles = uParents.filter(
2092
+ (p) => p.type === "apiReference"
2093
+ ).map((p) => p.title.replaceAll(" ", ""));
2094
+ const apiPrefix = apiParentTitles.join(":");
2095
+ if (node.type === "endpoint") {
2096
+ keys.push(`${apiPrefix}:api:endpoint:${node.method}:${node.endpointId}`);
2097
+ } else if (node.type === "webSocket") {
2098
+ keys.push(`${apiPrefix}:api:websocket:${node.webSocketId}`);
2099
+ } else if (node.type === "webhook") {
2100
+ keys.push(`${apiPrefix}:api:webhook:${node.method}:${node.webhookId}`);
2101
+ } else if (node.type === "grpc") {
2102
+ keys.push(`${apiPrefix}:api:grpc:${node.method}:${node.grpcId}`);
2103
+ } else if (node.type === "graphql") {
2104
+ keys.push(`${apiPrefix}:api:graphql:${node.id}`);
2105
+ }
2106
+ }
2107
+ const parentTitles = [];
2108
+ for (const p of uParents) {
2109
+ if (hasMetadata(p)) {
2110
+ parentTitles.push(p.title);
2111
+ }
2112
+ }
2113
+ keys.push([...parentTitles, node.title].join("###"));
2114
+ return keys;
2115
+ }
2116
+ function routeToLandingPage(ctx, route) {
2117
+ if (route == null || route.type !== "markdown" || route.fullPath == null) {
2118
+ return void 0;
2119
+ }
2120
+ const md = route.metadata;
2121
+ const pageId = metaStr(md, "pageId");
2122
+ if (pageId == null) {
2123
+ return void 0;
2124
+ }
2125
+ const slug = ctx.contentSlug(route.fullPath);
2126
+ return {
2127
+ type: "landingPage",
2128
+ ...withMetadataDefaults({
2129
+ id: ctx.nodeId(`landing:${slug}`),
2130
+ title: metaStr(md, "title") ?? "",
2131
+ slug,
2132
+ icon: metaStr(md, "icon"),
2133
+ hidden: route.hidden,
2134
+ authed: metaBool(md, "authed"),
2135
+ viewers: toViewers(metaStrArr(md, "viewers")),
2136
+ orphaned: metaBool(md, "orphaned"),
2137
+ featureFlags: metaFeatureFlags(md)
2138
+ }),
2139
+ pageId: PageId(pageId),
2140
+ noindex: metaBool(md, "noindex")
2141
+ };
2142
+ }
2143
+ function buildLandingPage(ctx, scopeSegs) {
2144
+ const landingSegment = scopeSegs.find(
2145
+ (s) => metaStr(s.seg.metadata, "type") === "tabRoot" && metaBool(s.seg.metadata, "landingPage") === true
2146
+ );
2147
+ return routeToLandingPage(
2148
+ ctx,
2149
+ landingSegment?.nav.find((route) => route.type === "markdown")
2150
+ );
2151
+ }
2152
+ function markCrossVersionCanonicalSlugs(versionNodes) {
2153
+ const keyToCanonical = /* @__PURE__ */ new Map();
2154
+ const sorted = [...versionNodes].sort((a, b) => {
2155
+ if (a.default !== b.default) {
2156
+ return a.default ? -1 : 1;
2157
+ }
2158
+ return 0;
2159
+ });
2160
+ for (const version of sorted) {
2161
+ traverseDF(version, (child, parents) => {
2162
+ if (hasMetadata(child) && isPage(child)) {
2163
+ const keys = crossVersionDedupKeys(child, parents);
2164
+ let matched = false;
2165
+ for (const key of keys) {
2166
+ const canonical = keyToCanonical.get(key);
2167
+ if (canonical != null && !version.default) {
2168
+ child.canonicalSlug = canonical;
2169
+ matched = true;
2170
+ break;
2171
+ }
2172
+ }
2173
+ if (!matched) {
2174
+ const canonical = child.canonicalSlug ?? child.slug;
2175
+ for (const key of keys) {
2176
+ if (!keyToCanonical.has(key)) {
2177
+ keyToCanonical.set(key, canonical);
2178
+ }
2179
+ }
2180
+ }
2181
+ }
2182
+ });
2183
+ }
2184
+ }
2185
+ function buildProductGroup(ctx, products, scoped, rootSlug, skipSharedSectionMerge) {
1788
2186
  const children = products.map((product) => {
1789
2187
  const productSegs = scoped.filter((s) => s.seg.product?.id === product.id);
1790
2188
  const versions = uniqueDetails(productSegs, "version");
1791
2189
  const tabsPresent = uniqueDetails(productSegs, "tab").length > 0;
1792
2190
  const productPrefix = scopeSlugPrefix(rootSlug, product);
1793
- const productChild = versions.length > 0 ? buildVersioned(ctx, versions, productSegs, rootSlug, productPrefix) : buildUnversioned(ctx, productSegs, asSlug(productPrefix), tabsPresent);
2191
+ const productChild = versions.length > 0 ? buildVersioned(ctx, versions, productSegs, rootSlug, productPrefix, skipSharedSectionMerge) : buildUnversioned(ctx, productSegs, asSlug(productPrefix), tabsPresent);
1794
2192
  const node = {
1795
2193
  type: "product",
1796
2194
  ...withMetadataDefaults({
@@ -1809,7 +2207,7 @@ function buildProductGroup(ctx, products, scoped, rootSlug) {
1809
2207
  // here; sourcing it from the hash makes that filter match nothing.
1810
2208
  productId: ProductId(product.displayName),
1811
2209
  subtitle: metaStr(product.metadata, "subtitle") ?? "",
1812
- image: void 0,
2210
+ image: metaStr(product.metadata, "image") != null ? FileId(metaStr(product.metadata, "image")) : void 0,
1813
2211
  announcement: void 0,
1814
2212
  pointsTo: metaStr(product.metadata, "pointsTo") != null ? pointsToSlug(metaStr(product.metadata, "pointsTo")) : void 0,
1815
2213
  child: productChild
@@ -1820,7 +2218,10 @@ function buildProductGroup(ctx, products, scoped, rootSlug) {
1820
2218
  type: "productgroup",
1821
2219
  id: ctx.nodeId("productgroup"),
1822
2220
  collapsed: void 0,
1823
- landingPage: void 0,
2221
+ landingPage: buildLandingPage(
2222
+ ctx,
2223
+ scoped.filter((s) => s.seg.product == null)
2224
+ ),
1824
2225
  children
1825
2226
  };
1826
2227
  }