@fern-api/fdr-sdk 1.2.42-b174eece86 → 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 +115 -24
- package/dist/js/navigation/ledger-root-builder.js.map +1 -1
- package/dist/js/navigation/ledger-root-builder.mjs +115 -24
- 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.rootChildOrder.test.js +102 -0
- package/dist/navigation/__test__/ledger-root-builder.rootChildOrder.test.js.map +1 -1
- 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 +207 -35
- 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
|
});
|
|
@@ -1090,16 +1161,26 @@ function buildSectionTree(ctx, scopeSegs, parentPath, scopePrefix, changelogScop
|
|
|
1090
1161
|
function computeSiblingSortBoundaries(ordered) {
|
|
1091
1162
|
const result = new Map();
|
|
1092
1163
|
// Collect root-level sections grouped by their section path.
|
|
1164
|
+
// Sections with sec==="" (skip-slug sections) are included: when multiple
|
|
1165
|
+
// skip-slug sections exist at the root, they share path "" and need
|
|
1166
|
+
// boundaries so each only adopts its own children.
|
|
1093
1167
|
const sectionPaths = new Set(ordered.map((s) => s.seg.section));
|
|
1094
1168
|
const rootSections = [];
|
|
1095
1169
|
for (const s of ordered) {
|
|
1096
1170
|
const type = metaStr(s.seg.metadata, "type");
|
|
1097
1171
|
const sec = s.seg.section;
|
|
1098
|
-
if (type !== "section"
|
|
1172
|
+
if (type !== "section") {
|
|
1173
|
+
continue;
|
|
1174
|
+
}
|
|
1175
|
+
if (sec === "") {
|
|
1176
|
+
rootSections.push(s);
|
|
1099
1177
|
continue;
|
|
1100
1178
|
}
|
|
1179
|
+
const depth = metaNum(s.seg.metadata, "sidebarDepth");
|
|
1101
1180
|
const parent = parentSectionPath(sec);
|
|
1102
|
-
const isRootLevel =
|
|
1181
|
+
const isRootLevel = depth === 0 ||
|
|
1182
|
+
parent === "" ||
|
|
1183
|
+
(!sectionPaths.has(parent) && nearestExistingAncestor(sec, sectionPaths) === "");
|
|
1103
1184
|
if (isRootLevel) {
|
|
1104
1185
|
rootSections.push(s);
|
|
1105
1186
|
}
|
|
@@ -1144,6 +1225,17 @@ function buildSidebarRootChildren(ctx, scopeSegs, scopePrefix, changelogScopePre
|
|
|
1144
1225
|
// segments. Both index into the SAME child list at write time, so they
|
|
1145
1226
|
// interleave directly (e.g. [page 0, page 1, section 2…15, page 16, 17]).
|
|
1146
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);
|
|
1147
1239
|
// ── Root-section adoption flow ──────────────────────────────────────
|
|
1148
1240
|
//
|
|
1149
1241
|
// A scope may contain a "root section" — a segment with type "section"
|
|
@@ -1155,28 +1247,42 @@ function buildSidebarRootChildren(ctx, scopeSegs, scopePrefix, changelogScopePre
|
|
|
1155
1247
|
// │ → emit pages/links inline at their displaySortOrder
|
|
1156
1248
|
// │
|
|
1157
1249
|
// ├─ (B) the root section itself (sec=="", type=="section")
|
|
1158
|
-
// │ → buildSectionTree adopts
|
|
1159
|
-
// │ → emit one sectionNode wrapping the
|
|
1250
|
+
// │ → buildSectionTree adopts nested children (depth > 0)
|
|
1251
|
+
// │ → emit one sectionNode wrapping the subtree
|
|
1160
1252
|
// │
|
|
1161
|
-
// └─ (C) depth-0 sections (parentSectionPath=="")
|
|
1162
|
-
// →
|
|
1253
|
+
// └─ (C) depth-0 sections (sidebarDepth==0 or parentSectionPath=="")
|
|
1254
|
+
// → emit directly as root children (not adopted by B)
|
|
1163
1255
|
//
|
|
1164
1256
|
// Without a root section, depth-0 sections are emitted directly.
|
|
1165
|
-
const hasRootSection = ordered.some((s) => metaStr(s.seg.metadata, "type") === "section" && s.seg.section === "");
|
|
1166
|
-
// ── Sibling section ownership ────────────────────────────────────────
|
|
1167
1257
|
//
|
|
1168
|
-
//
|
|
1169
|
-
//
|
|
1170
|
-
//
|
|
1171
|
-
//
|
|
1172
|
-
//
|
|
1173
|
-
|
|
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)));
|
|
1174
1271
|
for (const s of ordered) {
|
|
1175
1272
|
const type = metaStr(s.seg.metadata, "type");
|
|
1176
1273
|
const sec = s.seg.section;
|
|
1177
1274
|
if (hasOwningApiReference(s, scopeSegs) || isNestedApiPackage(s, scopeSegs)) {
|
|
1178
1275
|
continue;
|
|
1179
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
|
+
}
|
|
1180
1286
|
if (type === "tabRoot" && metaBool(s.seg.metadata, "landingPage") === true) {
|
|
1181
1287
|
continue;
|
|
1182
1288
|
}
|
|
@@ -1193,21 +1299,33 @@ function buildSidebarRootChildren(ctx, scopeSegs, scopePrefix, changelogScopePre
|
|
|
1193
1299
|
continue;
|
|
1194
1300
|
}
|
|
1195
1301
|
// (B) Root section wrapper — adopts depth-0 children via buildSectionTree.
|
|
1196
|
-
|
|
1197
|
-
|
|
1302
|
+
// Only applies to true root wrappers (no childIndex); sibling
|
|
1303
|
+
// sections with skipUrlSlug (have childIndex) fall through to (C).
|
|
1304
|
+
// When multiple sections share sec==="" (skip-slug), scope children
|
|
1305
|
+
// to each section's sortOrder range so subsections aren't duplicated.
|
|
1306
|
+
if (type === "section" && sec === "" && hasRootSection) {
|
|
1307
|
+
const bounds = siblingBoundaries.get(s);
|
|
1308
|
+
const ownedSegs = bounds != null
|
|
1309
|
+
? scopeSegs.filter((seg) => seg.seg.sortOrder >= bounds.lo && seg.seg.sortOrder < bounds.hi)
|
|
1310
|
+
: scopeSegs;
|
|
1311
|
+
const sectionChildren = buildSectionTree(ctx, ownedSegs, "", scopePrefix, changelogScopePrefix);
|
|
1198
1312
|
const node = sectionNode(ctx, s, sectionChildren, scopePrefix);
|
|
1199
1313
|
annotated.push({ node, pos: metaNum(s.seg.metadata, "childIndex"), isLeaf: false });
|
|
1200
1314
|
continue;
|
|
1201
1315
|
}
|
|
1202
1316
|
// (C) Depth-0 or orphaned sections — emit at root unless already
|
|
1203
1317
|
// adopted by a root section (B).
|
|
1318
|
+
const depth = metaNum(s.seg.metadata, "sidebarDepth");
|
|
1204
1319
|
const parent = parentSectionPath(sec);
|
|
1205
|
-
const isRootLevel =
|
|
1206
|
-
|
|
1207
|
-
|
|
1208
|
-
//
|
|
1209
|
-
//
|
|
1210
|
-
|
|
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) {
|
|
1211
1329
|
continue;
|
|
1212
1330
|
}
|
|
1213
1331
|
if (isRootLevel) {
|
|
@@ -1225,7 +1343,20 @@ function buildSidebarRootChildren(ctx, scopeSegs, scopePrefix, changelogScopePre
|
|
|
1225
1343
|
const ownedSegs = bounds != null
|
|
1226
1344
|
? scopeSegs.filter((seg) => seg.seg.sortOrder >= bounds.lo && seg.seg.sortOrder < bounds.hi)
|
|
1227
1345
|
: scopeSegs;
|
|
1228
|
-
|
|
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);
|
|
1229
1360
|
}
|
|
1230
1361
|
// Shared/merged sections (paths not rooted in scopePrefix) must not
|
|
1231
1362
|
// carry childIndex — their source-version positions would sort them
|
|
@@ -1295,11 +1426,6 @@ function groupSidebarRootChildren(ctx, children) {
|
|
|
1295
1426
|
flushRun();
|
|
1296
1427
|
out.push(child);
|
|
1297
1428
|
}
|
|
1298
|
-
else if (child.type === "changelog") {
|
|
1299
|
-
flushRun();
|
|
1300
|
-
run.push(child);
|
|
1301
|
-
flushRun();
|
|
1302
|
-
}
|
|
1303
1429
|
else {
|
|
1304
1430
|
run.push(child);
|
|
1305
1431
|
}
|
|
@@ -1466,7 +1592,7 @@ function buildVariantedNode(ctx, variants, scopeSegs, scopePrefix) {
|
|
|
1466
1592
|
? asSlug(FernNavigation.slugjoin(scopePrefix, variantIndividualSlug))
|
|
1467
1593
|
: scopePrefix;
|
|
1468
1594
|
const metaPointsTo = metaStr(variant.metadata, "pointsTo");
|
|
1469
|
-
const children = buildSidebarRootChildren(ctx, variantSegs,
|
|
1595
|
+
const children = buildSidebarRootChildren(ctx, variantSegs, variantSlug);
|
|
1470
1596
|
return {
|
|
1471
1597
|
type: "variant",
|
|
1472
1598
|
...withMetadataDefaults({
|
|
@@ -2002,6 +2128,14 @@ function buildProductGroup(ctx, products, scoped, rootSlug, skipSharedSectionMer
|
|
|
2002
2128
|
};
|
|
2003
2129
|
return node;
|
|
2004
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);
|
|
2005
2139
|
return {
|
|
2006
2140
|
type: "productgroup",
|
|
2007
2141
|
id: ctx.nodeId("productgroup"),
|
|
@@ -2010,5 +2144,43 @@ function buildProductGroup(ctx, products, scoped, rootSlug, skipSharedSectionMer
|
|
|
2010
2144
|
children
|
|
2011
2145
|
};
|
|
2012
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
|
+
}
|
|
2013
2185
|
// (root keeps the bare "root" id; it is the single tree root.)
|
|
2014
2186
|
//# sourceMappingURL=ledger-root-builder.js.map
|