@fern-api/fdr-sdk 1.2.42-d3871329d3 → 1.2.42-e01a8637c3
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.
- package/dist/js/client/FdrClient.js +1 -0
- package/dist/js/client/FdrClient.js.map +1 -1
- package/dist/js/client/FdrClient.mjs +1 -0
- package/dist/js/client/FdrClient.mjs.map +1 -1
- package/dist/js/index.js +1 -0
- package/dist/js/index.js.map +1 -1
- package/dist/js/index.mjs +1 -0
- package/dist/js/index.mjs.map +1 -1
- package/dist/js/navigation/ledger-root-builder.js +107 -22
- package/dist/js/navigation/ledger-root-builder.js.map +1 -1
- package/dist/js/navigation/ledger-root-builder.mjs +107 -22
- package/dist/js/navigation/ledger-root-builder.mjs.map +1 -1
- package/dist/js/orpc-client.js +1 -0
- package/dist/js/orpc-client.js.map +1 -1
- package/dist/js/orpc-client.mjs +1 -0
- package/dist/js/orpc-client.mjs.map +1 -1
- package/dist/navigation/__test__/ledger-root-builder.crossProductDedup.test.d.ts +2 -0
- package/dist/navigation/__test__/ledger-root-builder.crossProductDedup.test.d.ts.map +1 -0
- package/dist/navigation/__test__/ledger-root-builder.crossProductDedup.test.js +267 -0
- package/dist/navigation/__test__/ledger-root-builder.crossProductDedup.test.js.map +1 -0
- package/dist/navigation/__test__/ledger-root-builder.ledgerParity.test.d.ts +2 -0
- package/dist/navigation/__test__/ledger-root-builder.ledgerParity.test.d.ts.map +1 -0
- package/dist/navigation/__test__/ledger-root-builder.ledgerParity.test.js +306 -0
- package/dist/navigation/__test__/ledger-root-builder.ledgerParity.test.js.map +1 -0
- package/dist/navigation/__test__/ledger-root-builder.sidebarDepth.test.d.ts +2 -0
- package/dist/navigation/__test__/ledger-root-builder.sidebarDepth.test.d.ts.map +1 -0
- package/dist/navigation/__test__/ledger-root-builder.sidebarDepth.test.js +273 -0
- package/dist/navigation/__test__/ledger-root-builder.sidebarDepth.test.js.map +1 -0
- package/dist/navigation/__test__/ledger-root-builder.skipSlugGroupOrdering.test.d.ts +2 -0
- package/dist/navigation/__test__/ledger-root-builder.skipSlugGroupOrdering.test.d.ts.map +1 -0
- package/dist/navigation/__test__/ledger-root-builder.skipSlugGroupOrdering.test.js +234 -0
- package/dist/navigation/__test__/ledger-root-builder.skipSlugGroupOrdering.test.js.map +1 -0
- package/dist/navigation/__test__/ledger-root-builder.skipSlugSiblingSection.test.d.ts +2 -0
- package/dist/navigation/__test__/ledger-root-builder.skipSlugSiblingSection.test.d.ts.map +1 -0
- package/dist/navigation/__test__/ledger-root-builder.skipSlugSiblingSection.test.js +256 -0
- package/dist/navigation/__test__/ledger-root-builder.skipSlugSiblingSection.test.js.map +1 -0
- package/dist/navigation/ledger-root-builder.d.ts.map +1 -1
- package/dist/navigation/ledger-root-builder.js +192 -33
- package/dist/navigation/ledger-root-builder.js.map +1 -1
- package/dist/orpc-client/docs-ledger/contract.d.ts +9 -0
- package/dist/orpc-client/docs-ledger/contract.d.ts.map +1 -1
- package/dist/orpc-client/docs-ledger/contract.js +1 -0
- package/dist/orpc-client/docs-ledger/contract.js.map +1 -1
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/dist/types/navigation/__test__/ledger-root-builder.crossProductDedup.test.d.ts +2 -0
- package/dist/types/navigation/__test__/ledger-root-builder.crossProductDedup.test.d.ts.map +1 -0
- package/dist/types/navigation/__test__/ledger-root-builder.ledgerParity.test.d.ts +2 -0
- package/dist/types/navigation/__test__/ledger-root-builder.ledgerParity.test.d.ts.map +1 -0
- package/dist/types/navigation/__test__/ledger-root-builder.sidebarDepth.test.d.ts +2 -0
- package/dist/types/navigation/__test__/ledger-root-builder.sidebarDepth.test.d.ts.map +1 -0
- package/dist/types/navigation/__test__/ledger-root-builder.skipSlugGroupOrdering.test.d.ts +2 -0
- package/dist/types/navigation/__test__/ledger-root-builder.skipSlugGroupOrdering.test.d.ts.map +1 -0
- package/dist/types/navigation/__test__/ledger-root-builder.skipSlugSiblingSection.test.d.ts +2 -0
- package/dist/types/navigation/__test__/ledger-root-builder.skipSlugSiblingSection.test.d.ts.map +1 -0
- package/dist/types/navigation/ledger-root-builder.d.ts.map +1 -1
- package/dist/types/orpc-client/docs-ledger/contract.d.ts +9 -0
- package/dist/types/orpc-client/docs-ledger/contract.d.ts.map +1 -1
- package/package.json +3 -3
|
@@ -569,6 +569,30 @@ function stripLeadingScopePrefix(section, scopePrefix) {
|
|
|
569
569
|
}
|
|
570
570
|
return section;
|
|
571
571
|
}
|
|
572
|
+
function computeDuplicateSiblingSortBoundaries(siblings) {
|
|
573
|
+
const result = new Map();
|
|
574
|
+
const byPath = new Map();
|
|
575
|
+
for (const sibling of siblings) {
|
|
576
|
+
if (metaStr(sibling.seg.metadata, "type") !== "section") {
|
|
577
|
+
continue;
|
|
578
|
+
}
|
|
579
|
+
const group = byPath.get(sibling.seg.section) ?? [];
|
|
580
|
+
group.push(sibling);
|
|
581
|
+
byPath.set(sibling.seg.section, group);
|
|
582
|
+
}
|
|
583
|
+
for (const [, group] of byPath) {
|
|
584
|
+
if (group.length <= 1) {
|
|
585
|
+
continue;
|
|
586
|
+
}
|
|
587
|
+
for (let i = 0; i < group.length; i++) {
|
|
588
|
+
result.set(group[i], {
|
|
589
|
+
lo: group[i].seg.sortOrder,
|
|
590
|
+
hi: i + 1 < group.length ? group[i + 1].seg.sortOrder : Number.MAX_SAFE_INTEGER
|
|
591
|
+
});
|
|
592
|
+
}
|
|
593
|
+
}
|
|
594
|
+
return result;
|
|
595
|
+
}
|
|
572
596
|
/**
|
|
573
597
|
* Merge leaf children and section children by their original V1 tree position.
|
|
574
598
|
* Leaves carry position via `displaySortOrder` (stored in nav routes), sections
|
|
@@ -642,13 +666,16 @@ function sectionNode(ctx, scoped, sectionChildren, scopePrefix) {
|
|
|
642
666
|
* `parentSectionPath(seg.section) === parentSection`, or is an orphaned segment
|
|
643
667
|
* whose nearest existing ancestor is `parentSection`.
|
|
644
668
|
*/
|
|
645
|
-
function directChildApiPackages(parentSection, scopeSegs) {
|
|
669
|
+
function directChildApiPackages(parentSection, scopeSegs, apiDefinitionId) {
|
|
646
670
|
const sectionPaths = new Set(scopeSegs.map((s) => s.seg.section));
|
|
647
671
|
return scopeSegs
|
|
648
672
|
.filter((s) => {
|
|
649
673
|
if (metaStr(s.seg.metadata, "type") !== "apiPackage") {
|
|
650
674
|
return false;
|
|
651
675
|
}
|
|
676
|
+
if (metaStr(s.seg.metadata, "apiDefinitionId") !== apiDefinitionId) {
|
|
677
|
+
return false;
|
|
678
|
+
}
|
|
652
679
|
const parent = parentSectionPath(s.seg.section);
|
|
653
680
|
if (parent === parentSection) {
|
|
654
681
|
return true;
|
|
@@ -673,11 +700,37 @@ function buildApiChildren(ctx, scoped, scopeSegs, apiDefinitionId, scopePrefix)
|
|
|
673
700
|
// API leaf types.
|
|
674
701
|
const leafNodes = positionedApiLeafChildren(ctx, nav, apiDefinitionId);
|
|
675
702
|
// Build nested ApiPackageNodes from direct child apiPackage segments.
|
|
676
|
-
|
|
703
|
+
let childPackages = directChildApiPackages(seg.section, scopeSegs, apiDefinitionId);
|
|
704
|
+
// When multiple API-type segments share the same section path (skipUrlSlug
|
|
705
|
+
// group packages that collapse to their parent's section), children from
|
|
706
|
+
// different groups get mixed together. Scope children to this segment's DFS
|
|
707
|
+
// subtree using sortOrder boundaries to restore correct ownership.
|
|
708
|
+
const samePathApiSiblings = scopeSegs.filter((s) => s.seg.section === seg.section &&
|
|
709
|
+
s.seg.sortOrder !== seg.sortOrder &&
|
|
710
|
+
API_NODE_SEGMENT_TYPES.has(metaStr(s.seg.metadata, "type") ?? ""));
|
|
711
|
+
const hasCollapsedGroups = samePathApiSiblings.length > 0;
|
|
712
|
+
if (hasCollapsedGroups && metaStr(seg.metadata, "type") !== "apiReference") {
|
|
713
|
+
// This is a group package sharing its section with siblings.
|
|
714
|
+
// Only claim children whose sortOrder falls within our DFS subtree.
|
|
715
|
+
const nextSibling = samePathApiSiblings
|
|
716
|
+
.filter((s) => s.seg.sortOrder > seg.sortOrder)
|
|
717
|
+
.sort((a, b) => a.seg.sortOrder - b.seg.sortOrder)[0];
|
|
718
|
+
const lo = seg.sortOrder;
|
|
719
|
+
const hi = nextSibling?.seg.sortOrder ?? Number.MAX_SAFE_INTEGER;
|
|
720
|
+
childPackages = childPackages.filter((c) => c.seg.sortOrder > lo && c.seg.sortOrder < hi);
|
|
721
|
+
}
|
|
677
722
|
const packageNodes = childPackages.map((child) => ({
|
|
678
723
|
node: apiPackageChildNode(ctx, child, scopeSegs, apiDefinitionId, scopePrefix),
|
|
679
724
|
childIndex: metaNum(child.seg.metadata, "childIndex")
|
|
680
725
|
}));
|
|
726
|
+
// When collapsed groups are detected at apiReference level, children's
|
|
727
|
+
// childIndex values are relative to different parent groups and cannot be
|
|
728
|
+
// used for interleaving. Fall back to sortOrder (DFS creation order).
|
|
729
|
+
if (hasCollapsedGroups && metaStr(seg.metadata, "type") === "apiReference") {
|
|
730
|
+
return [...leafNodes, ...packageNodes.map((p, i) => ({ node: p.node, pos: leafNodes.length + i }))]
|
|
731
|
+
.sort((a, b) => a.pos - b.pos)
|
|
732
|
+
.map((p) => p.node);
|
|
733
|
+
}
|
|
681
734
|
// Interleave by position when childIndex metadata is available
|
|
682
735
|
const hasChildIndex = packageNodes.some((p) => p.childIndex != null);
|
|
683
736
|
if (!hasChildIndex) {
|
|
@@ -834,7 +887,7 @@ function apiReferenceNode(ctx, scoped, scopeSegs, scopePrefix) {
|
|
|
834
887
|
availability: metaAvailability(seg.metadata),
|
|
835
888
|
overviewPageId: overviewPageId != null ? FernNavigation.PageId(overviewPageId) : undefined,
|
|
836
889
|
noindex: metaBool(overview?.metadata, "noindex") ?? metaBool(seg.metadata, "noindex"),
|
|
837
|
-
paginated:
|
|
890
|
+
paginated: metaBool(seg.metadata, "paginated"),
|
|
838
891
|
showErrors: metaBool(seg.metadata, "showErrors"),
|
|
839
892
|
hideTitle: metaBool(seg.metadata, "hideTitle"),
|
|
840
893
|
pointsTo: metaPointsTo != null ? pointsToSlug(metaPointsTo) : undefined,
|
|
@@ -852,7 +905,12 @@ function attachedApiChangelogNode(ctx, scoped, scopeSegs, apiDefinitionId, scope
|
|
|
852
905
|
}
|
|
853
906
|
return metaStr(candidate.seg.metadata, "apiDefinitionId") === apiDefinitionId;
|
|
854
907
|
});
|
|
855
|
-
const
|
|
908
|
+
const apiReferenceCount = scopeSegs.filter((candidate) => {
|
|
909
|
+
return (metaStr(candidate.seg.metadata, "type") === "apiReference" &&
|
|
910
|
+
metaStr(candidate.seg.metadata, "apiDefinitionId") === apiDefinitionId);
|
|
911
|
+
}).length;
|
|
912
|
+
const changelogSeg = candidates.find((candidate) => parentSectionPath(candidate.seg.section) === seg.section) ??
|
|
913
|
+
(apiReferenceCount === 1 ? candidates[0] : undefined);
|
|
856
914
|
return changelogSeg != null ? changelogNode(ctx, changelogSeg, scopePrefix) : undefined;
|
|
857
915
|
}
|
|
858
916
|
function hasOwningApiReference(scoped, scopeSegs) {
|
|
@@ -860,12 +918,14 @@ function hasOwningApiReference(scoped, scopeSegs) {
|
|
|
860
918
|
if (apiDefinitionId == null || metaStr(scoped.seg.metadata, "type") !== "changelog") {
|
|
861
919
|
return false;
|
|
862
920
|
}
|
|
863
|
-
|
|
921
|
+
const candidates = scopeSegs.filter((candidate) => {
|
|
864
922
|
if (metaStr(candidate.seg.metadata, "type") !== "apiReference") {
|
|
865
923
|
return false;
|
|
866
924
|
}
|
|
867
925
|
return metaStr(candidate.seg.metadata, "apiDefinitionId") === apiDefinitionId;
|
|
868
926
|
});
|
|
927
|
+
return (candidates.some((candidate) => parentSectionPath(scoped.seg.section) === candidate.seg.section) ||
|
|
928
|
+
candidates.length === 1);
|
|
869
929
|
}
|
|
870
930
|
/**
|
|
871
931
|
* True when this segment is an `apiPackage` that will be nested under an in-scope
|
|
@@ -1046,6 +1106,12 @@ function buildSectionTree(ctx, scopeSegs, parentPath, scopePrefix, changelogScop
|
|
|
1046
1106
|
if (hasOwningApiReference(s, scopeSegs) || isNestedApiPackage(s, scopeSegs)) {
|
|
1047
1107
|
return false;
|
|
1048
1108
|
}
|
|
1109
|
+
// Segments marked as sidebar depth 0 are V1 root-level peers and
|
|
1110
|
+
// must never be adopted as children of another section.
|
|
1111
|
+
const depth = metaNum(s.seg.metadata, "sidebarDepth");
|
|
1112
|
+
if (depth === 0) {
|
|
1113
|
+
return false;
|
|
1114
|
+
}
|
|
1049
1115
|
const parent = parentSectionPath(sec);
|
|
1050
1116
|
if (parent === parentPath) {
|
|
1051
1117
|
return true;
|
|
@@ -1058,6 +1124,7 @@ function buildSectionTree(ctx, scopeSegs, parentPath, scopePrefix, changelogScop
|
|
|
1058
1124
|
return false;
|
|
1059
1125
|
})
|
|
1060
1126
|
.sort((a, b) => a.seg.sortOrder - b.seg.sortOrder);
|
|
1127
|
+
const siblingBoundaries = computeDuplicateSiblingSortBoundaries(direct);
|
|
1061
1128
|
return direct.map((s) => {
|
|
1062
1129
|
const type = metaStr(s.seg.metadata, "type");
|
|
1063
1130
|
const childIndex = metaNum(s.seg.metadata, "childIndex");
|
|
@@ -1070,7 +1137,11 @@ function buildSectionTree(ctx, scopeSegs, parentPath, scopePrefix, changelogScop
|
|
|
1070
1137
|
}
|
|
1071
1138
|
else {
|
|
1072
1139
|
// section (or unrecognised) → recurse for nested sections.
|
|
1073
|
-
|
|
1140
|
+
const boundary = siblingBoundaries.get(s);
|
|
1141
|
+
const childScopeSegs = boundary != null
|
|
1142
|
+
? scopeSegs.filter((candidate) => candidate.seg.sortOrder >= boundary.lo && candidate.seg.sortOrder < boundary.hi)
|
|
1143
|
+
: scopeSegs;
|
|
1144
|
+
node = sectionNode(ctx, s, buildSectionTree(ctx, childScopeSegs, s.seg.section, scopePrefix, changelogScopePrefix), scopePrefix);
|
|
1074
1145
|
}
|
|
1075
1146
|
return { node, childIndex };
|
|
1076
1147
|
});
|
|
@@ -1105,8 +1176,11 @@ function computeSiblingSortBoundaries(ordered) {
|
|
|
1105
1176
|
rootSections.push(s);
|
|
1106
1177
|
continue;
|
|
1107
1178
|
}
|
|
1179
|
+
const depth = metaNum(s.seg.metadata, "sidebarDepth");
|
|
1108
1180
|
const parent = parentSectionPath(sec);
|
|
1109
|
-
const isRootLevel =
|
|
1181
|
+
const isRootLevel = depth === 0 ||
|
|
1182
|
+
parent === "" ||
|
|
1183
|
+
(!sectionPaths.has(parent) && nearestExistingAncestor(sec, sectionPaths) === "");
|
|
1110
1184
|
if (isRootLevel) {
|
|
1111
1185
|
rootSections.push(s);
|
|
1112
1186
|
}
|
|
@@ -1151,6 +1225,17 @@ function buildSidebarRootChildren(ctx, scopeSegs, scopePrefix, changelogScopePre
|
|
|
1151
1225
|
// segments. Both index into the SAME child list at write time, so they
|
|
1152
1226
|
// interleave directly (e.g. [page 0, page 1, section 2…15, page 16, 17]).
|
|
1153
1227
|
const annotated = [];
|
|
1228
|
+
// ── Sibling section ownership ────────────────────────────────────────
|
|
1229
|
+
//
|
|
1230
|
+
// Multiple root-level sections can share the same section path (e.g.
|
|
1231
|
+
// three sections all with path equal to the version slug). When this
|
|
1232
|
+
// happens, buildSectionTree would incorrectly give ALL child subsections
|
|
1233
|
+
// to each sibling. We resolve ownership via sortOrder: a section owns
|
|
1234
|
+
// segments whose sortOrder falls between its own and the next sibling's.
|
|
1235
|
+
//
|
|
1236
|
+
// Computed first because hasRootSection (below) uses it to decide
|
|
1237
|
+
// whether bounded skip-slug siblings should activate adoption.
|
|
1238
|
+
const siblingBoundaries = computeSiblingSortBoundaries(ordered);
|
|
1154
1239
|
// ── Root-section adoption flow ──────────────────────────────────────
|
|
1155
1240
|
//
|
|
1156
1241
|
// A scope may contain a "root section" — a segment with type "section"
|
|
@@ -1162,28 +1247,42 @@ function buildSidebarRootChildren(ctx, scopeSegs, scopePrefix, changelogScopePre
|
|
|
1162
1247
|
// │ → emit pages/links inline at their displaySortOrder
|
|
1163
1248
|
// │
|
|
1164
1249
|
// ├─ (B) the root section itself (sec=="", type=="section")
|
|
1165
|
-
// │ → buildSectionTree adopts
|
|
1166
|
-
// │ → emit one sectionNode wrapping the
|
|
1250
|
+
// │ → buildSectionTree adopts nested children (depth > 0)
|
|
1251
|
+
// │ → emit one sectionNode wrapping the subtree
|
|
1167
1252
|
// │
|
|
1168
|
-
// └─ (C) depth-0 sections (parentSectionPath=="")
|
|
1169
|
-
// →
|
|
1253
|
+
// └─ (C) depth-0 sections (sidebarDepth==0 or parentSectionPath=="")
|
|
1254
|
+
// → emit directly as root children (not adopted by B)
|
|
1170
1255
|
//
|
|
1171
1256
|
// Without a root section, depth-0 sections are emitted directly.
|
|
1172
|
-
const hasRootSection = ordered.some((s) => metaStr(s.seg.metadata, "type") === "section" && s.seg.section === "");
|
|
1173
|
-
// ── Sibling section ownership ────────────────────────────────────────
|
|
1174
1257
|
//
|
|
1175
|
-
//
|
|
1176
|
-
//
|
|
1177
|
-
//
|
|
1178
|
-
//
|
|
1179
|
-
//
|
|
1180
|
-
|
|
1258
|
+
// A section with sec=="" AND `childIndex` in its metadata is a sibling
|
|
1259
|
+
// inside a V1 sidebarGroup that has skipUrlSlug — not a true structural
|
|
1260
|
+
// wrapper. `walkNavSubTrees` always passes `childIndex`; direct
|
|
1261
|
+
// sidebarRoot children (true wrappers) never receive one.
|
|
1262
|
+
//
|
|
1263
|
+
// Such siblings only activate adoption when they have sortOrder
|
|
1264
|
+
// boundaries (multiple sec="" sections sharing the same path), which
|
|
1265
|
+
// correctly scope each section's children. A *single* unbounded
|
|
1266
|
+
// skip-slug sibling (e.g. mangopay's "Introduction") must NOT adopt
|
|
1267
|
+
// its depth-0 peers — doing so produces spurious breadcrumb entries.
|
|
1268
|
+
const hasRootSection = ordered.some((s) => metaStr(s.seg.metadata, "type") === "section" &&
|
|
1269
|
+
s.seg.section === "" &&
|
|
1270
|
+
(metaNum(s.seg.metadata, "childIndex") == null || siblingBoundaries.has(s)));
|
|
1181
1271
|
for (const s of ordered) {
|
|
1182
1272
|
const type = metaStr(s.seg.metadata, "type");
|
|
1183
1273
|
const sec = s.seg.section;
|
|
1184
1274
|
if (hasOwningApiReference(s, scopeSegs) || isNestedApiPackage(s, scopeSegs)) {
|
|
1185
1275
|
continue;
|
|
1186
1276
|
}
|
|
1277
|
+
// Skip apiPackage segments that share their section path with an
|
|
1278
|
+
// apiReference sibling (skipUrlSlug structural wrappers). Their
|
|
1279
|
+
// children are already claimed by the apiReference via DFS ordering.
|
|
1280
|
+
if (type === "apiPackage") {
|
|
1281
|
+
const hasApiRefSibling = ordered.some((sib) => sib.seg.section === sec && metaStr(sib.seg.metadata, "type") === "apiReference");
|
|
1282
|
+
if (hasApiRefSibling) {
|
|
1283
|
+
continue;
|
|
1284
|
+
}
|
|
1285
|
+
}
|
|
1187
1286
|
if (type === "tabRoot" && metaBool(s.seg.metadata, "landingPage") === true) {
|
|
1188
1287
|
continue;
|
|
1189
1288
|
}
|
|
@@ -1200,9 +1299,11 @@ function buildSidebarRootChildren(ctx, scopeSegs, scopePrefix, changelogScopePre
|
|
|
1200
1299
|
continue;
|
|
1201
1300
|
}
|
|
1202
1301
|
// (B) Root section wrapper — adopts depth-0 children via buildSectionTree.
|
|
1302
|
+
// Only applies to true root wrappers (no childIndex); sibling
|
|
1303
|
+
// sections with skipUrlSlug (have childIndex) fall through to (C).
|
|
1203
1304
|
// When multiple sections share sec==="" (skip-slug), scope children
|
|
1204
1305
|
// to each section's sortOrder range so subsections aren't duplicated.
|
|
1205
|
-
if (type === "section" && sec === "") {
|
|
1306
|
+
if (type === "section" && sec === "" && hasRootSection) {
|
|
1206
1307
|
const bounds = siblingBoundaries.get(s);
|
|
1207
1308
|
const ownedSegs = bounds != null
|
|
1208
1309
|
? scopeSegs.filter((seg) => seg.seg.sortOrder >= bounds.lo && seg.seg.sortOrder < bounds.hi)
|
|
@@ -1214,13 +1315,17 @@ function buildSidebarRootChildren(ctx, scopeSegs, scopePrefix, changelogScopePre
|
|
|
1214
1315
|
}
|
|
1215
1316
|
// (C) Depth-0 or orphaned sections — emit at root unless already
|
|
1216
1317
|
// adopted by a root section (B).
|
|
1318
|
+
const depth = metaNum(s.seg.metadata, "sidebarDepth");
|
|
1217
1319
|
const parent = parentSectionPath(sec);
|
|
1218
|
-
const isRootLevel =
|
|
1219
|
-
|
|
1220
|
-
|
|
1221
|
-
//
|
|
1222
|
-
//
|
|
1223
|
-
|
|
1320
|
+
const isRootLevel = depth === 0 ||
|
|
1321
|
+
parent === "" ||
|
|
1322
|
+
(!sectionPaths.has(parent) && nearestExistingAncestor(sec, sectionPaths) === "");
|
|
1323
|
+
// Skip depth-0 sections already adopted by the root section wrapper (B).
|
|
1324
|
+
// When sidebarDepth is available, depth-0 sections are V1 root peers
|
|
1325
|
+
// that buildSectionTree now excludes, so they must NOT be skipped.
|
|
1326
|
+
// Only skip sections without sidebarDepth (old data) that would still
|
|
1327
|
+
// be adopted by path-based hierarchy in buildSectionTree.
|
|
1328
|
+
if (isRootLevel && hasRootSection && sec !== "" && depth == null) {
|
|
1224
1329
|
continue;
|
|
1225
1330
|
}
|
|
1226
1331
|
if (isRootLevel) {
|
|
@@ -1238,7 +1343,20 @@ function buildSidebarRootChildren(ctx, scopeSegs, scopePrefix, changelogScopePre
|
|
|
1238
1343
|
const ownedSegs = bounds != null
|
|
1239
1344
|
? scopeSegs.filter((seg) => seg.seg.sortOrder >= bounds.lo && seg.seg.sortOrder < bounds.hi)
|
|
1240
1345
|
: scopeSegs;
|
|
1241
|
-
|
|
1346
|
+
// A section with sec="" that reaches here (not adopted as a
|
|
1347
|
+
// root wrapper in case B) is a skipUrlSlug sibling.
|
|
1348
|
+
// When sortOrder boundaries exist (multiple sec="" sections),
|
|
1349
|
+
// the scoped ownedSegs correctly limits which subsections
|
|
1350
|
+
// each section can adopt. When there are NO boundaries
|
|
1351
|
+
// (single sec="" section, e.g. mangopay's "Introduction"),
|
|
1352
|
+
// buildSectionTree("") would re-adopt all depth-0 peers;
|
|
1353
|
+
// pass an empty child list instead — its leaf artifacts are
|
|
1354
|
+
// already in its own segment nav.
|
|
1355
|
+
const isUnboundedSkipSlugSibling = sec === "" && metaNum(s.seg.metadata, "childIndex") != null && bounds == null;
|
|
1356
|
+
const sectionChildren = isUnboundedSkipSlugSibling
|
|
1357
|
+
? []
|
|
1358
|
+
: buildSectionTree(ctx, ownedSegs, sec, scopePrefix, changelogScopePrefix);
|
|
1359
|
+
node = sectionNode(ctx, s, sectionChildren, scopePrefix);
|
|
1242
1360
|
}
|
|
1243
1361
|
// Shared/merged sections (paths not rooted in scopePrefix) must not
|
|
1244
1362
|
// carry childIndex — their source-version positions would sort them
|
|
@@ -1308,11 +1426,6 @@ function groupSidebarRootChildren(ctx, children) {
|
|
|
1308
1426
|
flushRun();
|
|
1309
1427
|
out.push(child);
|
|
1310
1428
|
}
|
|
1311
|
-
else if (child.type === "changelog") {
|
|
1312
|
-
flushRun();
|
|
1313
|
-
run.push(child);
|
|
1314
|
-
flushRun();
|
|
1315
|
-
}
|
|
1316
1429
|
else {
|
|
1317
1430
|
run.push(child);
|
|
1318
1431
|
}
|
|
@@ -1479,7 +1592,7 @@ function buildVariantedNode(ctx, variants, scopeSegs, scopePrefix) {
|
|
|
1479
1592
|
? asSlug(FernNavigation.slugjoin(scopePrefix, variantIndividualSlug))
|
|
1480
1593
|
: scopePrefix;
|
|
1481
1594
|
const metaPointsTo = metaStr(variant.metadata, "pointsTo");
|
|
1482
|
-
const children = buildSidebarRootChildren(ctx, variantSegs,
|
|
1595
|
+
const children = buildSidebarRootChildren(ctx, variantSegs, variantSlug);
|
|
1483
1596
|
return {
|
|
1484
1597
|
type: "variant",
|
|
1485
1598
|
...withMetadataDefaults({
|
|
@@ -2015,6 +2128,14 @@ function buildProductGroup(ctx, products, scoped, rootSlug, skipSharedSectionMer
|
|
|
2015
2128
|
};
|
|
2016
2129
|
return node;
|
|
2017
2130
|
});
|
|
2131
|
+
// Cross-product canonical slug deduplication (V2 parity).
|
|
2132
|
+
// V2's migrator deduplicates pages across products via a global pageId map:
|
|
2133
|
+
// the first product to register a pageId wins, and subsequent products'
|
|
2134
|
+
// pages with the same pageId receive a canonicalSlug pointing to the
|
|
2135
|
+
// canonical (default-product) page. Without this, shared content that is
|
|
2136
|
+
// materialized under every product scope (e.g. merge-api-basics pages)
|
|
2137
|
+
// appears as separate sitemap entries per product.
|
|
2138
|
+
markCrossProductCanonicalSlugs(children);
|
|
2018
2139
|
return {
|
|
2019
2140
|
type: "productgroup",
|
|
2020
2141
|
id: ctx.nodeId("productgroup"),
|
|
@@ -2023,5 +2144,43 @@ function buildProductGroup(ctx, products, scoped, rootSlug, skipSharedSectionMer
|
|
|
2023
2144
|
children
|
|
2024
2145
|
};
|
|
2025
2146
|
}
|
|
2147
|
+
/**
|
|
2148
|
+
* Deduplicate pages across products using pageId as a content-identity key.
|
|
2149
|
+
* Pages with the same pageId in different products are the same content
|
|
2150
|
+
* materialized per-scope; the default product's slug becomes canonical and
|
|
2151
|
+
* non-default copies receive `canonicalSlug` so the sitemap collapses them.
|
|
2152
|
+
*
|
|
2153
|
+
* API leaf nodes (endpoints, websockets, etc.) are unaffected — `getPageId`
|
|
2154
|
+
* returns undefined for those types, so product-specific API surface is
|
|
2155
|
+
* never suppressed.
|
|
2156
|
+
*/
|
|
2157
|
+
function markCrossProductCanonicalSlugs(productNodes) {
|
|
2158
|
+
const pageIdToCanonical = new Map();
|
|
2159
|
+
// Process default product first so its slugs become the canonical targets.
|
|
2160
|
+
const sorted = [...productNodes].sort((a, b) => {
|
|
2161
|
+
if (a.default !== b.default) {
|
|
2162
|
+
return a.default ? -1 : 1;
|
|
2163
|
+
}
|
|
2164
|
+
return 0;
|
|
2165
|
+
});
|
|
2166
|
+
for (const product of sorted) {
|
|
2167
|
+
FernNavigation.traverseDF(product, (child) => {
|
|
2168
|
+
if (FernNavigation.hasMetadata(child) && FernNavigation.isPage(child)) {
|
|
2169
|
+
const pid = FernNavigation.getPageId(child);
|
|
2170
|
+
if (pid == null) {
|
|
2171
|
+
return;
|
|
2172
|
+
}
|
|
2173
|
+
const key = `pid:${pid}`;
|
|
2174
|
+
const canonical = pageIdToCanonical.get(key);
|
|
2175
|
+
if (canonical != null && !product.default) {
|
|
2176
|
+
child.canonicalSlug = canonical;
|
|
2177
|
+
}
|
|
2178
|
+
else if (canonical == null) {
|
|
2179
|
+
pageIdToCanonical.set(key, child.canonicalSlug ?? child.slug);
|
|
2180
|
+
}
|
|
2181
|
+
}
|
|
2182
|
+
});
|
|
2183
|
+
}
|
|
2184
|
+
}
|
|
2026
2185
|
// (root keeps the bare "root" id; it is the single tree root.)
|
|
2027
2186
|
//# sourceMappingURL=ledger-root-builder.js.map
|