@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
@@ -135,6 +135,9 @@ var AuthSchemeIdSchema = z2.string();
135
135
  var SubpackageIdSchema = z2.string();
136
136
  var WebSocketMessageIdSchema = z2.string();
137
137
  var FileIdSchema = z2.string();
138
+ function FileId(value) {
139
+ return value;
140
+ }
138
141
  var MultipleAuthTypeSchema = z2.object({
139
142
  schemes: z2.array(AuthSchemeIdSchema)
140
143
  });
@@ -698,6 +701,77 @@ function getChildren(node) {
698
701
  }
699
702
  }
700
703
 
704
+ // ../commons/core-utils/dist/assertNever.js
705
+ function assertNever(x) {
706
+ throw new Error("Unexpected value: " + JSON.stringify(x));
707
+ }
708
+
709
+ // ../commons/core-utils/dist/visitDiscriminatedUnion.js
710
+ function visitDiscriminatedUnion(item, discriminant = "type") {
711
+ return {
712
+ _visit: (visitor) => {
713
+ const visit = visitor[item[discriminant]];
714
+ if (visit != null) {
715
+ return visit(item);
716
+ } else {
717
+ if (visitor._other == null) {
718
+ assertNever(item);
719
+ }
720
+ return visitor._other(item);
721
+ }
722
+ }
723
+ };
724
+ }
725
+ var visitDiscriminatedUnion_default = visitDiscriminatedUnion;
726
+
727
+ // src/navigation/versions/latest/getPageId.ts
728
+ var RETURN_PAGEID = (node) => node.pageId;
729
+ var RETURN_OVERVIEW_PAGEID = (node) => node.overviewPageId;
730
+ var RETURN_UNDEFINED = () => void 0;
731
+ function getPageId(node) {
732
+ return visitDiscriminatedUnion_default(node)._visit({
733
+ section: RETURN_OVERVIEW_PAGEID,
734
+ apiReference: RETURN_OVERVIEW_PAGEID,
735
+ changelog: RETURN_OVERVIEW_PAGEID,
736
+ apiPackage: RETURN_OVERVIEW_PAGEID,
737
+ changelogEntry: RETURN_PAGEID,
738
+ page: RETURN_PAGEID,
739
+ landingPage: RETURN_PAGEID,
740
+ // if the following changes, make sure to update the algolia records generator
741
+ endpoint: RETURN_UNDEFINED,
742
+ webSocket: RETURN_UNDEFINED,
743
+ webhook: RETURN_UNDEFINED,
744
+ grpc: RETURN_UNDEFINED,
745
+ graphql: RETURN_UNDEFINED,
746
+ graphqlType: RETURN_UNDEFINED
747
+ // changelogYear: RETURN_UNDEFINED,
748
+ // changelogMonth: RETURN_UNDEFINED,
749
+ });
750
+ }
751
+
752
+ // src/navigation/versions/latest/NavigationNodeSection.ts
753
+ function isSection(node) {
754
+ return node.type === "section" || node.type === "apiReference" || node.type === "changelog" || node.type === "apiPackage";
755
+ }
756
+
757
+ // src/navigation/versions/latest/NavigationNodeSectionOverview.ts
758
+ function isSectionOverview(node) {
759
+ return isSection(node) && node.overviewPageId != null;
760
+ }
761
+
762
+ // src/navigation/versions/latest/NavigationNodeMarkdown.ts
763
+ function hasMarkdown(node) {
764
+ return isMarkdownLeaf(node) || isSectionOverview(node);
765
+ }
766
+
767
+ // src/navigation/versions/latest/NavigationNodePage.ts
768
+ function isPage(node) {
769
+ return isApiLeaf(node) || node.type === "changelog" || // TODO: Uncomment when changelog years and months are visitable
770
+ // node.type === "changelogYear" ||
771
+ // node.type === "changelogMonth" ||
772
+ hasMarkdown(node);
773
+ }
774
+
701
775
  // src/navigation/versions/latest/NavigationNodeWithMetadata.ts
702
776
  function hasMetadata(node) {
703
777
  return typeof node.title === "string" && typeof node.slug === "string";
@@ -835,6 +909,13 @@ function metaStrArr(metadata, key) {
835
909
  }
836
910
  return void 0;
837
911
  }
912
+ function metaFeatureFlags(metadata) {
913
+ if (metadata != null && typeof metadata === "object" && "featureFlags" in metadata) {
914
+ const v = metadata.featureFlags;
915
+ return Array.isArray(v) && v.length > 0 ? v : void 0;
916
+ }
917
+ return void 0;
918
+ }
838
919
  function toViewers(viewers) {
839
920
  if (viewers == null || viewers.length === 0) {
840
921
  return void 0;
@@ -870,17 +951,17 @@ function scopeKey(seg) {
870
951
  return [seg.product?.id ?? "", seg.version?.id ?? "", seg.variant?.id ?? "", seg.tab?.id ?? ""].join("|");
871
952
  }
872
953
  function withMetadataDefaults(base) {
873
- const { viewers, ...rest } = base;
954
+ const { viewers, featureFlags, ...rest } = base;
874
955
  return {
875
956
  ...rest,
876
957
  canonicalSlug: void 0,
877
- authed: void 0,
958
+ authed: base.authed === true ? true : void 0,
878
959
  collapsed: void 0,
879
960
  // Normalise empty arrays to undefined so unrestricted nodes don't add
880
961
  // filter-level noise; a non-empty array engages the RBAC view gate.
881
962
  viewers: viewers != null && viewers.length > 0 ? viewers : void 0,
882
- orphaned: void 0,
883
- featureFlags: void 0
963
+ orphaned: base.orphaned === true ? true : void 0,
964
+ featureFlags: featureFlags != null && featureFlags.length > 0 ? featureFlags : void 0
884
965
  };
885
966
  }
886
967
  function artifactToNode(ctx, route) {
@@ -903,7 +984,10 @@ function artifactToNode(ctx, route) {
903
984
  slug,
904
985
  icon,
905
986
  hidden: route.hidden,
906
- viewers
987
+ authed: metaBool(md, "authed"),
988
+ viewers,
989
+ orphaned: metaBool(md, "orphaned"),
990
+ featureFlags: metaFeatureFlags(md)
907
991
  }),
908
992
  pageId: PageId(pageId),
909
993
  noindex: metaBool(md, "noindex"),
@@ -919,7 +1003,7 @@ function artifactToNode(ctx, route) {
919
1003
  title,
920
1004
  icon,
921
1005
  url: Url(metaStr(md, "url") ?? ""),
922
- target: void 0
1006
+ target: metaStr(md, "target")
923
1007
  };
924
1008
  return node;
925
1009
  }
@@ -954,7 +1038,10 @@ function apiLeafNodeFromNavRouteInternal(ctx, route, segmentApiDefinitionId2) {
954
1038
  slug,
955
1039
  icon,
956
1040
  hidden: route.hidden,
957
- viewers
1041
+ authed: metaBool(md, "authed"),
1042
+ viewers,
1043
+ orphaned: metaBool(md, "orphaned"),
1044
+ featureFlags: metaFeatureFlags(md)
958
1045
  });
959
1046
  if (route.type === "rest") {
960
1047
  const endpointId = metaStr(md, "endpointId");
@@ -969,7 +1056,7 @@ function apiLeafNodeFromNavRouteInternal(ctx, route, segmentApiDefinitionId2) {
969
1056
  endpointId: EndpointId(endpointId),
970
1057
  apiDefinitionId,
971
1058
  isResponseStream: metaBool(md, "isResponseStream"),
972
- availability: void 0,
1059
+ availability: metaStr(md, "availability"),
973
1060
  playground: void 0
974
1061
  };
975
1062
  }
@@ -985,7 +1072,7 @@ function apiLeafNodeFromNavRouteInternal(ctx, route, segmentApiDefinitionId2) {
985
1072
  method,
986
1073
  webhookId: WebhookId(webhookId),
987
1074
  apiDefinitionId,
988
- availability: void 0
1075
+ availability: metaStr(md, "availability")
989
1076
  };
990
1077
  }
991
1078
  if (route.type === "asyncapi") {
@@ -998,7 +1085,7 @@ function apiLeafNodeFromNavRouteInternal(ctx, route, segmentApiDefinitionId2) {
998
1085
  ...meta,
999
1086
  webSocketId: WebSocketId(webSocketId),
1000
1087
  apiDefinitionId,
1001
- availability: void 0,
1088
+ availability: metaStr(md, "availability"),
1002
1089
  playground: void 0
1003
1090
  };
1004
1091
  }
@@ -1014,7 +1101,7 @@ function apiLeafNodeFromNavRouteInternal(ctx, route, segmentApiDefinitionId2) {
1014
1101
  method,
1015
1102
  grpcId: GrpcId(grpcId),
1016
1103
  apiDefinitionId,
1017
- availability: void 0
1104
+ availability: metaStr(md, "availability")
1018
1105
  };
1019
1106
  }
1020
1107
  if (route.type === "graphql") {
@@ -1030,12 +1117,69 @@ function apiLeafNodeFromNavRouteInternal(ctx, route, segmentApiDefinitionId2) {
1030
1117
  graphqlOperationId: GraphQlOperationId(graphqlOperationId),
1031
1118
  graphqlOperationIds: void 0,
1032
1119
  apiDefinitionId,
1033
- availability: void 0,
1120
+ availability: metaStr(md, "availability"),
1034
1121
  playground: void 0
1035
1122
  };
1036
1123
  }
1037
1124
  return void 0;
1038
1125
  }
1126
+ function apiLeafPositionKey(node) {
1127
+ switch (node.type) {
1128
+ case "endpoint":
1129
+ return `endpoint:${node.endpointId}`;
1130
+ case "webhook":
1131
+ return `webhook:${node.webhookId}`;
1132
+ case "webSocket":
1133
+ return `webSocket:${node.webSocketId}`;
1134
+ case "grpc":
1135
+ return `grpc:${node.grpcId}`;
1136
+ case "graphql":
1137
+ return `graphql:${node.graphqlOperationId}`;
1138
+ }
1139
+ }
1140
+ function apiChildPositionKey(node) {
1141
+ switch (node.type) {
1142
+ case "endpoint":
1143
+ case "webhook":
1144
+ case "webSocket":
1145
+ case "grpc":
1146
+ case "graphql":
1147
+ return apiLeafPositionKey(node);
1148
+ default:
1149
+ return void 0;
1150
+ }
1151
+ }
1152
+ function positionedApiLeafChildren(ctx, nav, apiDefinitionId) {
1153
+ const rawLeafNodes = [];
1154
+ const leafPositions = /* @__PURE__ */ new Map();
1155
+ const nonApiLeafNodes = [];
1156
+ for (let i = 0; i < nav.length; i++) {
1157
+ const route = nav[i];
1158
+ const pos = route.displaySortOrder ?? i;
1159
+ const apiLeaf = apiLeafNodeFromNavRouteInternal(ctx, route, apiDefinitionId);
1160
+ if (apiLeaf != null) {
1161
+ rawLeafNodes.push(apiLeaf);
1162
+ const key = apiLeafPositionKey(apiLeaf);
1163
+ leafPositions.set(key, Math.min(leafPositions.get(key) ?? pos, pos));
1164
+ continue;
1165
+ }
1166
+ const node = artifactToNode(ctx, route);
1167
+ if (node?.type === "page" || node?.type === "link") {
1168
+ nonApiLeafNodes.push({ node, pos });
1169
+ }
1170
+ }
1171
+ const apiLeafNodes = mergeEndpointPairs(ctx, rawLeafNodes, nav);
1172
+ const positionedApiLeafNodes = apiLeafNodes.map((node, index) => {
1173
+ if (node.type === "endpointPair") {
1174
+ const streamPos = leafPositions.get(apiLeafPositionKey(node.stream)) ?? index;
1175
+ const nonStreamPos = leafPositions.get(apiLeafPositionKey(node.nonStream)) ?? index;
1176
+ return { node, pos: Math.min(streamPos, nonStreamPos) };
1177
+ }
1178
+ const key = apiChildPositionKey(node);
1179
+ return { node, pos: key != null ? leafPositions.get(key) ?? index : index };
1180
+ });
1181
+ return [...positionedApiLeafNodes, ...nonApiLeafNodes];
1182
+ }
1039
1183
  function collectApiLeafNodesFromNav(routes, basePath, segmentApiDefinitionId2) {
1040
1184
  const ctx = createBuildContext(basePath);
1041
1185
  return routes.map((route) => apiLeafNodeFromNavRouteInternal(ctx, route, segmentApiDefinitionId2)).filter((node) => node != null);
@@ -1125,8 +1269,9 @@ function sectionNode(ctx, scoped, sectionChildren, scopePrefix) {
1125
1269
  const { seg, nav } = scoped;
1126
1270
  const overview = overviewRoute(nav) ?? overviewRouteFromSegmentMeta(seg, nav);
1127
1271
  const overviewPageId = overview != null ? metaStr(overview.metadata, "pageId") : void 0;
1128
- let slug = structuralNodeSlug(ctx, overview?.fullPath, seg.section, scopePrefix);
1129
- if (seg.section === "" && overview?.fullPath == null) {
1272
+ const relSection = stripLeadingScopePrefix(seg.section, scopePrefix);
1273
+ let slug = structuralNodeSlug(ctx, overview?.fullPath, relSection, scopePrefix);
1274
+ if (relSection === "" && overview?.fullPath == null) {
1130
1275
  slug = inferSectionSlugFromNav(ctx, nav, scopePrefix) ?? slug;
1131
1276
  }
1132
1277
  const metaPointsTo = metaStr(seg.metadata, "pointsTo");
@@ -1136,17 +1281,22 @@ function sectionNode(ctx, scoped, sectionChildren, scopePrefix) {
1136
1281
  slug,
1137
1282
  icon: metaStr(seg.metadata, "icon"),
1138
1283
  hidden: seg.hidden,
1139
- viewers: mergeVariantViewers(seg)
1284
+ authed: metaBool(seg.metadata, "authed"),
1285
+ viewers: mergeVariantViewers(seg),
1286
+ orphaned: metaBool(seg.metadata, "orphaned"),
1287
+ featureFlags: metaFeatureFlags(seg.metadata)
1140
1288
  });
1141
1289
  return {
1142
1290
  type: "section",
1143
1291
  ...defaults,
1144
- collapsed: metaCollapsed(seg.metadata) ?? defaults.collapsed,
1292
+ // V2 convention: collapsed:false in docs.yml maps to "open-by-default"
1293
+ // so getInitiallyOpenByDefaultNodes picks it up and the UI starts open.
1294
+ collapsed: metaCollapsed(seg.metadata) === false ? "open-by-default" : metaCollapsed(seg.metadata) ?? defaults.collapsed,
1145
1295
  overviewPageId: overviewPageId != null ? PageId(overviewPageId) : void 0,
1146
- noindex: void 0,
1296
+ noindex: metaBool(overview?.metadata, "noindex") ?? metaBool(seg.metadata, "noindex"),
1147
1297
  collapsible: metaBool(seg.metadata, "collapsible") ?? (metaCollapsed(seg.metadata) != null ? true : void 0),
1148
1298
  collapsedByDefault: metaBool(seg.metadata, "collapsedByDefault") ?? (metaCollapsed(seg.metadata) === true ? true : void 0),
1149
- availability: void 0,
1299
+ availability: metaStr(seg.metadata, "availability"),
1150
1300
  pointsTo: metaPointsTo != null ? pointsToSlug(metaPointsTo) : void 0,
1151
1301
  children: mergeChildrenByPosition(ctx, nav, sectionChildren)
1152
1302
  };
@@ -1169,8 +1319,7 @@ function directChildApiPackages(parentSection, scopeSegs) {
1169
1319
  }
1170
1320
  function buildApiChildren(ctx, scoped, scopeSegs, apiDefinitionId, scopePrefix) {
1171
1321
  const { seg, nav } = scoped;
1172
- const rawLeafNodes = nav.map((route) => apiLeafNodeFromNavRouteInternal(ctx, route, apiDefinitionId)).filter((node) => node != null);
1173
- const leafNodes = mergeEndpointPairs(ctx, rawLeafNodes, nav);
1322
+ const leafNodes = positionedApiLeafChildren(ctx, nav, apiDefinitionId);
1174
1323
  const childPackages = directChildApiPackages(seg.section, scopeSegs);
1175
1324
  const packageNodes = childPackages.map((child) => ({
1176
1325
  node: apiPackageChildNode(
@@ -1184,18 +1333,10 @@ function buildApiChildren(ctx, scoped, scopeSegs, apiDefinitionId, scopePrefix)
1184
1333
  }));
1185
1334
  const hasChildIndex = packageNodes.some((p) => p.childIndex != null);
1186
1335
  if (!hasChildIndex) {
1187
- return [...leafNodes, ...packageNodes.map((p) => p.node)];
1188
- }
1189
- const leafPositions = [];
1190
- for (const route of nav) {
1191
- if (route.displaySortOrder != null) {
1192
- leafPositions.push(route.displaySortOrder);
1193
- }
1336
+ return [...leafNodes, ...packageNodes.map((p) => ({ node: p.node, pos: Number.MAX_SAFE_INTEGER }))].sort((a, b) => a.pos - b.pos).map((p) => p.node);
1194
1337
  }
1195
1338
  const positioned = [];
1196
- for (let i = 0; i < leafNodes.length; i++) {
1197
- positioned.push({ node: leafNodes[i], pos: leafPositions[i] ?? i });
1198
- }
1339
+ positioned.push(...leafNodes);
1199
1340
  for (const p of packageNodes) {
1200
1341
  positioned.push({ node: p.node, pos: p.childIndex ?? Number.MAX_SAFE_INTEGER });
1201
1342
  }
@@ -1207,7 +1348,12 @@ function apiPackageChildNode(ctx, scoped, scopeSegs, parentApiDefinitionId, scop
1207
1348
  const overview = overviewRoute(nav);
1208
1349
  const overviewPageId = overview != null ? metaStr(overview.metadata, "pageId") : void 0;
1209
1350
  const apiDefinitionId = metaStr(seg.metadata, "apiDefinitionId") ?? parentApiDefinitionId;
1210
- const slug = structuralNodeSlug(ctx, overview?.fullPath, seg.section, scopePrefix);
1351
+ const slug = structuralNodeSlug(
1352
+ ctx,
1353
+ overview?.fullPath,
1354
+ stripLeadingScopePrefix(seg.section, scopePrefix),
1355
+ scopePrefix
1356
+ );
1211
1357
  const metaPointsTo = metaStr(seg.metadata, "pointsTo");
1212
1358
  const children = buildApiChildren(ctx, scoped, scopeSegs, apiDefinitionId, scopePrefix);
1213
1359
  return {
@@ -1218,12 +1364,15 @@ function apiPackageChildNode(ctx, scoped, scopeSegs, parentApiDefinitionId, scop
1218
1364
  slug,
1219
1365
  icon: metaStr(seg.metadata, "icon"),
1220
1366
  hidden: seg.hidden,
1221
- viewers: toViewers(metaStrArr(seg.metadata, "viewers"))
1367
+ authed: metaBool(seg.metadata, "authed"),
1368
+ viewers: toViewers(metaStrArr(seg.metadata, "viewers")),
1369
+ orphaned: metaBool(seg.metadata, "orphaned"),
1370
+ featureFlags: metaFeatureFlags(seg.metadata)
1222
1371
  }),
1223
1372
  apiDefinitionId: ApiDefinitionId(apiDefinitionId),
1224
1373
  availability: metaStr(seg.metadata, "availability"),
1225
1374
  overviewPageId: overviewPageId != null ? PageId(overviewPageId) : void 0,
1226
- noindex: void 0,
1375
+ noindex: metaBool(overview?.metadata, "noindex") ?? metaBool(seg.metadata, "noindex"),
1227
1376
  pointsTo: metaPointsTo != null ? pointsToSlug(metaPointsTo) : void 0,
1228
1377
  children,
1229
1378
  playground: void 0
@@ -1286,7 +1435,12 @@ function apiReferenceNode(ctx, scoped, scopeSegs, scopePrefix) {
1286
1435
  const overview = overviewRoute(nav);
1287
1436
  const overviewPageId = overview != null ? metaStr(overview.metadata, "pageId") : void 0;
1288
1437
  const apiDefinitionId = metaStr(seg.metadata, "apiDefinitionId") ?? "";
1289
- const slug = structuralNodeSlug(ctx, overview?.fullPath, seg.section, scopePrefix);
1438
+ const slug = structuralNodeSlug(
1439
+ ctx,
1440
+ overview?.fullPath,
1441
+ stripLeadingScopePrefix(seg.section, scopePrefix),
1442
+ scopePrefix
1443
+ );
1290
1444
  const metaPointsTo = metaStr(seg.metadata, "pointsTo");
1291
1445
  const children = buildApiChildren(ctx, scoped, scopeSegs, apiDefinitionId, scopePrefix);
1292
1446
  const changelog = attachedApiChangelogNode(ctx, scoped, scopeSegs, apiDefinitionId, scopePrefix);
@@ -1298,12 +1452,15 @@ function apiReferenceNode(ctx, scoped, scopeSegs, scopePrefix) {
1298
1452
  slug,
1299
1453
  icon: metaStr(seg.metadata, "icon"),
1300
1454
  hidden: seg.hidden,
1301
- viewers: mergeVariantViewers(seg)
1455
+ authed: metaBool(seg.metadata, "authed"),
1456
+ viewers: mergeVariantViewers(seg),
1457
+ orphaned: metaBool(seg.metadata, "orphaned"),
1458
+ featureFlags: metaFeatureFlags(seg.metadata)
1302
1459
  }),
1303
1460
  apiDefinitionId: ApiDefinitionId(apiDefinitionId),
1304
- availability: void 0,
1461
+ availability: metaStr(seg.metadata, "availability"),
1305
1462
  overviewPageId: overviewPageId != null ? PageId(overviewPageId) : void 0,
1306
- noindex: void 0,
1463
+ noindex: metaBool(overview?.metadata, "noindex") ?? metaBool(seg.metadata, "noindex"),
1307
1464
  paginated: true,
1308
1465
  showErrors: metaBool(seg.metadata, "showErrors"),
1309
1466
  hideTitle: metaBool(seg.metadata, "hideTitle"),
@@ -1356,12 +1513,15 @@ function changelogNode(ctx, scoped, scopePrefix) {
1356
1513
  slug: ctx.contentSlug(r.fullPath ?? ""),
1357
1514
  icon: metaStr(r.metadata, "icon"),
1358
1515
  hidden: r.hidden,
1359
- viewers: toViewers(metaStrArr(r.metadata, "viewers"))
1516
+ authed: metaBool(r.metadata, "authed"),
1517
+ viewers: toViewers(metaStrArr(r.metadata, "viewers")),
1518
+ orphaned: metaBool(r.metadata, "orphaned"),
1519
+ featureFlags: metaFeatureFlags(r.metadata)
1360
1520
  }),
1361
1521
  pageId: PageId(metaStr(r.metadata, "pageId") ?? ""),
1362
- noindex: void 0,
1522
+ noindex: metaBool(r.metadata, "noindex"),
1363
1523
  date: dateStr ?? "",
1364
- tags: void 0
1524
+ tags: metaStrArr(r.metadata, "tags")
1365
1525
  };
1366
1526
  let months = byYear.get(year);
1367
1527
  if (months == null) {
@@ -1405,10 +1565,13 @@ function changelogNode(ctx, scoped, scopePrefix) {
1405
1565
  slug: baseSlug,
1406
1566
  icon: metaStr(seg.metadata, "icon"),
1407
1567
  hidden: seg.hidden,
1408
- viewers: mergeVariantViewers(seg)
1568
+ authed: metaBool(seg.metadata, "authed"),
1569
+ viewers: mergeVariantViewers(seg),
1570
+ orphaned: metaBool(seg.metadata, "orphaned"),
1571
+ featureFlags: metaFeatureFlags(seg.metadata)
1409
1572
  }),
1410
1573
  overviewPageId: overviewPageId != null ? PageId(overviewPageId) : void 0,
1411
- noindex: void 0,
1574
+ noindex: metaBool(overview?.metadata, "noindex") ?? metaBool(seg.metadata, "noindex"),
1412
1575
  children: years
1413
1576
  };
1414
1577
  }
@@ -1465,17 +1628,55 @@ function buildSectionTree(ctx, scopeSegs, parentPath, scopePrefix, changelogScop
1465
1628
  return { node, childIndex };
1466
1629
  });
1467
1630
  }
1631
+ function computeSiblingSortBoundaries(ordered) {
1632
+ const result = /* @__PURE__ */ new Map();
1633
+ const sectionPaths = new Set(ordered.map((s) => s.seg.section));
1634
+ const rootSections = [];
1635
+ for (const s of ordered) {
1636
+ const type = metaStr(s.seg.metadata, "type");
1637
+ const sec = s.seg.section;
1638
+ if (type !== "section" || sec === "") {
1639
+ continue;
1640
+ }
1641
+ const parent = parentSectionPath(sec);
1642
+ const isRootLevel = parent === "" || !sectionPaths.has(parent) && nearestExistingAncestor(sec, sectionPaths) === "";
1643
+ if (isRootLevel) {
1644
+ rootSections.push(s);
1645
+ }
1646
+ }
1647
+ const byPath = /* @__PURE__ */ new Map();
1648
+ for (const s of rootSections) {
1649
+ const group = byPath.get(s.seg.section) ?? [];
1650
+ group.push(s);
1651
+ byPath.set(s.seg.section, group);
1652
+ }
1653
+ for (const [, siblings] of byPath) {
1654
+ if (siblings.length <= 1) {
1655
+ continue;
1656
+ }
1657
+ for (let i = 0; i < siblings.length; i++) {
1658
+ const lo = siblings[i].seg.sortOrder;
1659
+ const hi = i + 1 < siblings.length ? siblings[i + 1].seg.sortOrder : Number.MAX_SAFE_INTEGER;
1660
+ result.set(siblings[i], { lo, hi });
1661
+ }
1662
+ }
1663
+ return result;
1664
+ }
1468
1665
  function buildSidebarRootChildren(ctx, scopeSegs, scopePrefix, changelogScopePrefix) {
1469
1666
  const ordered = [...scopeSegs].sort((a, b) => a.seg.sortOrder - b.seg.sortOrder);
1470
1667
  const sectionPaths = new Set(ordered.map((s) => s.seg.section));
1471
1668
  const annotated = [];
1472
1669
  const hasRootSection = ordered.some((s) => metaStr(s.seg.metadata, "type") === "section" && s.seg.section === "");
1670
+ const siblingBoundaries = computeSiblingSortBoundaries(ordered);
1473
1671
  for (const s of ordered) {
1474
1672
  const type = metaStr(s.seg.metadata, "type");
1475
1673
  const sec = s.seg.section;
1476
1674
  if (hasOwningApiReference(s, scopeSegs)) {
1477
1675
  continue;
1478
1676
  }
1677
+ if (type === "tabRoot" && metaBool(s.seg.metadata, "landingPage") === true) {
1678
+ continue;
1679
+ }
1479
1680
  if (type === "tabRoot" || sec === "" && type !== "section") {
1480
1681
  for (const r of s.nav) {
1481
1682
  const node = artifactToNode(ctx, r);
@@ -1503,14 +1704,21 @@ function buildSidebarRootChildren(ctx, scopeSegs, scopePrefix, changelogScopePre
1503
1704
  } else if (type === "changelog") {
1504
1705
  node = changelogNode(ctx, s, changelogScopePrefix ?? scopePrefix);
1505
1706
  } else {
1707
+ const bounds = siblingBoundaries.get(s);
1708
+ const ownedSegs = bounds != null ? scopeSegs.filter((seg) => seg.seg.sortOrder >= bounds.lo && seg.seg.sortOrder < bounds.hi) : scopeSegs;
1506
1709
  node = sectionNode(
1507
1710
  ctx,
1508
1711
  s,
1509
- buildSectionTree(ctx, scopeSegs, sec, scopePrefix, changelogScopePrefix),
1712
+ buildSectionTree(ctx, ownedSegs, sec, scopePrefix, changelogScopePrefix),
1510
1713
  scopePrefix
1511
1714
  );
1512
1715
  }
1513
- annotated.push({ node, pos: metaNum(s.seg.metadata, "childIndex"), isLeaf: false });
1716
+ const isSharedSection = siblingBoundaries.size > 0 && scopePrefix !== "" && sec !== "" && !sec.startsWith(scopePrefix);
1717
+ annotated.push({
1718
+ node,
1719
+ pos: isSharedSection ? void 0 : metaNum(s.seg.metadata, "childIndex"),
1720
+ isLeaf: false
1721
+ });
1514
1722
  }
1515
1723
  }
1516
1724
  const hasLeafPos = annotated.some((a) => a.isLeaf && a.pos != null);
@@ -1562,13 +1770,13 @@ function buildSidebarRoot(ctx, scopeSegs, scopePrefix, changelogScopePrefix) {
1562
1770
  };
1563
1771
  }
1564
1772
  function buildFullRootFromSegments(options) {
1565
- const { basePath, title, segments, navBySegment } = options;
1773
+ const { basePath, rootSlug: sourceRootSlug, title, segments, navBySegment, skipSharedSectionMerge } = options;
1566
1774
  const ctx = createBuildContext(basePath);
1567
1775
  const scoped = segments.filter((seg) => {
1568
1776
  const t = metaStr(seg.metadata, "type");
1569
1777
  return t !== "files" && t !== "redirects";
1570
1778
  }).map((seg) => ({ seg, nav: navBySegment.get(seg.segmentHash) ?? [] }));
1571
- const rootSlug = asSlug(basePath);
1779
+ const rootSlug = asSlug(sourceRootSlug ?? basePath);
1572
1780
  const scopesByKey = /* @__PURE__ */ new Map();
1573
1781
  for (const s of scoped) {
1574
1782
  const key = scopeKey(s.seg);
@@ -1581,9 +1789,9 @@ function buildFullRootFromSegments(options) {
1581
1789
  const tabs = uniqueDetails(scoped, "tab");
1582
1790
  let child;
1583
1791
  if (products.length > 0) {
1584
- child = buildProductGroup(ctx, products, scoped, rootSlug);
1792
+ child = buildProductGroup(ctx, products, scoped, rootSlug, skipSharedSectionMerge);
1585
1793
  } else if (versions.length > 0) {
1586
- child = buildVersioned(ctx, versions, scoped, rootSlug, void 0);
1794
+ child = buildVersioned(ctx, versions, scoped, rootSlug, void 0, skipSharedSectionMerge);
1587
1795
  } else {
1588
1796
  child = buildUnversioned(ctx, scoped, rootSlug, tabs.length > 0);
1589
1797
  }
@@ -1594,6 +1802,24 @@ function buildFullRootFromSegments(options) {
1594
1802
  } else if (child.type === "versioned") {
1595
1803
  const defaultVersion = child.children.find((v) => v.default) ?? child.children[0];
1596
1804
  rootPointsTo = defaultVersion?.pointsTo;
1805
+ } else if (child.type === "unversioned") {
1806
+ if (tabs.length > 0) {
1807
+ const firstTab = tabs[0];
1808
+ const pt = firstTab != null ? metaStr(firstTab.metadata, "pointsTo") : void 0;
1809
+ rootPointsTo = pt != null ? pointsToSlug(pt) : void 0;
1810
+ } else {
1811
+ const sorted = [...scoped].sort((a, b) => a.seg.sortOrder - b.seg.sortOrder);
1812
+ for (const s of sorted) {
1813
+ if (s.seg.hidden) {
1814
+ continue;
1815
+ }
1816
+ const pt = metaStr(s.seg.metadata, "pointsTo");
1817
+ if (pt != null) {
1818
+ rootPointsTo = pointsToSlug(pt);
1819
+ break;
1820
+ }
1821
+ }
1822
+ }
1597
1823
  }
1598
1824
  const root = {
1599
1825
  type: "root",
@@ -1606,21 +1832,22 @@ function buildFullRootFromSegments(options) {
1606
1832
  return root;
1607
1833
  }
1608
1834
  function uniqueDetails(scoped, dim) {
1609
- const bySlug = /* @__PURE__ */ new Map();
1835
+ const byKey = /* @__PURE__ */ new Map();
1610
1836
  for (const s of scoped) {
1611
1837
  const d = s.seg[dim];
1612
1838
  if (d == null) {
1613
1839
  continue;
1614
1840
  }
1615
1841
  const slug = detailSlug(d);
1616
- const existing = bySlug.get(slug);
1842
+ const key = dim === "tab" ? d.id : slug !== "" ? slug : d.id;
1843
+ const existing = byKey.get(key);
1617
1844
  if (existing == null) {
1618
- bySlug.set(slug, d);
1845
+ byKey.set(key, d);
1619
1846
  } else if (metaStr(d.metadata, "pointsTo") != null && metaStr(existing.metadata, "pointsTo") == null) {
1620
- bySlug.set(slug, d);
1847
+ byKey.set(key, d);
1621
1848
  }
1622
1849
  }
1623
- return [...bySlug.values()].sort((a, b) => a.sortOrder - b.sortOrder);
1850
+ return [...byKey.values()].sort((a, b) => a.sortOrder - b.sortOrder);
1624
1851
  }
1625
1852
  function scopeSlugPrefix(rootSlug, ...details) {
1626
1853
  const parts = [rootSlug];
@@ -1654,7 +1881,7 @@ function buildVariantedNode(ctx, variants, scopeSegs, scopePrefix) {
1654
1881
  default: detailIsDefault(variant),
1655
1882
  variantId: VariantId(String(variant.id)),
1656
1883
  subtitle: metaStr(variant.metadata, "subtitle") ?? void 0,
1657
- image: void 0,
1884
+ image: metaStr(variant.metadata, "image") != null ? FileId(metaStr(variant.metadata, "image")) : void 0,
1658
1885
  pointsTo: metaPointsTo != null ? pointsToSlug(metaPointsTo) : void 0,
1659
1886
  children
1660
1887
  };
@@ -1691,7 +1918,10 @@ function buildTabbedOrSidebar(ctx, scopeSegs, tabsPresent, scopePrefix) {
1691
1918
  const tabDetails = uniqueDetails(scopeSegs, "tab");
1692
1919
  const children = tabDetails.map((tab) => {
1693
1920
  const tabType = metaStr(tab.metadata, "tab_type");
1694
- const tabSlug = asSlug(scopeSlugPrefix(scopePrefix, tab));
1921
+ const rawTabSlug = detailSlug(tab);
1922
+ const versionDetail = scopeSegs[0]?.seg.version;
1923
+ const versionSlugStr = versionDetail != null ? detailSlug(versionDetail) : void 0;
1924
+ const tabSlug = versionSlugStr != null && rawTabSlug !== "" && (rawTabSlug === versionSlugStr || rawTabSlug.startsWith(versionSlugStr + "/")) ? asSlug(rawTabSlug) : asSlug(scopeSlugPrefix(scopePrefix, tab));
1695
1925
  if (tabType === "link") {
1696
1926
  return {
1697
1927
  type: "link",
@@ -1700,7 +1930,7 @@ function buildTabbedOrSidebar(ctx, scopeSegs, tabsPresent, scopePrefix) {
1700
1930
  title: tab.displayName,
1701
1931
  icon: tab.icon ?? void 0,
1702
1932
  url: Url(metaStr(tab.metadata, "url") ?? ""),
1703
- target: void 0
1933
+ target: metaStr(tab.metadata, "target")
1704
1934
  };
1705
1935
  }
1706
1936
  if (tabType === "changelog") {
@@ -1761,16 +1991,71 @@ function buildUnversioned(ctx, scopeSegs, scopePrefix, tabsPresent) {
1761
1991
  type: "unversioned",
1762
1992
  id: ctx.nodeId(`unversioned:${first?.seg.product?.id ?? "root"}`),
1763
1993
  collapsed: void 0,
1764
- landingPage: void 0,
1994
+ landingPage: buildLandingPage(ctx, scopeSegs),
1765
1995
  // The enclosing scope prefix (product slug, or rootSlug at the top
1766
1996
  // level) roots structural sections so they share the scope's slug
1767
1997
  // namespace.
1768
1998
  child: buildTabbedOrSidebar(ctx, scopeSegs, tabsPresent, scopePrefix)
1769
1999
  };
1770
2000
  }
1771
- function buildVersioned(ctx, versions, scoped, rootSlug, productPrefix) {
2001
+ function mergeSharedSections(defaultSegs, allScoped, defaultVersion, versionSlugs) {
2002
+ const defaultTabsByName = /* @__PURE__ */ new Map();
2003
+ for (const s of defaultSegs) {
2004
+ if (s.seg.tab != null) {
2005
+ const tabName = s.seg.tab.displayName;
2006
+ if (!defaultTabsByName.has(tabName)) {
2007
+ defaultTabsByName.set(tabName, s.seg.tab);
2008
+ }
2009
+ }
2010
+ }
2011
+ const existingSections = new Set(
2012
+ defaultSegs.filter((s) => metaStr(s.seg.metadata, "type") === "section").map((s) => s.seg.section)
2013
+ );
2014
+ const maxSortOrder = defaultSegs.reduce((max, s) => Math.max(max, s.seg.sortOrder), 0);
2015
+ let nextSortOrder = maxSortOrder + 1;
2016
+ const merged = [...defaultSegs];
2017
+ for (const s of allScoped) {
2018
+ if (s.seg.version?.id === defaultVersion.id) {
2019
+ continue;
2020
+ }
2021
+ const type = metaStr(s.seg.metadata, "type");
2022
+ if (type !== "section") {
2023
+ continue;
2024
+ }
2025
+ const sectionPath = s.seg.section;
2026
+ if (versionSlugs.some((vs) => sectionPath === vs || sectionPath.startsWith(vs + "/"))) {
2027
+ continue;
2028
+ }
2029
+ if (existingSections.has(sectionPath)) {
2030
+ continue;
2031
+ }
2032
+ const sourceTabName = s.seg.tab?.displayName ?? "";
2033
+ const targetTab = defaultTabsByName.get(sourceTabName);
2034
+ if (targetTab == null) {
2035
+ continue;
2036
+ }
2037
+ const metadata = s.seg.metadata != null ? { ...s.seg.metadata, childIndex: void 0 } : s.seg.metadata;
2038
+ merged.push({
2039
+ seg: {
2040
+ ...s.seg,
2041
+ tab: targetTab,
2042
+ version: defaultVersion,
2043
+ sortOrder: nextSortOrder++,
2044
+ metadata
2045
+ },
2046
+ nav: s.nav
2047
+ });
2048
+ existingSections.add(sectionPath);
2049
+ }
2050
+ return merged;
2051
+ }
2052
+ function buildVersioned(ctx, versions, scoped, rootSlug, productPrefix, skipSharedSectionMerge) {
2053
+ const versionSlugs = versions.map((v) => detailSlug(v)).filter((s) => s !== "");
1772
2054
  const children = versions.map((version) => {
1773
- const versionSegs = scoped.filter((s) => s.seg.version?.id === version.id);
2055
+ let versionSegs = scoped.filter((s) => s.seg.version?.id === version.id);
2056
+ if (detailIsDefault(version) && !skipSharedSectionMerge) {
2057
+ versionSegs = mergeSharedSections(versionSegs, scoped, version, versionSlugs);
2058
+ }
1774
2059
  const tabsPresent = uniqueDetails(versionSegs, "tab").length > 0;
1775
2060
  const prefix = productPrefix != null ? productPrefix : rootSlug;
1776
2061
  const versionSegment = detailSlug(version);
@@ -1788,7 +2073,7 @@ function buildVersioned(ctx, versions, scoped, rootSlug, productPrefix) {
1788
2073
  default: detailIsDefault(version),
1789
2074
  versionId: VersionId(version.id),
1790
2075
  availability: void 0,
1791
- landingPage: void 0,
2076
+ landingPage: buildLandingPage(ctx, versionSegs),
1792
2077
  announcement: void 0,
1793
2078
  pointsTo: metaStr(version.metadata, "pointsTo") != null ? pointsToSlug(metaStr(version.metadata, "pointsTo")) : void 0,
1794
2079
  // The version's full slug roots structural sections so the whole
@@ -1800,6 +2085,7 @@ function buildVersioned(ctx, versions, scoped, rootSlug, productPrefix) {
1800
2085
  }
1801
2086
  return node;
1802
2087
  });
2088
+ markCrossVersionCanonicalSlugs(children);
1803
2089
  return {
1804
2090
  type: "versioned",
1805
2091
  id: ctx.nodeId(`versioned:${productPrefix ?? "root"}`),
@@ -1825,13 +2111,125 @@ function markDefaultVersionCanonicalSlugs(node, pruneSlug, versionSlug) {
1825
2111
  return;
1826
2112
  });
1827
2113
  }
1828
- function buildProductGroup(ctx, products, scoped, rootSlug) {
2114
+ function unversionedParents(parents) {
2115
+ let versionIdx = -1;
2116
+ for (let i = parents.length - 1; i >= 0; i--) {
2117
+ if (parents[i].type === "version") {
2118
+ versionIdx = i;
2119
+ break;
2120
+ }
2121
+ }
2122
+ return versionIdx >= 0 ? parents.slice(versionIdx + 1) : parents;
2123
+ }
2124
+ function crossVersionDedupKeys(node, parents) {
2125
+ const keys = [];
2126
+ const uParents = unversionedParents(parents);
2127
+ const pid = getPageId(node);
2128
+ if (pid != null) {
2129
+ keys.push(`pid:${pid}`);
2130
+ }
2131
+ if (node.type === "endpoint" || node.type === "webSocket" || node.type === "webhook" || node.type === "grpc" || node.type === "graphql") {
2132
+ const apiParentTitles = uParents.filter(
2133
+ (p) => p.type === "apiReference"
2134
+ ).map((p) => p.title.replaceAll(" ", ""));
2135
+ const apiPrefix = apiParentTitles.join(":");
2136
+ if (node.type === "endpoint") {
2137
+ keys.push(`${apiPrefix}:api:endpoint:${node.method}:${node.endpointId}`);
2138
+ } else if (node.type === "webSocket") {
2139
+ keys.push(`${apiPrefix}:api:websocket:${node.webSocketId}`);
2140
+ } else if (node.type === "webhook") {
2141
+ keys.push(`${apiPrefix}:api:webhook:${node.method}:${node.webhookId}`);
2142
+ } else if (node.type === "grpc") {
2143
+ keys.push(`${apiPrefix}:api:grpc:${node.method}:${node.grpcId}`);
2144
+ } else if (node.type === "graphql") {
2145
+ keys.push(`${apiPrefix}:api:graphql:${node.id}`);
2146
+ }
2147
+ }
2148
+ const parentTitles = [];
2149
+ for (const p of uParents) {
2150
+ if (hasMetadata(p)) {
2151
+ parentTitles.push(p.title);
2152
+ }
2153
+ }
2154
+ keys.push([...parentTitles, node.title].join("###"));
2155
+ return keys;
2156
+ }
2157
+ function routeToLandingPage(ctx, route) {
2158
+ if (route == null || route.type !== "markdown" || route.fullPath == null) {
2159
+ return void 0;
2160
+ }
2161
+ const md = route.metadata;
2162
+ const pageId = metaStr(md, "pageId");
2163
+ if (pageId == null) {
2164
+ return void 0;
2165
+ }
2166
+ const slug = ctx.contentSlug(route.fullPath);
2167
+ return {
2168
+ type: "landingPage",
2169
+ ...withMetadataDefaults({
2170
+ id: ctx.nodeId(`landing:${slug}`),
2171
+ title: metaStr(md, "title") ?? "",
2172
+ slug,
2173
+ icon: metaStr(md, "icon"),
2174
+ hidden: route.hidden,
2175
+ authed: metaBool(md, "authed"),
2176
+ viewers: toViewers(metaStrArr(md, "viewers")),
2177
+ orphaned: metaBool(md, "orphaned"),
2178
+ featureFlags: metaFeatureFlags(md)
2179
+ }),
2180
+ pageId: PageId(pageId),
2181
+ noindex: metaBool(md, "noindex")
2182
+ };
2183
+ }
2184
+ function buildLandingPage(ctx, scopeSegs) {
2185
+ const landingSegment = scopeSegs.find(
2186
+ (s) => metaStr(s.seg.metadata, "type") === "tabRoot" && metaBool(s.seg.metadata, "landingPage") === true
2187
+ );
2188
+ return routeToLandingPage(
2189
+ ctx,
2190
+ landingSegment?.nav.find((route) => route.type === "markdown")
2191
+ );
2192
+ }
2193
+ function markCrossVersionCanonicalSlugs(versionNodes) {
2194
+ const keyToCanonical = /* @__PURE__ */ new Map();
2195
+ const sorted = [...versionNodes].sort((a, b) => {
2196
+ if (a.default !== b.default) {
2197
+ return a.default ? -1 : 1;
2198
+ }
2199
+ return 0;
2200
+ });
2201
+ for (const version of sorted) {
2202
+ traverseDF(version, (child, parents) => {
2203
+ if (hasMetadata(child) && isPage(child)) {
2204
+ const keys = crossVersionDedupKeys(child, parents);
2205
+ let matched = false;
2206
+ for (const key of keys) {
2207
+ const canonical = keyToCanonical.get(key);
2208
+ if (canonical != null && !version.default) {
2209
+ child.canonicalSlug = canonical;
2210
+ matched = true;
2211
+ break;
2212
+ }
2213
+ }
2214
+ if (!matched) {
2215
+ const canonical = child.canonicalSlug ?? child.slug;
2216
+ for (const key of keys) {
2217
+ if (!keyToCanonical.has(key)) {
2218
+ keyToCanonical.set(key, canonical);
2219
+ }
2220
+ }
2221
+ }
2222
+ }
2223
+ });
2224
+ }
2225
+ }
2226
+ function buildProductGroup(ctx, products, scoped, rootSlug, skipSharedSectionMerge) {
1829
2227
  const children = products.map((product) => {
1830
2228
  const productSegs = scoped.filter((s) => s.seg.product?.id === product.id);
1831
2229
  const versions = uniqueDetails(productSegs, "version");
1832
2230
  const tabsPresent = uniqueDetails(productSegs, "tab").length > 0;
1833
2231
  const productPrefix = scopeSlugPrefix(rootSlug, product);
1834
- const productChild = versions.length > 0 ? buildVersioned(ctx, versions, productSegs, rootSlug, productPrefix) : buildUnversioned(ctx, productSegs, asSlug(productPrefix), tabsPresent);
2232
+ const productChild = versions.length > 0 ? buildVersioned(ctx, versions, productSegs, rootSlug, productPrefix, skipSharedSectionMerge) : buildUnversioned(ctx, productSegs, asSlug(productPrefix), tabsPresent);
1835
2233
  const node = {
1836
2234
  type: "product",
1837
2235
  ...withMetadataDefaults({
@@ -1850,7 +2248,7 @@ function buildProductGroup(ctx, products, scoped, rootSlug) {
1850
2248
  // here; sourcing it from the hash makes that filter match nothing.
1851
2249
  productId: ProductId(product.displayName),
1852
2250
  subtitle: metaStr(product.metadata, "subtitle") ?? "",
1853
- image: void 0,
2251
+ image: metaStr(product.metadata, "image") != null ? FileId(metaStr(product.metadata, "image")) : void 0,
1854
2252
  announcement: void 0,
1855
2253
  pointsTo: metaStr(product.metadata, "pointsTo") != null ? pointsToSlug(metaStr(product.metadata, "pointsTo")) : void 0,
1856
2254
  child: productChild
@@ -1861,7 +2259,10 @@ function buildProductGroup(ctx, products, scoped, rootSlug) {
1861
2259
  type: "productgroup",
1862
2260
  id: ctx.nodeId("productgroup"),
1863
2261
  collapsed: void 0,
1864
- landingPage: void 0,
2262
+ landingPage: buildLandingPage(
2263
+ ctx,
2264
+ scoped.filter((s) => s.seg.product == null)
2265
+ ),
1865
2266
  children
1866
2267
  };
1867
2268
  }