@fern-api/fdr-sdk 1.2.39-008eed59b3 → 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 (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
@@ -1082,6 +1082,63 @@ function apiLeafNodeFromNavRouteInternal(ctx, route, segmentApiDefinitionId2) {
1082
1082
  }
1083
1083
  return void 0;
1084
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
+ }
1085
1142
  function collectApiLeafNodesFromNav(routes, basePath, segmentApiDefinitionId2) {
1086
1143
  const ctx = createBuildContext(basePath);
1087
1144
  return routes.map((route) => apiLeafNodeFromNavRouteInternal(ctx, route, segmentApiDefinitionId2)).filter((node) => node != null);
@@ -1221,8 +1278,7 @@ function directChildApiPackages(parentSection, scopeSegs) {
1221
1278
  }
1222
1279
  function buildApiChildren(ctx, scoped, scopeSegs, apiDefinitionId, scopePrefix) {
1223
1280
  const { seg, nav } = scoped;
1224
- const rawLeafNodes = nav.map((route) => apiLeafNodeFromNavRouteInternal(ctx, route, apiDefinitionId)).filter((node) => node != null);
1225
- const leafNodes = mergeEndpointPairs(ctx, rawLeafNodes, nav);
1281
+ const leafNodes = positionedApiLeafChildren(ctx, nav, apiDefinitionId);
1226
1282
  const childPackages = directChildApiPackages(seg.section, scopeSegs);
1227
1283
  const packageNodes = childPackages.map((child) => ({
1228
1284
  node: apiPackageChildNode(
@@ -1236,18 +1292,10 @@ function buildApiChildren(ctx, scoped, scopeSegs, apiDefinitionId, scopePrefix)
1236
1292
  }));
1237
1293
  const hasChildIndex = packageNodes.some((p) => p.childIndex != null);
1238
1294
  if (!hasChildIndex) {
1239
- return [...leafNodes, ...packageNodes.map((p) => p.node)];
1240
- }
1241
- const leafPositions = [];
1242
- for (const route of nav) {
1243
- if (route.displaySortOrder != null) {
1244
- leafPositions.push(route.displaySortOrder);
1245
- }
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);
1246
1296
  }
1247
1297
  const positioned = [];
1248
- for (let i = 0; i < leafNodes.length; i++) {
1249
- positioned.push({ node: leafNodes[i], pos: leafPositions[i] ?? i });
1250
- }
1298
+ positioned.push(...leafNodes);
1251
1299
  for (const p of packageNodes) {
1252
1300
  positioned.push({ node: p.node, pos: p.childIndex ?? Number.MAX_SAFE_INTEGER });
1253
1301
  }
@@ -1432,7 +1480,7 @@ function changelogNode(ctx, scoped, scopePrefix) {
1432
1480
  pageId: PageId(metaStr(r.metadata, "pageId") ?? ""),
1433
1481
  noindex: metaBool(r.metadata, "noindex"),
1434
1482
  date: dateStr ?? "",
1435
- tags: void 0
1483
+ tags: metaStrArr(r.metadata, "tags")
1436
1484
  };
1437
1485
  let months = byYear.get(year);
1438
1486
  if (months == null) {
@@ -1539,17 +1587,55 @@ function buildSectionTree(ctx, scopeSegs, parentPath, scopePrefix, changelogScop
1539
1587
  return { node, childIndex };
1540
1588
  });
1541
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
+ }
1542
1624
  function buildSidebarRootChildren(ctx, scopeSegs, scopePrefix, changelogScopePrefix) {
1543
1625
  const ordered = [...scopeSegs].sort((a, b) => a.seg.sortOrder - b.seg.sortOrder);
1544
1626
  const sectionPaths = new Set(ordered.map((s) => s.seg.section));
1545
1627
  const annotated = [];
1546
1628
  const hasRootSection = ordered.some((s) => metaStr(s.seg.metadata, "type") === "section" && s.seg.section === "");
1629
+ const siblingBoundaries = computeSiblingSortBoundaries(ordered);
1547
1630
  for (const s of ordered) {
1548
1631
  const type = metaStr(s.seg.metadata, "type");
1549
1632
  const sec = s.seg.section;
1550
1633
  if (hasOwningApiReference(s, scopeSegs)) {
1551
1634
  continue;
1552
1635
  }
1636
+ if (type === "tabRoot" && metaBool(s.seg.metadata, "landingPage") === true) {
1637
+ continue;
1638
+ }
1553
1639
  if (type === "tabRoot" || sec === "" && type !== "section") {
1554
1640
  for (const r of s.nav) {
1555
1641
  const node = artifactToNode(ctx, r);
@@ -1577,14 +1663,21 @@ function buildSidebarRootChildren(ctx, scopeSegs, scopePrefix, changelogScopePre
1577
1663
  } else if (type === "changelog") {
1578
1664
  node = changelogNode(ctx, s, changelogScopePrefix ?? scopePrefix);
1579
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;
1580
1668
  node = sectionNode(
1581
1669
  ctx,
1582
1670
  s,
1583
- buildSectionTree(ctx, scopeSegs, sec, scopePrefix, changelogScopePrefix),
1671
+ buildSectionTree(ctx, ownedSegs, sec, scopePrefix, changelogScopePrefix),
1584
1672
  scopePrefix
1585
1673
  );
1586
1674
  }
1587
- 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
+ });
1588
1681
  }
1589
1682
  }
1590
1683
  const hasLeafPos = annotated.some((a) => a.isLeaf && a.pos != null);
@@ -1636,13 +1729,13 @@ function buildSidebarRoot(ctx, scopeSegs, scopePrefix, changelogScopePrefix) {
1636
1729
  };
1637
1730
  }
1638
1731
  function buildFullRootFromSegments(options) {
1639
- const { basePath, title, segments, navBySegment } = options;
1732
+ const { basePath, rootSlug: sourceRootSlug, title, segments, navBySegment, skipSharedSectionMerge } = options;
1640
1733
  const ctx = createBuildContext(basePath);
1641
1734
  const scoped = segments.filter((seg) => {
1642
1735
  const t = metaStr(seg.metadata, "type");
1643
1736
  return t !== "files" && t !== "redirects";
1644
1737
  }).map((seg) => ({ seg, nav: navBySegment.get(seg.segmentHash) ?? [] }));
1645
- const rootSlug = asSlug(basePath);
1738
+ const rootSlug = asSlug(sourceRootSlug ?? basePath);
1646
1739
  const scopesByKey = /* @__PURE__ */ new Map();
1647
1740
  for (const s of scoped) {
1648
1741
  const key = scopeKey(s.seg);
@@ -1655,9 +1748,9 @@ function buildFullRootFromSegments(options) {
1655
1748
  const tabs = uniqueDetails(scoped, "tab");
1656
1749
  let child;
1657
1750
  if (products.length > 0) {
1658
- child = buildProductGroup(ctx, products, scoped, rootSlug);
1751
+ child = buildProductGroup(ctx, products, scoped, rootSlug, skipSharedSectionMerge);
1659
1752
  } else if (versions.length > 0) {
1660
- child = buildVersioned(ctx, versions, scoped, rootSlug, void 0);
1753
+ child = buildVersioned(ctx, versions, scoped, rootSlug, void 0, skipSharedSectionMerge);
1661
1754
  } else {
1662
1755
  child = buildUnversioned(ctx, scoped, rootSlug, tabs.length > 0);
1663
1756
  }
@@ -1705,7 +1798,7 @@ function uniqueDetails(scoped, dim) {
1705
1798
  continue;
1706
1799
  }
1707
1800
  const slug = detailSlug(d);
1708
- const key = slug !== "" ? slug : d.id;
1801
+ const key = dim === "tab" ? d.id : slug !== "" ? slug : d.id;
1709
1802
  const existing = byKey.get(key);
1710
1803
  if (existing == null) {
1711
1804
  byKey.set(key, d);
@@ -1857,16 +1950,71 @@ function buildUnversioned(ctx, scopeSegs, scopePrefix, tabsPresent) {
1857
1950
  type: "unversioned",
1858
1951
  id: ctx.nodeId(`unversioned:${first?.seg.product?.id ?? "root"}`),
1859
1952
  collapsed: void 0,
1860
- landingPage: void 0,
1953
+ landingPage: buildLandingPage(ctx, scopeSegs),
1861
1954
  // The enclosing scope prefix (product slug, or rootSlug at the top
1862
1955
  // level) roots structural sections so they share the scope's slug
1863
1956
  // namespace.
1864
1957
  child: buildTabbedOrSidebar(ctx, scopeSegs, tabsPresent, scopePrefix)
1865
1958
  };
1866
1959
  }
1867
- 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 !== "");
1868
2013
  const children = versions.map((version) => {
1869
- 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
+ }
1870
2018
  const tabsPresent = uniqueDetails(versionSegs, "tab").length > 0;
1871
2019
  const prefix = productPrefix != null ? productPrefix : rootSlug;
1872
2020
  const versionSegment = detailSlug(version);
@@ -1884,7 +2032,7 @@ function buildVersioned(ctx, versions, scoped, rootSlug, productPrefix) {
1884
2032
  default: detailIsDefault(version),
1885
2033
  versionId: VersionId(version.id),
1886
2034
  availability: void 0,
1887
- landingPage: buildVersionLandingPage(ctx, versionSegs, asSlug(slug)),
2035
+ landingPage: buildLandingPage(ctx, versionSegs),
1888
2036
  announcement: void 0,
1889
2037
  pointsTo: metaStr(version.metadata, "pointsTo") != null ? pointsToSlug(metaStr(version.metadata, "pointsTo")) : void 0,
1890
2038
  // The version's full slug roots structural sections so the whole
@@ -1965,34 +2113,41 @@ function crossVersionDedupKeys(node, parents) {
1965
2113
  keys.push([...parentTitles, node.title].join("###"));
1966
2114
  return keys;
1967
2115
  }
1968
- function buildVersionLandingPage(ctx, versionSegs, versionSlug) {
1969
- const tablessSegs = versionSegs.filter((s) => s.seg.tab == null);
1970
- if (tablessSegs.length === 0) {
2116
+ function routeToLandingPage(ctx, route) {
2117
+ if (route == null || route.type !== "markdown" || route.fullPath == null) {
1971
2118
  return void 0;
1972
2119
  }
1973
- for (const s of tablessSegs) {
1974
- for (const r of s.nav) {
1975
- if (r.type === "markdown" && r.fullPath != null) {
1976
- const md = r.metadata;
1977
- const pageId = metaStr(md, "pageId") ?? "";
1978
- const slug = ctx.contentSlug(r.fullPath);
1979
- return {
1980
- type: "landingPage",
1981
- ...withMetadataDefaults({
1982
- id: ctx.nodeId(`landing:${slug}`),
1983
- title: metaStr(md, "title") ?? "",
1984
- slug,
1985
- icon: metaStr(md, "icon"),
1986
- hidden: r.hidden,
1987
- viewers: toViewers(metaStrArr(md, "viewers"))
1988
- }),
1989
- pageId: PageId(pageId),
1990
- noindex: metaBool(md, "noindex")
1991
- };
1992
- }
1993
- }
2120
+ const md = route.metadata;
2121
+ const pageId = metaStr(md, "pageId");
2122
+ if (pageId == null) {
2123
+ return void 0;
1994
2124
  }
1995
- return void 0;
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
+ );
1996
2151
  }
1997
2152
  function markCrossVersionCanonicalSlugs(versionNodes) {
1998
2153
  const keyToCanonical = /* @__PURE__ */ new Map();
@@ -2027,13 +2182,13 @@ function markCrossVersionCanonicalSlugs(versionNodes) {
2027
2182
  });
2028
2183
  }
2029
2184
  }
2030
- function buildProductGroup(ctx, products, scoped, rootSlug) {
2185
+ function buildProductGroup(ctx, products, scoped, rootSlug, skipSharedSectionMerge) {
2031
2186
  const children = products.map((product) => {
2032
2187
  const productSegs = scoped.filter((s) => s.seg.product?.id === product.id);
2033
2188
  const versions = uniqueDetails(productSegs, "version");
2034
2189
  const tabsPresent = uniqueDetails(productSegs, "tab").length > 0;
2035
2190
  const productPrefix = scopeSlugPrefix(rootSlug, product);
2036
- 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);
2037
2192
  const node = {
2038
2193
  type: "product",
2039
2194
  ...withMetadataDefaults({
@@ -2063,7 +2218,10 @@ function buildProductGroup(ctx, products, scoped, rootSlug) {
2063
2218
  type: "productgroup",
2064
2219
  id: ctx.nodeId("productgroup"),
2065
2220
  collapsed: void 0,
2066
- landingPage: void 0,
2221
+ landingPage: buildLandingPage(
2222
+ ctx,
2223
+ scoped.filter((s) => s.seg.product == null)
2224
+ ),
2067
2225
  children
2068
2226
  };
2069
2227
  }