@fern-api/fdr-sdk 1.2.39-008eed59b3 → 1.2.39-438fcc7e23

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 (34) hide show
  1. package/dist/js/client/FdrClient.js +1 -0
  2. package/dist/js/client/FdrClient.js.map +1 -1
  3. package/dist/js/client/FdrClient.mjs +1 -0
  4. package/dist/js/client/FdrClient.mjs.map +1 -1
  5. package/dist/js/index.js +1 -0
  6. package/dist/js/index.js.map +1 -1
  7. package/dist/js/index.mjs +1 -0
  8. package/dist/js/index.mjs.map +1 -1
  9. package/dist/js/navigation/ledger-root-builder.js +210 -52
  10. package/dist/js/navigation/ledger-root-builder.js.map +1 -1
  11. package/dist/js/navigation/ledger-root-builder.mjs +210 -52
  12. package/dist/js/navigation/ledger-root-builder.mjs.map +1 -1
  13. package/dist/js/orpc-client.js +1 -0
  14. package/dist/js/orpc-client.js.map +1 -1
  15. package/dist/js/orpc-client.mjs +1 -0
  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/ledger-root-builder.d.ts +12 -0
  29. package/dist/types/navigation/ledger-root-builder.d.ts.map +1 -1
  30. package/dist/types/orpc-client/docs-ledger/contract.d.ts +50 -0
  31. package/dist/types/orpc-client/docs-ledger/contract.d.ts.map +1 -1
  32. package/dist/types/orpc-client/docs-ledger/ledger-manifest.d.ts +12 -0
  33. package/dist/types/orpc-client/docs-ledger/ledger-manifest.d.ts.map +1 -1
  34. package/package.json +1 -1
@@ -1123,6 +1123,63 @@ function apiLeafNodeFromNavRouteInternal(ctx, route, segmentApiDefinitionId2) {
1123
1123
  }
1124
1124
  return void 0;
1125
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
+ }
1126
1183
  function collectApiLeafNodesFromNav(routes, basePath, segmentApiDefinitionId2) {
1127
1184
  const ctx = createBuildContext(basePath);
1128
1185
  return routes.map((route) => apiLeafNodeFromNavRouteInternal(ctx, route, segmentApiDefinitionId2)).filter((node) => node != null);
@@ -1262,8 +1319,7 @@ function directChildApiPackages(parentSection, scopeSegs) {
1262
1319
  }
1263
1320
  function buildApiChildren(ctx, scoped, scopeSegs, apiDefinitionId, scopePrefix) {
1264
1321
  const { seg, nav } = scoped;
1265
- const rawLeafNodes = nav.map((route) => apiLeafNodeFromNavRouteInternal(ctx, route, apiDefinitionId)).filter((node) => node != null);
1266
- const leafNodes = mergeEndpointPairs(ctx, rawLeafNodes, nav);
1322
+ const leafNodes = positionedApiLeafChildren(ctx, nav, apiDefinitionId);
1267
1323
  const childPackages = directChildApiPackages(seg.section, scopeSegs);
1268
1324
  const packageNodes = childPackages.map((child) => ({
1269
1325
  node: apiPackageChildNode(
@@ -1277,18 +1333,10 @@ function buildApiChildren(ctx, scoped, scopeSegs, apiDefinitionId, scopePrefix)
1277
1333
  }));
1278
1334
  const hasChildIndex = packageNodes.some((p) => p.childIndex != null);
1279
1335
  if (!hasChildIndex) {
1280
- return [...leafNodes, ...packageNodes.map((p) => p.node)];
1281
- }
1282
- const leafPositions = [];
1283
- for (const route of nav) {
1284
- if (route.displaySortOrder != null) {
1285
- leafPositions.push(route.displaySortOrder);
1286
- }
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);
1287
1337
  }
1288
1338
  const positioned = [];
1289
- for (let i = 0; i < leafNodes.length; i++) {
1290
- positioned.push({ node: leafNodes[i], pos: leafPositions[i] ?? i });
1291
- }
1339
+ positioned.push(...leafNodes);
1292
1340
  for (const p of packageNodes) {
1293
1341
  positioned.push({ node: p.node, pos: p.childIndex ?? Number.MAX_SAFE_INTEGER });
1294
1342
  }
@@ -1473,7 +1521,7 @@ function changelogNode(ctx, scoped, scopePrefix) {
1473
1521
  pageId: PageId(metaStr(r.metadata, "pageId") ?? ""),
1474
1522
  noindex: metaBool(r.metadata, "noindex"),
1475
1523
  date: dateStr ?? "",
1476
- tags: void 0
1524
+ tags: metaStrArr(r.metadata, "tags")
1477
1525
  };
1478
1526
  let months = byYear.get(year);
1479
1527
  if (months == null) {
@@ -1580,17 +1628,55 @@ function buildSectionTree(ctx, scopeSegs, parentPath, scopePrefix, changelogScop
1580
1628
  return { node, childIndex };
1581
1629
  });
1582
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
+ }
1583
1665
  function buildSidebarRootChildren(ctx, scopeSegs, scopePrefix, changelogScopePrefix) {
1584
1666
  const ordered = [...scopeSegs].sort((a, b) => a.seg.sortOrder - b.seg.sortOrder);
1585
1667
  const sectionPaths = new Set(ordered.map((s) => s.seg.section));
1586
1668
  const annotated = [];
1587
1669
  const hasRootSection = ordered.some((s) => metaStr(s.seg.metadata, "type") === "section" && s.seg.section === "");
1670
+ const siblingBoundaries = computeSiblingSortBoundaries(ordered);
1588
1671
  for (const s of ordered) {
1589
1672
  const type = metaStr(s.seg.metadata, "type");
1590
1673
  const sec = s.seg.section;
1591
1674
  if (hasOwningApiReference(s, scopeSegs)) {
1592
1675
  continue;
1593
1676
  }
1677
+ if (type === "tabRoot" && metaBool(s.seg.metadata, "landingPage") === true) {
1678
+ continue;
1679
+ }
1594
1680
  if (type === "tabRoot" || sec === "" && type !== "section") {
1595
1681
  for (const r of s.nav) {
1596
1682
  const node = artifactToNode(ctx, r);
@@ -1618,14 +1704,21 @@ function buildSidebarRootChildren(ctx, scopeSegs, scopePrefix, changelogScopePre
1618
1704
  } else if (type === "changelog") {
1619
1705
  node = changelogNode(ctx, s, changelogScopePrefix ?? scopePrefix);
1620
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;
1621
1709
  node = sectionNode(
1622
1710
  ctx,
1623
1711
  s,
1624
- buildSectionTree(ctx, scopeSegs, sec, scopePrefix, changelogScopePrefix),
1712
+ buildSectionTree(ctx, ownedSegs, sec, scopePrefix, changelogScopePrefix),
1625
1713
  scopePrefix
1626
1714
  );
1627
1715
  }
1628
- 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
+ });
1629
1722
  }
1630
1723
  }
1631
1724
  const hasLeafPos = annotated.some((a) => a.isLeaf && a.pos != null);
@@ -1677,13 +1770,13 @@ function buildSidebarRoot(ctx, scopeSegs, scopePrefix, changelogScopePrefix) {
1677
1770
  };
1678
1771
  }
1679
1772
  function buildFullRootFromSegments(options) {
1680
- const { basePath, title, segments, navBySegment } = options;
1773
+ const { basePath, rootSlug: sourceRootSlug, title, segments, navBySegment, skipSharedSectionMerge } = options;
1681
1774
  const ctx = createBuildContext(basePath);
1682
1775
  const scoped = segments.filter((seg) => {
1683
1776
  const t = metaStr(seg.metadata, "type");
1684
1777
  return t !== "files" && t !== "redirects";
1685
1778
  }).map((seg) => ({ seg, nav: navBySegment.get(seg.segmentHash) ?? [] }));
1686
- const rootSlug = asSlug(basePath);
1779
+ const rootSlug = asSlug(sourceRootSlug ?? basePath);
1687
1780
  const scopesByKey = /* @__PURE__ */ new Map();
1688
1781
  for (const s of scoped) {
1689
1782
  const key = scopeKey(s.seg);
@@ -1696,9 +1789,9 @@ function buildFullRootFromSegments(options) {
1696
1789
  const tabs = uniqueDetails(scoped, "tab");
1697
1790
  let child;
1698
1791
  if (products.length > 0) {
1699
- child = buildProductGroup(ctx, products, scoped, rootSlug);
1792
+ child = buildProductGroup(ctx, products, scoped, rootSlug, skipSharedSectionMerge);
1700
1793
  } else if (versions.length > 0) {
1701
- child = buildVersioned(ctx, versions, scoped, rootSlug, void 0);
1794
+ child = buildVersioned(ctx, versions, scoped, rootSlug, void 0, skipSharedSectionMerge);
1702
1795
  } else {
1703
1796
  child = buildUnversioned(ctx, scoped, rootSlug, tabs.length > 0);
1704
1797
  }
@@ -1746,7 +1839,7 @@ function uniqueDetails(scoped, dim) {
1746
1839
  continue;
1747
1840
  }
1748
1841
  const slug = detailSlug(d);
1749
- const key = slug !== "" ? slug : d.id;
1842
+ const key = dim === "tab" ? d.id : slug !== "" ? slug : d.id;
1750
1843
  const existing = byKey.get(key);
1751
1844
  if (existing == null) {
1752
1845
  byKey.set(key, d);
@@ -1898,16 +1991,71 @@ function buildUnversioned(ctx, scopeSegs, scopePrefix, tabsPresent) {
1898
1991
  type: "unversioned",
1899
1992
  id: ctx.nodeId(`unversioned:${first?.seg.product?.id ?? "root"}`),
1900
1993
  collapsed: void 0,
1901
- landingPage: void 0,
1994
+ landingPage: buildLandingPage(ctx, scopeSegs),
1902
1995
  // The enclosing scope prefix (product slug, or rootSlug at the top
1903
1996
  // level) roots structural sections so they share the scope's slug
1904
1997
  // namespace.
1905
1998
  child: buildTabbedOrSidebar(ctx, scopeSegs, tabsPresent, scopePrefix)
1906
1999
  };
1907
2000
  }
1908
- 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 !== "");
1909
2054
  const children = versions.map((version) => {
1910
- 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
+ }
1911
2059
  const tabsPresent = uniqueDetails(versionSegs, "tab").length > 0;
1912
2060
  const prefix = productPrefix != null ? productPrefix : rootSlug;
1913
2061
  const versionSegment = detailSlug(version);
@@ -1925,7 +2073,7 @@ function buildVersioned(ctx, versions, scoped, rootSlug, productPrefix) {
1925
2073
  default: detailIsDefault(version),
1926
2074
  versionId: VersionId(version.id),
1927
2075
  availability: void 0,
1928
- landingPage: buildVersionLandingPage(ctx, versionSegs, asSlug(slug)),
2076
+ landingPage: buildLandingPage(ctx, versionSegs),
1929
2077
  announcement: void 0,
1930
2078
  pointsTo: metaStr(version.metadata, "pointsTo") != null ? pointsToSlug(metaStr(version.metadata, "pointsTo")) : void 0,
1931
2079
  // The version's full slug roots structural sections so the whole
@@ -2006,34 +2154,41 @@ function crossVersionDedupKeys(node, parents) {
2006
2154
  keys.push([...parentTitles, node.title].join("###"));
2007
2155
  return keys;
2008
2156
  }
2009
- function buildVersionLandingPage(ctx, versionSegs, versionSlug) {
2010
- const tablessSegs = versionSegs.filter((s) => s.seg.tab == null);
2011
- if (tablessSegs.length === 0) {
2157
+ function routeToLandingPage(ctx, route) {
2158
+ if (route == null || route.type !== "markdown" || route.fullPath == null) {
2012
2159
  return void 0;
2013
2160
  }
2014
- for (const s of tablessSegs) {
2015
- for (const r of s.nav) {
2016
- if (r.type === "markdown" && r.fullPath != null) {
2017
- const md = r.metadata;
2018
- const pageId = metaStr(md, "pageId") ?? "";
2019
- const slug = ctx.contentSlug(r.fullPath);
2020
- return {
2021
- type: "landingPage",
2022
- ...withMetadataDefaults({
2023
- id: ctx.nodeId(`landing:${slug}`),
2024
- title: metaStr(md, "title") ?? "",
2025
- slug,
2026
- icon: metaStr(md, "icon"),
2027
- hidden: r.hidden,
2028
- viewers: toViewers(metaStrArr(md, "viewers"))
2029
- }),
2030
- pageId: PageId(pageId),
2031
- noindex: metaBool(md, "noindex")
2032
- };
2033
- }
2034
- }
2161
+ const md = route.metadata;
2162
+ const pageId = metaStr(md, "pageId");
2163
+ if (pageId == null) {
2164
+ return void 0;
2035
2165
  }
2036
- return void 0;
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
+ );
2037
2192
  }
2038
2193
  function markCrossVersionCanonicalSlugs(versionNodes) {
2039
2194
  const keyToCanonical = /* @__PURE__ */ new Map();
@@ -2068,13 +2223,13 @@ function markCrossVersionCanonicalSlugs(versionNodes) {
2068
2223
  });
2069
2224
  }
2070
2225
  }
2071
- function buildProductGroup(ctx, products, scoped, rootSlug) {
2226
+ function buildProductGroup(ctx, products, scoped, rootSlug, skipSharedSectionMerge) {
2072
2227
  const children = products.map((product) => {
2073
2228
  const productSegs = scoped.filter((s) => s.seg.product?.id === product.id);
2074
2229
  const versions = uniqueDetails(productSegs, "version");
2075
2230
  const tabsPresent = uniqueDetails(productSegs, "tab").length > 0;
2076
2231
  const productPrefix = scopeSlugPrefix(rootSlug, product);
2077
- 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);
2078
2233
  const node = {
2079
2234
  type: "product",
2080
2235
  ...withMetadataDefaults({
@@ -2104,7 +2259,10 @@ function buildProductGroup(ctx, products, scoped, rootSlug) {
2104
2259
  type: "productgroup",
2105
2260
  id: ctx.nodeId("productgroup"),
2106
2261
  collapsed: void 0,
2107
- landingPage: void 0,
2262
+ landingPage: buildLandingPage(
2263
+ ctx,
2264
+ scoped.filter((s) => s.seg.product == null)
2265
+ ),
2108
2266
  children
2109
2267
  };
2110
2268
  }