@fern-api/fdr-sdk 1.2.42-080b4f1d94 → 1.2.42-0c024f5b0d
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 +0 -1
- package/dist/js/client/FdrClient.js.map +1 -1
- package/dist/js/client/FdrClient.mjs +0 -1
- package/dist/js/client/FdrClient.mjs.map +1 -1
- package/dist/js/index.js +0 -1
- package/dist/js/index.js.map +1 -1
- package/dist/js/index.mjs +0 -1
- package/dist/js/index.mjs.map +1 -1
- package/dist/js/navigation/ledger-root-builder.js +33 -164
- package/dist/js/navigation/ledger-root-builder.js.map +1 -1
- package/dist/js/navigation/ledger-root-builder.mjs +33 -164
- package/dist/js/navigation/ledger-root-builder.mjs.map +1 -1
- package/dist/js/orpc-client.js +0 -1
- package/dist/js/orpc-client.js.map +1 -1
- package/dist/js/orpc-client.mjs +0 -1
- package/dist/js/orpc-client.mjs.map +1 -1
- package/dist/navigation/__test__/ledger-root-builder.metadataPreservation.test.js +4 -109
- package/dist/navigation/__test__/ledger-root-builder.metadataPreservation.test.js.map +1 -1
- package/dist/navigation/__test__/ledger-root-builder.rootChildOrder.test.js +0 -102
- package/dist/navigation/__test__/ledger-root-builder.rootChildOrder.test.js.map +1 -1
- package/dist/navigation/ledger-root-builder.d.ts.map +1 -1
- package/dist/navigation/ledger-root-builder.js +44 -253
- package/dist/navigation/ledger-root-builder.js.map +1 -1
- package/dist/orpc-client/docs-ledger/contract.d.ts +0 -9
- package/dist/orpc-client/docs-ledger/contract.d.ts.map +1 -1
- package/dist/orpc-client/docs-ledger/contract.js +0 -1
- package/dist/orpc-client/docs-ledger/contract.js.map +1 -1
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/dist/types/navigation/ledger-root-builder.d.ts.map +1 -1
- package/dist/types/orpc-client/docs-ledger/contract.d.ts +0 -9
- package/dist/types/orpc-client/docs-ledger/contract.d.ts.map +1 -1
- package/package.json +1 -1
- package/dist/navigation/__test__/ledger-root-builder.crossProductDedup.test.d.ts +0 -2
- package/dist/navigation/__test__/ledger-root-builder.crossProductDedup.test.d.ts.map +0 -1
- package/dist/navigation/__test__/ledger-root-builder.crossProductDedup.test.js +0 -267
- package/dist/navigation/__test__/ledger-root-builder.crossProductDedup.test.js.map +0 -1
- package/dist/navigation/__test__/ledger-root-builder.ledgerParity.test.d.ts +0 -2
- package/dist/navigation/__test__/ledger-root-builder.ledgerParity.test.d.ts.map +0 -1
- package/dist/navigation/__test__/ledger-root-builder.ledgerParity.test.js +0 -306
- package/dist/navigation/__test__/ledger-root-builder.ledgerParity.test.js.map +0 -1
- package/dist/navigation/__test__/ledger-root-builder.sidebarDepth.test.d.ts +0 -2
- package/dist/navigation/__test__/ledger-root-builder.sidebarDepth.test.d.ts.map +0 -1
- package/dist/navigation/__test__/ledger-root-builder.sidebarDepth.test.js +0 -273
- package/dist/navigation/__test__/ledger-root-builder.sidebarDepth.test.js.map +0 -1
- package/dist/navigation/__test__/ledger-root-builder.skipSlugGroupOrdering.test.d.ts +0 -2
- package/dist/navigation/__test__/ledger-root-builder.skipSlugGroupOrdering.test.d.ts.map +0 -1
- package/dist/navigation/__test__/ledger-root-builder.skipSlugGroupOrdering.test.js +0 -234
- package/dist/navigation/__test__/ledger-root-builder.skipSlugGroupOrdering.test.js.map +0 -1
- package/dist/navigation/__test__/ledger-root-builder.skipSlugSiblingSection.test.d.ts +0 -2
- package/dist/navigation/__test__/ledger-root-builder.skipSlugSiblingSection.test.d.ts.map +0 -1
- package/dist/navigation/__test__/ledger-root-builder.skipSlugSiblingSection.test.js +0 -360
- package/dist/navigation/__test__/ledger-root-builder.skipSlugSiblingSection.test.js.map +0 -1
- package/dist/types/navigation/__test__/ledger-root-builder.crossProductDedup.test.d.ts +0 -2
- package/dist/types/navigation/__test__/ledger-root-builder.crossProductDedup.test.d.ts.map +0 -1
- package/dist/types/navigation/__test__/ledger-root-builder.ledgerParity.test.d.ts +0 -2
- package/dist/types/navigation/__test__/ledger-root-builder.ledgerParity.test.d.ts.map +0 -1
- package/dist/types/navigation/__test__/ledger-root-builder.sidebarDepth.test.d.ts +0 -2
- package/dist/types/navigation/__test__/ledger-root-builder.sidebarDepth.test.d.ts.map +0 -1
- package/dist/types/navigation/__test__/ledger-root-builder.skipSlugGroupOrdering.test.d.ts +0 -2
- package/dist/types/navigation/__test__/ledger-root-builder.skipSlugGroupOrdering.test.d.ts.map +0 -1
- package/dist/types/navigation/__test__/ledger-root-builder.skipSlugSiblingSection.test.d.ts +0 -2
- package/dist/types/navigation/__test__/ledger-root-builder.skipSlugSiblingSection.test.d.ts.map +0 -1
|
@@ -60,41 +60,6 @@ function metaStr(metadata, key) {
|
|
|
60
60
|
}
|
|
61
61
|
return undefined;
|
|
62
62
|
}
|
|
63
|
-
/**
|
|
64
|
-
* V1 availability values stored in ledger metadata are lowercase (e.g. "deprecated"),
|
|
65
|
-
* but the latest Availability type uses PascalCase (e.g. "Deprecated"). This map
|
|
66
|
-
* normalises both formats so the badge component can look up display names correctly.
|
|
67
|
-
*/
|
|
68
|
-
const AVAILABILITY_LOOKUP = {
|
|
69
|
-
// V1 lowercase values (stored by docsPublishTransform from V1 navigation tree)
|
|
70
|
-
alpha: FernNavigation.Availability.Alpha,
|
|
71
|
-
stable: FernNavigation.Availability.Stable,
|
|
72
|
-
"generally-available": FernNavigation.Availability.GenerallyAvailable,
|
|
73
|
-
"in-development": FernNavigation.Availability.InDevelopment,
|
|
74
|
-
"pre-release": FernNavigation.Availability.PreRelease,
|
|
75
|
-
deprecated: FernNavigation.Availability.Deprecated,
|
|
76
|
-
beta: FernNavigation.Availability.Beta,
|
|
77
|
-
preview: FernNavigation.Availability.Preview,
|
|
78
|
-
legacy: FernNavigation.Availability.Legacy,
|
|
79
|
-
// Latest PascalCase values (pass-through)
|
|
80
|
-
Alpha: FernNavigation.Availability.Alpha,
|
|
81
|
-
Stable: FernNavigation.Availability.Stable,
|
|
82
|
-
GenerallyAvailable: FernNavigation.Availability.GenerallyAvailable,
|
|
83
|
-
InDevelopment: FernNavigation.Availability.InDevelopment,
|
|
84
|
-
PreRelease: FernNavigation.Availability.PreRelease,
|
|
85
|
-
Deprecated: FernNavigation.Availability.Deprecated,
|
|
86
|
-
Beta: FernNavigation.Availability.Beta,
|
|
87
|
-
Preview: FernNavigation.Availability.Preview,
|
|
88
|
-
Legacy: FernNavigation.Availability.Legacy
|
|
89
|
-
};
|
|
90
|
-
/** Read an availability field from metadata, converting V1 lowercase to latest PascalCase. */
|
|
91
|
-
function metaAvailability(metadata) {
|
|
92
|
-
const raw = metaStr(metadata, "availability");
|
|
93
|
-
if (raw == null) {
|
|
94
|
-
return undefined;
|
|
95
|
-
}
|
|
96
|
-
return AVAILABILITY_LOOKUP[raw];
|
|
97
|
-
}
|
|
98
63
|
function metaBool(metadata, key) {
|
|
99
64
|
if (metadata != null && typeof metadata === "object" && key in metadata) {
|
|
100
65
|
const v = metadata[key];
|
|
@@ -233,7 +198,7 @@ function artifactToNode(ctx, route) {
|
|
|
233
198
|
}),
|
|
234
199
|
pageId: FernNavigation.PageId(pageId),
|
|
235
200
|
noindex: metaBool(md, "noindex"),
|
|
236
|
-
availability:
|
|
201
|
+
availability: metaStr(md, "availability")
|
|
237
202
|
};
|
|
238
203
|
return node;
|
|
239
204
|
}
|
|
@@ -304,7 +269,7 @@ function apiLeafNodeFromNavRouteInternal(ctx, route, segmentApiDefinitionId) {
|
|
|
304
269
|
endpointId: FernNavigation.EndpointId(endpointId),
|
|
305
270
|
apiDefinitionId,
|
|
306
271
|
isResponseStream: metaBool(md, "isResponseStream"),
|
|
307
|
-
availability:
|
|
272
|
+
availability: metaStr(md, "availability"),
|
|
308
273
|
playground: undefined
|
|
309
274
|
};
|
|
310
275
|
}
|
|
@@ -320,7 +285,7 @@ function apiLeafNodeFromNavRouteInternal(ctx, route, segmentApiDefinitionId) {
|
|
|
320
285
|
method: method,
|
|
321
286
|
webhookId: FernNavigation.WebhookId(webhookId),
|
|
322
287
|
apiDefinitionId,
|
|
323
|
-
availability:
|
|
288
|
+
availability: metaStr(md, "availability")
|
|
324
289
|
};
|
|
325
290
|
}
|
|
326
291
|
if (route.type === "asyncapi") {
|
|
@@ -333,7 +298,7 @@ function apiLeafNodeFromNavRouteInternal(ctx, route, segmentApiDefinitionId) {
|
|
|
333
298
|
...meta,
|
|
334
299
|
webSocketId: FernNavigation.WebSocketId(webSocketId),
|
|
335
300
|
apiDefinitionId,
|
|
336
|
-
availability:
|
|
301
|
+
availability: metaStr(md, "availability"),
|
|
337
302
|
playground: undefined
|
|
338
303
|
};
|
|
339
304
|
}
|
|
@@ -349,7 +314,7 @@ function apiLeafNodeFromNavRouteInternal(ctx, route, segmentApiDefinitionId) {
|
|
|
349
314
|
method: method,
|
|
350
315
|
grpcId: FernNavigation.GrpcId(grpcId),
|
|
351
316
|
apiDefinitionId,
|
|
352
|
-
availability:
|
|
317
|
+
availability: metaStr(md, "availability")
|
|
353
318
|
};
|
|
354
319
|
}
|
|
355
320
|
if (route.type === "graphql") {
|
|
@@ -365,7 +330,7 @@ function apiLeafNodeFromNavRouteInternal(ctx, route, segmentApiDefinitionId) {
|
|
|
365
330
|
graphqlOperationId: FernNavigation.GraphQlOperationId(graphqlOperationId),
|
|
366
331
|
graphqlOperationIds: undefined,
|
|
367
332
|
apiDefinitionId,
|
|
368
|
-
availability:
|
|
333
|
+
availability: metaStr(md, "availability"),
|
|
369
334
|
playground: undefined
|
|
370
335
|
};
|
|
371
336
|
}
|
|
@@ -569,30 +534,6 @@ function stripLeadingScopePrefix(section, scopePrefix) {
|
|
|
569
534
|
}
|
|
570
535
|
return section;
|
|
571
536
|
}
|
|
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
|
-
}
|
|
596
537
|
/**
|
|
597
538
|
* Merge leaf children and section children by their original V1 tree position.
|
|
598
539
|
* Leaves carry position via `displaySortOrder` (stored in nav routes), sections
|
|
@@ -656,7 +597,7 @@ function sectionNode(ctx, scoped, sectionChildren, scopePrefix) {
|
|
|
656
597
|
noindex: metaBool(overview?.metadata, "noindex") ?? metaBool(seg.metadata, "noindex"),
|
|
657
598
|
collapsible: metaBool(seg.metadata, "collapsible") ?? (metaCollapsed(seg.metadata) === true ? true : undefined),
|
|
658
599
|
collapsedByDefault: metaBool(seg.metadata, "collapsedByDefault") ?? (metaCollapsed(seg.metadata) === true ? true : undefined),
|
|
659
|
-
availability:
|
|
600
|
+
availability: metaStr(seg.metadata, "availability"),
|
|
660
601
|
pointsTo: metaPointsTo != null ? pointsToSlug(metaPointsTo) : undefined,
|
|
661
602
|
children: mergeChildrenByPosition(ctx, nav, sectionChildren)
|
|
662
603
|
};
|
|
@@ -666,16 +607,13 @@ function sectionNode(ctx, scoped, sectionChildren, scopePrefix) {
|
|
|
666
607
|
* `parentSectionPath(seg.section) === parentSection`, or is an orphaned segment
|
|
667
608
|
* whose nearest existing ancestor is `parentSection`.
|
|
668
609
|
*/
|
|
669
|
-
function directChildApiPackages(parentSection, scopeSegs
|
|
610
|
+
function directChildApiPackages(parentSection, scopeSegs) {
|
|
670
611
|
const sectionPaths = new Set(scopeSegs.map((s) => s.seg.section));
|
|
671
612
|
return scopeSegs
|
|
672
613
|
.filter((s) => {
|
|
673
614
|
if (metaStr(s.seg.metadata, "type") !== "apiPackage") {
|
|
674
615
|
return false;
|
|
675
616
|
}
|
|
676
|
-
if (metaStr(s.seg.metadata, "apiDefinitionId") !== apiDefinitionId) {
|
|
677
|
-
return false;
|
|
678
|
-
}
|
|
679
617
|
const parent = parentSectionPath(s.seg.section);
|
|
680
618
|
if (parent === parentSection) {
|
|
681
619
|
return true;
|
|
@@ -700,37 +638,11 @@ function buildApiChildren(ctx, scoped, scopeSegs, apiDefinitionId, scopePrefix)
|
|
|
700
638
|
// API leaf types.
|
|
701
639
|
const leafNodes = positionedApiLeafChildren(ctx, nav, apiDefinitionId);
|
|
702
640
|
// Build nested ApiPackageNodes from direct child apiPackage segments.
|
|
703
|
-
|
|
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
|
-
}
|
|
641
|
+
const childPackages = directChildApiPackages(seg.section, scopeSegs);
|
|
722
642
|
const packageNodes = childPackages.map((child) => ({
|
|
723
643
|
node: apiPackageChildNode(ctx, child, scopeSegs, apiDefinitionId, scopePrefix),
|
|
724
644
|
childIndex: metaNum(child.seg.metadata, "childIndex")
|
|
725
645
|
}));
|
|
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
|
-
}
|
|
734
646
|
// Interleave by position when childIndex metadata is available
|
|
735
647
|
const hasChildIndex = packageNodes.some((p) => p.childIndex != null);
|
|
736
648
|
if (!hasChildIndex) {
|
|
@@ -774,7 +686,7 @@ function apiPackageChildNode(ctx, scoped, scopeSegs, parentApiDefinitionId, scop
|
|
|
774
686
|
featureFlags: metaFeatureFlags(seg.metadata)
|
|
775
687
|
}),
|
|
776
688
|
apiDefinitionId: FernNavigation.ApiDefinitionId(apiDefinitionId),
|
|
777
|
-
availability:
|
|
689
|
+
availability: metaStr(seg.metadata, "availability"),
|
|
778
690
|
overviewPageId: overviewPageId != null ? FernNavigation.PageId(overviewPageId) : undefined,
|
|
779
691
|
noindex: metaBool(overview?.metadata, "noindex") ?? metaBool(seg.metadata, "noindex"),
|
|
780
692
|
pointsTo: metaPointsTo != null ? pointsToSlug(metaPointsTo) : undefined,
|
|
@@ -884,10 +796,10 @@ function apiReferenceNode(ctx, scoped, scopeSegs, scopePrefix) {
|
|
|
884
796
|
featureFlags: metaFeatureFlags(seg.metadata)
|
|
885
797
|
}),
|
|
886
798
|
apiDefinitionId: FernNavigation.ApiDefinitionId(apiDefinitionId),
|
|
887
|
-
availability:
|
|
799
|
+
availability: metaStr(seg.metadata, "availability"),
|
|
888
800
|
overviewPageId: overviewPageId != null ? FernNavigation.PageId(overviewPageId) : undefined,
|
|
889
801
|
noindex: metaBool(overview?.metadata, "noindex") ?? metaBool(seg.metadata, "noindex"),
|
|
890
|
-
paginated:
|
|
802
|
+
paginated: true,
|
|
891
803
|
showErrors: metaBool(seg.metadata, "showErrors"),
|
|
892
804
|
hideTitle: metaBool(seg.metadata, "hideTitle"),
|
|
893
805
|
pointsTo: metaPointsTo != null ? pointsToSlug(metaPointsTo) : undefined,
|
|
@@ -905,12 +817,7 @@ function attachedApiChangelogNode(ctx, scoped, scopeSegs, apiDefinitionId, scope
|
|
|
905
817
|
}
|
|
906
818
|
return metaStr(candidate.seg.metadata, "apiDefinitionId") === apiDefinitionId;
|
|
907
819
|
});
|
|
908
|
-
const
|
|
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);
|
|
820
|
+
const changelogSeg = candidates.find((candidate) => parentSectionPath(candidate.seg.section) === seg.section) ?? candidates[0];
|
|
914
821
|
return changelogSeg != null ? changelogNode(ctx, changelogSeg, scopePrefix) : undefined;
|
|
915
822
|
}
|
|
916
823
|
function hasOwningApiReference(scoped, scopeSegs) {
|
|
@@ -918,14 +825,12 @@ function hasOwningApiReference(scoped, scopeSegs) {
|
|
|
918
825
|
if (apiDefinitionId == null || metaStr(scoped.seg.metadata, "type") !== "changelog") {
|
|
919
826
|
return false;
|
|
920
827
|
}
|
|
921
|
-
|
|
828
|
+
return scopeSegs.some((candidate) => {
|
|
922
829
|
if (metaStr(candidate.seg.metadata, "type") !== "apiReference") {
|
|
923
830
|
return false;
|
|
924
831
|
}
|
|
925
832
|
return metaStr(candidate.seg.metadata, "apiDefinitionId") === apiDefinitionId;
|
|
926
833
|
});
|
|
927
|
-
return (candidates.some((candidate) => parentSectionPath(scoped.seg.section) === candidate.seg.section) ||
|
|
928
|
-
candidates.length === 1);
|
|
929
834
|
}
|
|
930
835
|
/**
|
|
931
836
|
* True when this segment is an `apiPackage` that will be nested under an in-scope
|
|
@@ -1106,12 +1011,6 @@ function buildSectionTree(ctx, scopeSegs, parentPath, scopePrefix, changelogScop
|
|
|
1106
1011
|
if (hasOwningApiReference(s, scopeSegs) || isNestedApiPackage(s, scopeSegs)) {
|
|
1107
1012
|
return false;
|
|
1108
1013
|
}
|
|
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
|
-
}
|
|
1115
1014
|
const parent = parentSectionPath(sec);
|
|
1116
1015
|
if (parent === parentPath) {
|
|
1117
1016
|
return true;
|
|
@@ -1124,7 +1023,6 @@ function buildSectionTree(ctx, scopeSegs, parentPath, scopePrefix, changelogScop
|
|
|
1124
1023
|
return false;
|
|
1125
1024
|
})
|
|
1126
1025
|
.sort((a, b) => a.seg.sortOrder - b.seg.sortOrder);
|
|
1127
|
-
const siblingBoundaries = computeDuplicateSiblingSortBoundaries(direct);
|
|
1128
1026
|
return direct.map((s) => {
|
|
1129
1027
|
const type = metaStr(s.seg.metadata, "type");
|
|
1130
1028
|
const childIndex = metaNum(s.seg.metadata, "childIndex");
|
|
@@ -1137,11 +1035,7 @@ function buildSectionTree(ctx, scopeSegs, parentPath, scopePrefix, changelogScop
|
|
|
1137
1035
|
}
|
|
1138
1036
|
else {
|
|
1139
1037
|
// section (or unrecognised) → recurse for nested sections.
|
|
1140
|
-
|
|
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);
|
|
1038
|
+
node = sectionNode(ctx, s, buildSectionTree(ctx, scopeSegs, s.seg.section, scopePrefix, changelogScopePrefix), scopePrefix);
|
|
1145
1039
|
}
|
|
1146
1040
|
return { node, childIndex };
|
|
1147
1041
|
});
|
|
@@ -1161,26 +1055,16 @@ function buildSectionTree(ctx, scopeSegs, parentPath, scopePrefix, changelogScop
|
|
|
1161
1055
|
function computeSiblingSortBoundaries(ordered) {
|
|
1162
1056
|
const result = new Map();
|
|
1163
1057
|
// 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.
|
|
1167
1058
|
const sectionPaths = new Set(ordered.map((s) => s.seg.section));
|
|
1168
1059
|
const rootSections = [];
|
|
1169
1060
|
for (const s of ordered) {
|
|
1170
1061
|
const type = metaStr(s.seg.metadata, "type");
|
|
1171
1062
|
const sec = s.seg.section;
|
|
1172
|
-
if (type !== "section") {
|
|
1063
|
+
if (type !== "section" || sec === "") {
|
|
1173
1064
|
continue;
|
|
1174
1065
|
}
|
|
1175
|
-
if (sec === "") {
|
|
1176
|
-
rootSections.push(s);
|
|
1177
|
-
continue;
|
|
1178
|
-
}
|
|
1179
|
-
const depth = metaNum(s.seg.metadata, "sidebarDepth");
|
|
1180
1066
|
const parent = parentSectionPath(sec);
|
|
1181
|
-
const isRootLevel =
|
|
1182
|
-
parent === "" ||
|
|
1183
|
-
(!sectionPaths.has(parent) && nearestExistingAncestor(sec, sectionPaths) === "");
|
|
1067
|
+
const isRootLevel = parent === "" || (!sectionPaths.has(parent) && nearestExistingAncestor(sec, sectionPaths) === "");
|
|
1184
1068
|
if (isRootLevel) {
|
|
1185
1069
|
rootSections.push(s);
|
|
1186
1070
|
}
|
|
@@ -1225,17 +1109,6 @@ function buildSidebarRootChildren(ctx, scopeSegs, scopePrefix, changelogScopePre
|
|
|
1225
1109
|
// segments. Both index into the SAME child list at write time, so they
|
|
1226
1110
|
// interleave directly (e.g. [page 0, page 1, section 2…15, page 16, 17]).
|
|
1227
1111
|
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);
|
|
1239
1112
|
// ── Root-section adoption flow ──────────────────────────────────────
|
|
1240
1113
|
//
|
|
1241
1114
|
// A scope may contain a "root section" — a segment with type "section"
|
|
@@ -1247,42 +1120,28 @@ function buildSidebarRootChildren(ctx, scopeSegs, scopePrefix, changelogScopePre
|
|
|
1247
1120
|
// │ → emit pages/links inline at their displaySortOrder
|
|
1248
1121
|
// │
|
|
1249
1122
|
// ├─ (B) the root section itself (sec=="", type=="section")
|
|
1250
|
-
// │ → buildSectionTree adopts
|
|
1251
|
-
// │ → emit one sectionNode wrapping the subtree
|
|
1123
|
+
// │ → buildSectionTree adopts ALL depth-0 children as its own
|
|
1124
|
+
// │ → emit one sectionNode wrapping the whole subtree
|
|
1252
1125
|
// │
|
|
1253
|
-
// └─ (C) depth-0 sections (
|
|
1254
|
-
// →
|
|
1126
|
+
// └─ (C) depth-0 sections (parentSectionPath=="")
|
|
1127
|
+
// → SKIP — already adopted by (B) via buildSectionTree
|
|
1255
1128
|
//
|
|
1256
1129
|
// Without a root section, depth-0 sections are emitted directly.
|
|
1130
|
+
const hasRootSection = ordered.some((s) => metaStr(s.seg.metadata, "type") === "section" && s.seg.section === "");
|
|
1131
|
+
// ── Sibling section ownership ────────────────────────────────────────
|
|
1257
1132
|
//
|
|
1258
|
-
//
|
|
1259
|
-
//
|
|
1260
|
-
//
|
|
1261
|
-
//
|
|
1262
|
-
//
|
|
1263
|
-
|
|
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)));
|
|
1133
|
+
// Multiple root-level sections can share the same section path (e.g.
|
|
1134
|
+
// three sections all with path equal to the version slug). When this
|
|
1135
|
+
// happens, buildSectionTree would incorrectly give ALL child subsections
|
|
1136
|
+
// to each sibling. We resolve ownership via sortOrder: a section owns
|
|
1137
|
+
// segments whose sortOrder falls between its own and the next sibling's.
|
|
1138
|
+
const siblingBoundaries = computeSiblingSortBoundaries(ordered);
|
|
1271
1139
|
for (const s of ordered) {
|
|
1272
1140
|
const type = metaStr(s.seg.metadata, "type");
|
|
1273
1141
|
const sec = s.seg.section;
|
|
1274
1142
|
if (hasOwningApiReference(s, scopeSegs) || isNestedApiPackage(s, scopeSegs)) {
|
|
1275
1143
|
continue;
|
|
1276
1144
|
}
|
|
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
|
-
}
|
|
1286
1145
|
if (type === "tabRoot" && metaBool(s.seg.metadata, "landingPage") === true) {
|
|
1287
1146
|
continue;
|
|
1288
1147
|
}
|
|
@@ -1299,35 +1158,21 @@ function buildSidebarRootChildren(ctx, scopeSegs, scopePrefix, changelogScopePre
|
|
|
1299
1158
|
continue;
|
|
1300
1159
|
}
|
|
1301
1160
|
// (B) Root section wrapper — adopts depth-0 children via buildSectionTree.
|
|
1302
|
-
|
|
1303
|
-
|
|
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);
|
|
1161
|
+
if (type === "section" && sec === "") {
|
|
1162
|
+
const sectionChildren = buildSectionTree(ctx, scopeSegs, "", scopePrefix);
|
|
1312
1163
|
const node = sectionNode(ctx, s, sectionChildren, scopePrefix);
|
|
1313
1164
|
annotated.push({ node, pos: metaNum(s.seg.metadata, "childIndex"), isLeaf: false });
|
|
1314
1165
|
continue;
|
|
1315
1166
|
}
|
|
1316
1167
|
// (C) Depth-0 or orphaned sections — emit at root unless already
|
|
1317
1168
|
// adopted by a root section (B).
|
|
1318
|
-
const depth = metaNum(s.seg.metadata, "sidebarDepth");
|
|
1319
1169
|
const parent = parentSectionPath(sec);
|
|
1320
|
-
const isRootLevel =
|
|
1321
|
-
|
|
1322
|
-
|
|
1323
|
-
//
|
|
1324
|
-
//
|
|
1325
|
-
|
|
1326
|
-
// Sections with sidebarDepth>0 are nested children that SHOULD
|
|
1327
|
-
// be adopted, so they must be skipped to avoid duplication.
|
|
1328
|
-
// Sections without sidebarDepth (old data) are also skipped —
|
|
1329
|
-
// they rely on path-based hierarchy in buildSectionTree.
|
|
1330
|
-
if (isRootLevel && hasRootSection && sec !== "" && depth !== 0) {
|
|
1170
|
+
const isRootLevel = parent === "" || (!sectionPaths.has(parent) && nearestExistingAncestor(sec, sectionPaths) === "");
|
|
1171
|
+
// Skip depth-0 sections already adopted by the root section wrapper (B),
|
|
1172
|
+
// but NOT structural types with sec="" (skip-slug). buildSectionTree
|
|
1173
|
+
// filters out sec="" segments (line 1249), so they wouldn't be adopted
|
|
1174
|
+
// and must be emitted here directly.
|
|
1175
|
+
if (isRootLevel && hasRootSection && sec !== "") {
|
|
1331
1176
|
continue;
|
|
1332
1177
|
}
|
|
1333
1178
|
if (isRootLevel) {
|
|
@@ -1345,20 +1190,7 @@ function buildSidebarRootChildren(ctx, scopeSegs, scopePrefix, changelogScopePre
|
|
|
1345
1190
|
const ownedSegs = bounds != null
|
|
1346
1191
|
? scopeSegs.filter((seg) => seg.seg.sortOrder >= bounds.lo && seg.seg.sortOrder < bounds.hi)
|
|
1347
1192
|
: scopeSegs;
|
|
1348
|
-
|
|
1349
|
-
// root wrapper in case B) is a skipUrlSlug sibling.
|
|
1350
|
-
// When sortOrder boundaries exist (multiple sec="" sections),
|
|
1351
|
-
// the scoped ownedSegs correctly limits which subsections
|
|
1352
|
-
// each section can adopt. When there are NO boundaries
|
|
1353
|
-
// (single sec="" section, e.g. mangopay's "Introduction"),
|
|
1354
|
-
// buildSectionTree("") would re-adopt all depth-0 peers;
|
|
1355
|
-
// pass an empty child list instead — its leaf artifacts are
|
|
1356
|
-
// already in its own segment nav.
|
|
1357
|
-
const isUnboundedSkipSlugSibling = sec === "" && metaNum(s.seg.metadata, "childIndex") != null && bounds == null;
|
|
1358
|
-
const sectionChildren = isUnboundedSkipSlugSibling
|
|
1359
|
-
? []
|
|
1360
|
-
: buildSectionTree(ctx, ownedSegs, sec, scopePrefix, changelogScopePrefix);
|
|
1361
|
-
node = sectionNode(ctx, s, sectionChildren, scopePrefix);
|
|
1193
|
+
node = sectionNode(ctx, s, buildSectionTree(ctx, ownedSegs, sec, scopePrefix, changelogScopePrefix), scopePrefix);
|
|
1362
1194
|
}
|
|
1363
1195
|
// Shared/merged sections (paths not rooted in scopePrefix) must not
|
|
1364
1196
|
// carry childIndex — their source-version positions would sort them
|
|
@@ -1428,6 +1260,11 @@ function groupSidebarRootChildren(ctx, children) {
|
|
|
1428
1260
|
flushRun();
|
|
1429
1261
|
out.push(child);
|
|
1430
1262
|
}
|
|
1263
|
+
else if (child.type === "changelog") {
|
|
1264
|
+
flushRun();
|
|
1265
|
+
run.push(child);
|
|
1266
|
+
flushRun();
|
|
1267
|
+
}
|
|
1431
1268
|
else {
|
|
1432
1269
|
run.push(child);
|
|
1433
1270
|
}
|
|
@@ -1594,7 +1431,7 @@ function buildVariantedNode(ctx, variants, scopeSegs, scopePrefix) {
|
|
|
1594
1431
|
? asSlug(FernNavigation.slugjoin(scopePrefix, variantIndividualSlug))
|
|
1595
1432
|
: scopePrefix;
|
|
1596
1433
|
const metaPointsTo = metaStr(variant.metadata, "pointsTo");
|
|
1597
|
-
const children = buildSidebarRootChildren(ctx, variantSegs,
|
|
1434
|
+
const children = buildSidebarRootChildren(ctx, variantSegs, scopePrefix);
|
|
1598
1435
|
return {
|
|
1599
1436
|
type: "variant",
|
|
1600
1437
|
...withMetadataDefaults({
|
|
@@ -2130,14 +1967,6 @@ function buildProductGroup(ctx, products, scoped, rootSlug, skipSharedSectionMer
|
|
|
2130
1967
|
};
|
|
2131
1968
|
return node;
|
|
2132
1969
|
});
|
|
2133
|
-
// Cross-product canonical slug deduplication (V2 parity).
|
|
2134
|
-
// V2's migrator deduplicates pages across products via a global pageId map:
|
|
2135
|
-
// the first product to register a pageId wins, and subsequent products'
|
|
2136
|
-
// pages with the same pageId receive a canonicalSlug pointing to the
|
|
2137
|
-
// canonical (default-product) page. Without this, shared content that is
|
|
2138
|
-
// materialized under every product scope (e.g. merge-api-basics pages)
|
|
2139
|
-
// appears as separate sitemap entries per product.
|
|
2140
|
-
markCrossProductCanonicalSlugs(children);
|
|
2141
1970
|
return {
|
|
2142
1971
|
type: "productgroup",
|
|
2143
1972
|
id: ctx.nodeId("productgroup"),
|
|
@@ -2146,43 +1975,5 @@ function buildProductGroup(ctx, products, scoped, rootSlug, skipSharedSectionMer
|
|
|
2146
1975
|
children
|
|
2147
1976
|
};
|
|
2148
1977
|
}
|
|
2149
|
-
/**
|
|
2150
|
-
* Deduplicate pages across products using pageId as a content-identity key.
|
|
2151
|
-
* Pages with the same pageId in different products are the same content
|
|
2152
|
-
* materialized per-scope; the default product's slug becomes canonical and
|
|
2153
|
-
* non-default copies receive `canonicalSlug` so the sitemap collapses them.
|
|
2154
|
-
*
|
|
2155
|
-
* API leaf nodes (endpoints, websockets, etc.) are unaffected — `getPageId`
|
|
2156
|
-
* returns undefined for those types, so product-specific API surface is
|
|
2157
|
-
* never suppressed.
|
|
2158
|
-
*/
|
|
2159
|
-
function markCrossProductCanonicalSlugs(productNodes) {
|
|
2160
|
-
const pageIdToCanonical = new Map();
|
|
2161
|
-
// Process default product first so its slugs become the canonical targets.
|
|
2162
|
-
const sorted = [...productNodes].sort((a, b) => {
|
|
2163
|
-
if (a.default !== b.default) {
|
|
2164
|
-
return a.default ? -1 : 1;
|
|
2165
|
-
}
|
|
2166
|
-
return 0;
|
|
2167
|
-
});
|
|
2168
|
-
for (const product of sorted) {
|
|
2169
|
-
FernNavigation.traverseDF(product, (child) => {
|
|
2170
|
-
if (FernNavigation.hasMetadata(child) && FernNavigation.isPage(child)) {
|
|
2171
|
-
const pid = FernNavigation.getPageId(child);
|
|
2172
|
-
if (pid == null) {
|
|
2173
|
-
return;
|
|
2174
|
-
}
|
|
2175
|
-
const key = `pid:${pid}`;
|
|
2176
|
-
const canonical = pageIdToCanonical.get(key);
|
|
2177
|
-
if (canonical != null && !product.default) {
|
|
2178
|
-
child.canonicalSlug = canonical;
|
|
2179
|
-
}
|
|
2180
|
-
else if (canonical == null) {
|
|
2181
|
-
pageIdToCanonical.set(key, child.canonicalSlug ?? child.slug);
|
|
2182
|
-
}
|
|
2183
|
-
}
|
|
2184
|
-
});
|
|
2185
|
-
}
|
|
2186
|
-
}
|
|
2187
1978
|
// (root keeps the bare "root" id; it is the single tree root.)
|
|
2188
1979
|
//# sourceMappingURL=ledger-root-builder.js.map
|