diff --git a/fern/apis/fdr/definition/navigation/latest/__package__.yml b/fern/apis/fdr/definition/navigation/latest/__package__.yml index 6e28d041e2..3a9809603a 100644 --- a/fern/apis/fdr/definition/navigation/latest/__package__.yml +++ b/fern/apis/fdr/definition/navigation/latest/__package__.yml @@ -327,6 +327,7 @@ types: extends: - WithNodeId - WithPermissions + - WithFeatureFlags properties: title: type: string @@ -373,6 +374,16 @@ types: properties: pointsTo: optional + WithFeatureFlags: + properties: + featureFlags: optional> + + FeatureFlagOptions: + properties: + flag: string + default: optional + match: optional + BreadcrumbItem: properties: title: string diff --git a/packages/fdr-sdk/src/__test__/__snapshots__/hume.test.ts.snap b/packages/fdr-sdk/src/__test__/__snapshots__/hume.test.ts.snap index e1c7650087..8c5d155726 100644 --- a/packages/fdr-sdk/src/__test__/__snapshots__/hume.test.ts.snap +++ b/packages/fdr-sdk/src/__test__/__snapshots__/hume.test.ts.snap @@ -130,6 +130,7 @@ exports[`hume > gets navigation root for /support 1`] = ` { "authed": undefined, "canonicalSlug": undefined, + "featureFlags": undefined, "hidden": false, "icon": undefined, "id": "root.uv.docs.docs.introduction.support", diff --git a/packages/fdr-sdk/src/__test__/__snapshots__/no-version-no-tabs.test.ts.snap b/packages/fdr-sdk/src/__test__/__snapshots__/no-version-no-tabs.test.ts.snap index 1ca59ad661..96bd20c700 100644 --- a/packages/fdr-sdk/src/__test__/__snapshots__/no-version-no-tabs.test.ts.snap +++ b/packages/fdr-sdk/src/__test__/__snapshots__/no-version-no-tabs.test.ts.snap @@ -23,6 +23,7 @@ exports[`no-version-no-tabs > gets navigation root for /docs/api/page-6 1`] = ` { "authed": undefined, "canonicalSlug": undefined, + "featureFlags": undefined, "hidden": undefined, "icon": undefined, "id": "root.uv.section-3.page-6", @@ -51,6 +52,7 @@ exports[`no-version-no-tabs > gets navigation root for /docs/api/section-2 1`] = { "authed": undefined, "canonicalSlug": undefined, + "featureFlags": undefined, "hidden": undefined, "icon": undefined, "id": "root.uv.section-2.page-3", diff --git a/packages/fdr-sdk/src/__test__/__snapshots__/no-version-yes-tabs.test.ts.snap b/packages/fdr-sdk/src/__test__/__snapshots__/no-version-yes-tabs.test.ts.snap index 6339cb230b..cb20a2386d 100644 --- a/packages/fdr-sdk/src/__test__/__snapshots__/no-version-yes-tabs.test.ts.snap +++ b/packages/fdr-sdk/src/__test__/__snapshots__/no-version-yes-tabs.test.ts.snap @@ -25,6 +25,7 @@ exports[`no-version-yes-tabs > gets navigation root for /docs/api/page-2 1`] = ` { "authed": undefined, "canonicalSlug": undefined, + "featureFlags": undefined, "hidden": undefined, "icon": undefined, "id": "root.uv.tab-1.tab-1.section-1.page-2", @@ -54,6 +55,7 @@ exports[`no-version-yes-tabs > gets navigation root for /docs/api/tab-1 1`] = ` { "authed": undefined, "canonicalSlug": undefined, + "featureFlags": undefined, "hidden": undefined, "icon": undefined, "id": "root.uv.tab-1.tab-1.section-3.page-6", @@ -75,6 +77,7 @@ exports[`no-version-yes-tabs > gets navigation root for /docs/api/tab-2 1`] = ` { "authed": undefined, "canonicalSlug": undefined, + "featureFlags": undefined, "hidden": undefined, "icon": undefined, "id": "root.uv.tab-2.tab-2.section-5.page-10", diff --git a/packages/fdr-sdk/src/__test__/__snapshots__/polytomic.test.ts.snap b/packages/fdr-sdk/src/__test__/__snapshots__/polytomic.test.ts.snap index e8e057ca06..0d537649ed 100644 --- a/packages/fdr-sdk/src/__test__/__snapshots__/polytomic.test.ts.snap +++ b/packages/fdr-sdk/src/__test__/__snapshots__/polytomic.test.ts.snap @@ -897,6 +897,7 @@ exports[`polytomic > gets navigation root for /2024-02-08/guides/introduction 1` { "authed": undefined, "canonicalSlug": undefined, + "featureFlags": undefined, "hidden": false, "icon": undefined, "id": "root..2024-02-08.uv.guides.guides.getting-started.introduction", diff --git a/packages/fdr-sdk/src/__test__/__snapshots__/primer.test.ts.snap b/packages/fdr-sdk/src/__test__/__snapshots__/primer.test.ts.snap index 7648a385b1..87d3e8e403 100644 --- a/packages/fdr-sdk/src/__test__/__snapshots__/primer.test.ts.snap +++ b/packages/fdr-sdk/src/__test__/__snapshots__/primer.test.ts.snap @@ -161,6 +161,7 @@ exports[`primer > gets navigation root for /docs/api/v2.1/api-reference/client-s "availability": undefined, "canonicalSlug": "docs/api/v2.3/api-reference/client-session-api/retrieve-client-side-token", "endpointId": "subpackage_clientSessionApi.retrieveClientSideToken", + "featureFlags": undefined, "hidden": undefined, "icon": undefined, "id": "root..v2.1.uv.api-reference.subpackage_clientSessionApi.subpackage_clientSessionApi.retrieveClientSideToken", diff --git a/packages/fdr-sdk/src/__test__/__snapshots__/uploadcare.test.ts.snap b/packages/fdr-sdk/src/__test__/__snapshots__/uploadcare.test.ts.snap index 34721771ee..8e1c6215d0 100644 --- a/packages/fdr-sdk/src/__test__/__snapshots__/uploadcare.test.ts.snap +++ b/packages/fdr-sdk/src/__test__/__snapshots__/uploadcare.test.ts.snap @@ -88,6 +88,7 @@ exports[`uploadcare > gets navigation root for /docs/file-management/overview 1` { "authed": undefined, "canonicalSlug": undefined, + "featureFlags": undefined, "hidden": undefined, "icon": undefined, "id": "root.uv.docs.docs.file-management.overview", @@ -123,6 +124,7 @@ exports[`uploadcare > gets navigation root for /docs/introduction/intro 1`] = ` { "authed": undefined, "canonicalSlug": undefined, + "featureFlags": undefined, "hidden": undefined, "icon": undefined, "id": "root.uv.docs.docs.introduction.intro", diff --git a/packages/fdr-sdk/src/__test__/__snapshots__/yes-version-no-tabs.test.ts.snap b/packages/fdr-sdk/src/__test__/__snapshots__/yes-version-no-tabs.test.ts.snap index 63c8a16fcf..f13e54eaaf 100644 --- a/packages/fdr-sdk/src/__test__/__snapshots__/yes-version-no-tabs.test.ts.snap +++ b/packages/fdr-sdk/src/__test__/__snapshots__/yes-version-no-tabs.test.ts.snap @@ -69,6 +69,7 @@ exports[`yes-version-no-tabs > gets navigation root for /docs/api/version-1/page { "authed": undefined, "canonicalSlug": undefined, + "featureFlags": undefined, "hidden": undefined, "icon": undefined, "id": "root..version-1.uv.section-3.page-5", @@ -97,6 +98,7 @@ exports[`yes-version-no-tabs > gets navigation root for /docs/api/version-1/vers { "authed": undefined, "canonicalSlug": undefined, + "featureFlags": undefined, "hidden": undefined, "icon": undefined, "id": "root..version-1.uv.section-3.page-6", @@ -125,6 +127,7 @@ exports[`yes-version-no-tabs > gets navigation root for /docs/api/version-2/vers { "authed": undefined, "canonicalSlug": undefined, + "featureFlags": undefined, "hidden": undefined, "icon": undefined, "id": "root..version-2.uv.section-5.page-9", diff --git a/packages/fdr-sdk/src/__test__/__snapshots__/yes-version-yes-tabs.test.ts.snap b/packages/fdr-sdk/src/__test__/__snapshots__/yes-version-yes-tabs.test.ts.snap index 665aef754e..adc65d0561 100644 --- a/packages/fdr-sdk/src/__test__/__snapshots__/yes-version-yes-tabs.test.ts.snap +++ b/packages/fdr-sdk/src/__test__/__snapshots__/yes-version-yes-tabs.test.ts.snap @@ -72,6 +72,7 @@ exports[`yes-version-yes-tabs > gets navigation root for /docs/api/version-1/sec { "authed": undefined, "canonicalSlug": undefined, + "featureFlags": undefined, "hidden": undefined, "icon": undefined, "id": "root..version-1.uv.tab-1.tab-1.section-2.page-3", diff --git a/packages/fdr-sdk/src/client/generated/api/resources/navigation/resources/latest/types/FeatureFlagOptions.ts b/packages/fdr-sdk/src/client/generated/api/resources/navigation/resources/latest/types/FeatureFlagOptions.ts new file mode 100644 index 0000000000..a5b4312c8f --- /dev/null +++ b/packages/fdr-sdk/src/client/generated/api/resources/navigation/resources/latest/types/FeatureFlagOptions.ts @@ -0,0 +1,9 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ + +export interface FeatureFlagOptions { + flag: string; + default: unknown | undefined; + match: unknown | undefined; +} diff --git a/packages/fdr-sdk/src/client/generated/api/resources/navigation/resources/latest/types/WithFeatureFlags.ts b/packages/fdr-sdk/src/client/generated/api/resources/navigation/resources/latest/types/WithFeatureFlags.ts new file mode 100644 index 0000000000..1fcf864468 --- /dev/null +++ b/packages/fdr-sdk/src/client/generated/api/resources/navigation/resources/latest/types/WithFeatureFlags.ts @@ -0,0 +1,9 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ + +import * as FernRegistry from "../../../../../index"; + +export interface WithFeatureFlags { + featureFlags: FernRegistry.navigation.latest.FeatureFlagOptions[] | undefined; +} diff --git a/packages/fdr-sdk/src/client/generated/api/resources/navigation/resources/latest/types/WithNodeMetadata.ts b/packages/fdr-sdk/src/client/generated/api/resources/navigation/resources/latest/types/WithNodeMetadata.ts index 5d4c18ccc8..adb1d5958e 100644 --- a/packages/fdr-sdk/src/client/generated/api/resources/navigation/resources/latest/types/WithNodeMetadata.ts +++ b/packages/fdr-sdk/src/client/generated/api/resources/navigation/resources/latest/types/WithNodeMetadata.ts @@ -6,7 +6,8 @@ import * as FernRegistry from "../../../../../index"; export interface WithNodeMetadata extends FernRegistry.navigation.latest.WithNodeId, - FernRegistry.navigation.latest.WithPermissions { + FernRegistry.navigation.latest.WithPermissions, + FernRegistry.navigation.latest.WithFeatureFlags { /** * This is the title that will be displayed in the sidebar. Unlike the h1 or SEO headline, this should be short and concise. * Do NOT use this property for the h1 of the page, the SEO headline, or search results, unless they are omitted. (This title should only be used as a fallback) diff --git a/packages/fdr-sdk/src/client/generated/api/resources/navigation/resources/latest/types/index.ts b/packages/fdr-sdk/src/client/generated/api/resources/navigation/resources/latest/types/index.ts index 101b957315..6fca98f54b 100644 --- a/packages/fdr-sdk/src/client/generated/api/resources/navigation/resources/latest/types/index.ts +++ b/packages/fdr-sdk/src/client/generated/api/resources/navigation/resources/latest/types/index.ts @@ -42,4 +42,6 @@ export * from "./WithPage"; export * from "./WithOverviewPage"; export * from "./WithApiDefinitionId"; export * from "./WithRedirect"; +export * from "./WithFeatureFlags"; +export * from "./FeatureFlagOptions"; export * from "./BreadcrumbItem"; diff --git a/packages/fdr-sdk/src/navigation/migrators/v1ToV2.ts b/packages/fdr-sdk/src/navigation/migrators/v1ToV2.ts index cdfc557660..5df221dcfa 100644 --- a/packages/fdr-sdk/src/navigation/migrators/v1ToV2.ts +++ b/packages/fdr-sdk/src/navigation/migrators/v1ToV2.ts @@ -36,6 +36,7 @@ export class FernNavigationV1ToLatest { viewers: node.viewers, orphaned: node.orphaned, roles: node.roles, + featureFlags: undefined, }; return latest; @@ -61,11 +62,11 @@ export class FernNavigationV1ToLatest { /** * the default version should be the preferred canonical slug */ - // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - const defaultVersion = this.version(node.children[defaultVersionIdx]!, [ - ...parents, - node, - ]); + const defaultVersionV1 = node.children[defaultVersionIdx]; + if (!defaultVersionV1) { + throw new Error("default version is undefined"); + } + const defaultVersion = this.version(defaultVersionV1, [...parents, node]); /** * visit the rest of the children, but splice the default version in its original position @@ -118,6 +119,7 @@ export class FernNavigationV1ToLatest { pointsTo: node.pointsTo ? FernNavigation.Slug(node.pointsTo) : undefined, viewers: node.viewers, orphaned: node.orphaned, + featureFlags: undefined, }; return latest; }; @@ -144,6 +146,7 @@ export class FernNavigationV1ToLatest { noindex: node.noindex, viewers: node.viewers, orphaned: node.orphaned, + featureFlags: undefined, }; return latest; }; @@ -183,13 +186,14 @@ export class FernNavigationV1ToLatest { pointsTo: node.pointsTo ? FernNavigation.Slug(node.pointsTo) : undefined, viewers: node.viewers, orphaned: node.orphaned, + featureFlags: undefined, }; return latest; }; public link = ( node: FernNavigation.V1.LinkNode, - parents: FernNavigation.V1.NavigationNode[] + _parents: FernNavigation.V1.NavigationNode[] ): FernNavigation.LinkNode => { const latest: FernNavigation.LinkNode = { type: "link", @@ -247,11 +251,11 @@ export class FernNavigationV1ToLatest { /** * the default product should be the preferred canonical slug */ - // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - const defaultProduct = this.product(node.children[defaultProductIdx]!, [ - ...parents, - node, - ]); + const defaultProductV1 = node.children[defaultProductIdx]; + if (!defaultProductV1) { + throw new Error("default product is undefined"); + } + const defaultProduct = this.product(defaultProductV1, [...parents, node]); /** * visit the rest of the children, but splice the default product in its original position @@ -300,6 +304,7 @@ export class FernNavigationV1ToLatest { subtitle: node.subtitle, viewers: node.viewers, orphaned: node.orphaned, + featureFlags: undefined, }; return latest; }; @@ -358,6 +363,7 @@ export class FernNavigationV1ToLatest { noindex: node.noindex, viewers: node.viewers, orphaned: node.orphaned, + featureFlags: undefined, }; return latest; }; @@ -395,6 +401,7 @@ export class FernNavigationV1ToLatest { noindex: node.noindex, viewers: node.viewers, orphaned: node.orphaned, + featureFlags: undefined, }; return latest; }; @@ -440,6 +447,7 @@ export class FernNavigationV1ToLatest { pointsTo: node.pointsTo ? FernNavigation.Slug(node.pointsTo) : undefined, viewers: node.viewers, orphaned: node.orphaned, + featureFlags: undefined, }; return latest; }; @@ -472,6 +480,7 @@ export class FernNavigationV1ToLatest { noindex: node.noindex, viewers: node.viewers, orphaned: node.orphaned, + featureFlags: undefined, }; return latest; }; @@ -495,6 +504,7 @@ export class FernNavigationV1ToLatest { year: node.year, viewers: node.viewers, orphaned: node.orphaned, + featureFlags: undefined, }; return latest; }; @@ -518,13 +528,14 @@ export class FernNavigationV1ToLatest { month: node.month, viewers: node.viewers, orphaned: node.orphaned, + featureFlags: undefined, }; return latest; }; public changelogEntry = ( node: FernNavigation.V1.ChangelogEntryNode, - parents: FernNavigation.V1.NavigationNode[] + _parents: FernNavigation.V1.NavigationNode[] ): FernNavigation.ChangelogEntryNode => { const slug = FernNavigation.Slug(node.slug); // NOTE: do NOT use title disambiguation key here, since the title may not always be unique @@ -543,6 +554,7 @@ export class FernNavigationV1ToLatest { noindex: node.noindex, viewers: node.viewers, orphaned: node.orphaned, + featureFlags: undefined, }; return latest; }; @@ -582,6 +594,7 @@ export class FernNavigationV1ToLatest { availability: this.#availability(node.availability), viewers: node.viewers, orphaned: node.orphaned, + featureFlags: undefined, }; return latest; }; @@ -615,6 +628,7 @@ export class FernNavigationV1ToLatest { isResponseStream: node.isResponseStream, viewers: node.viewers, orphaned: node.orphaned, + featureFlags: undefined, }; return latest; }; @@ -659,6 +673,7 @@ export class FernNavigationV1ToLatest { webSocketId: node.webSocketId, viewers: node.viewers, orphaned: node.orphaned, + featureFlags: undefined, }; return latest; }; @@ -690,6 +705,7 @@ export class FernNavigationV1ToLatest { webhookId: node.webhookId, viewers: node.viewers, orphaned: node.orphaned, + featureFlags: undefined, }; return latest; }; diff --git a/packages/fdr-sdk/src/navigation/utils/__test__/followRedirects.test.ts b/packages/fdr-sdk/src/navigation/utils/__test__/followRedirects.test.ts index b1ce6dc4ff..4c5e6f6faf 100644 --- a/packages/fdr-sdk/src/navigation/utils/__test__/followRedirects.test.ts +++ b/packages/fdr-sdk/src/navigation/utils/__test__/followRedirects.test.ts @@ -20,6 +20,7 @@ function sectionNode( overviewPageId: undefined, noindex: undefined, pointsTo: undefined, + featureFlags: undefined, ...section, }; } @@ -39,6 +40,7 @@ function pageNode( viewers: undefined, orphaned: undefined, noindex: undefined, + featureFlags: undefined, ...page, }; } diff --git a/packages/fdr-sdk/src/navigation/utils/__test__/pruneNavigationTree.test.ts b/packages/fdr-sdk/src/navigation/utils/__test__/pruneNavigationTree.test.ts index cb6c704420..54b06648bb 100644 --- a/packages/fdr-sdk/src/navigation/utils/__test__/pruneNavigationTree.test.ts +++ b/packages/fdr-sdk/src/navigation/utils/__test__/pruneNavigationTree.test.ts @@ -22,6 +22,7 @@ describe("pruneNavigationTree", () => { noindex: undefined, viewers: undefined, orphaned: undefined, + featureFlags: undefined, }, ], collapsed: undefined, @@ -34,6 +35,7 @@ describe("pruneNavigationTree", () => { pointsTo: undefined, viewers: undefined, orphaned: undefined, + featureFlags: undefined, }; const result = Pruner.from(root) @@ -62,6 +64,7 @@ describe("pruneNavigationTree", () => { noindex: undefined, viewers: undefined, orphaned: undefined, + featureFlags: undefined, }, ], collapsed: undefined, @@ -74,6 +77,7 @@ describe("pruneNavigationTree", () => { pointsTo: FernNavigation.Slug("root/page"), viewers: undefined, orphaned: undefined, + featureFlags: undefined, }); }); @@ -97,6 +101,7 @@ describe("pruneNavigationTree", () => { noindex: undefined, viewers: undefined, orphaned: undefined, + featureFlags: undefined, }, ], collapsed: undefined, @@ -109,6 +114,7 @@ describe("pruneNavigationTree", () => { pointsTo: FernNavigation.Slug("root/page"), viewers: undefined, orphaned: undefined, + featureFlags: undefined, }; const result = Pruner.from(root) @@ -139,6 +145,7 @@ describe("pruneNavigationTree", () => { noindex: undefined, viewers: undefined, orphaned: undefined, + featureFlags: undefined, }, ], collapsed: undefined, @@ -150,6 +157,7 @@ describe("pruneNavigationTree", () => { pointsTo: undefined, viewers: undefined, orphaned: undefined, + featureFlags: undefined, }; const result = Pruner.from(root) @@ -179,6 +187,7 @@ describe("pruneNavigationTree", () => { noindex: undefined, viewers: undefined, orphaned: undefined, + featureFlags: undefined, }, ], collapsed: undefined, @@ -190,6 +199,7 @@ describe("pruneNavigationTree", () => { pointsTo: FernNavigation.Slug("root/page"), viewers: undefined, orphaned: undefined, + featureFlags: undefined, }); }); @@ -214,6 +224,7 @@ describe("pruneNavigationTree", () => { noindex: undefined, viewers: undefined, orphaned: undefined, + featureFlags: undefined, }, ], collapsed: undefined, @@ -225,6 +236,7 @@ describe("pruneNavigationTree", () => { pointsTo: undefined, viewers: undefined, orphaned: undefined, + featureFlags: undefined, }; const result = Pruner.from(root) @@ -250,6 +262,7 @@ describe("pruneNavigationTree", () => { pointsTo: undefined, viewers: undefined, orphaned: undefined, + featureFlags: undefined, }); }); @@ -274,6 +287,7 @@ describe("pruneNavigationTree", () => { noindex: undefined, viewers: undefined, orphaned: undefined, + featureFlags: undefined, }, ], collapsed: undefined, @@ -285,6 +299,7 @@ describe("pruneNavigationTree", () => { pointsTo: undefined, viewers: undefined, orphaned: undefined, + featureFlags: undefined, }; const result = Pruner.from(root) @@ -314,6 +329,7 @@ describe("pruneNavigationTree", () => { noindex: undefined, viewers: undefined, orphaned: undefined, + featureFlags: undefined, }, ], collapsed: undefined, @@ -325,6 +341,7 @@ describe("pruneNavigationTree", () => { pointsTo: FernNavigation.Slug("root/page"), viewers: undefined, orphaned: undefined, + featureFlags: undefined, }); }); @@ -356,6 +373,7 @@ describe("pruneNavigationTree", () => { noindex: undefined, viewers: undefined, orphaned: undefined, + featureFlags: undefined, }, ], collapsed: undefined, @@ -367,6 +385,7 @@ describe("pruneNavigationTree", () => { pointsTo: undefined, viewers: undefined, orphaned: undefined, + featureFlags: undefined, }, { type: "page", @@ -381,6 +400,7 @@ describe("pruneNavigationTree", () => { noindex: undefined, viewers: undefined, orphaned: undefined, + featureFlags: undefined, }, ], collapsed: undefined, @@ -392,6 +412,7 @@ describe("pruneNavigationTree", () => { pointsTo: undefined, viewers: undefined, orphaned: undefined, + featureFlags: undefined, }; const result = Pruner.from(root) @@ -421,6 +442,7 @@ describe("pruneNavigationTree", () => { noindex: undefined, viewers: undefined, orphaned: undefined, + featureFlags: undefined, }, ], collapsed: undefined, @@ -431,7 +453,7 @@ describe("pruneNavigationTree", () => { noindex: undefined, viewers: undefined, orphaned: undefined, - + featureFlags: undefined, // NOTE: points to is updated! pointsTo: "root/page", }); diff --git a/packages/fern-docs/bundle/package.json b/packages/fern-docs/bundle/package.json index 70d4eac0f2..802ffaa0d4 100644 --- a/packages/fern-docs/bundle/package.json +++ b/packages/fern-docs/bundle/package.json @@ -44,6 +44,7 @@ "@fern-docs/utils": "workspace:*", "@fern-fern/fern-docs-sdk": "0.0.5", "@fern-platform/fdr-utils": "workspace:*", + "@launchdarkly/node-server-sdk": "^9.7.2", "@opentelemetry/api": "^1.9.0", "@opentelemetry/api-logs": "^0.57.0", "@opentelemetry/instrumentation": "^0.57.0", diff --git a/packages/fern-docs/bundle/src/middleware.ts b/packages/fern-docs/bundle/src/middleware.ts index aa2d44543f..fa3e1eadc8 100644 --- a/packages/fern-docs/bundle/src/middleware.ts +++ b/packages/fern-docs/bundle/src/middleware.ts @@ -1,5 +1,6 @@ import { rewritePosthog } from "@/server/analytics/rewritePosthog"; import { extractNextDataPathname } from "@/server/extractNextDataPathname"; +import { getLaunchDarklySettings } from "@fern-docs/edge-config"; import { removeTrailingSlash } from "next/dist/shared/lib/router/utils/remove-trailing-slash"; import { NextResponse, type NextMiddleware } from "next/server"; import { MARKDOWN_PATTERN, RSS_PATTERN } from "./server/patterns"; @@ -128,10 +129,13 @@ export const middleware: NextMiddleware = async (request) => { return NextResponse.rewrite(String(url)); } + // TODO: this adds additional latency to the page load. can we batch this somehow? + const launchDarkly = await getLaunchDarklySettings(request.nextUrl.origin); + return withMiddlewareAuth( request, pathname, - withMiddlewareRewrite(request, pathname) + withMiddlewareRewrite(request, pathname, !!launchDarkly?.["sdk-key"]) ); }; diff --git a/packages/fern-docs/bundle/src/server/ld-adapter.ts b/packages/fern-docs/bundle/src/server/ld-adapter.ts new file mode 100644 index 0000000000..64940d46b2 --- /dev/null +++ b/packages/fern-docs/bundle/src/server/ld-adapter.ts @@ -0,0 +1,134 @@ +import { FernNavigation } from "@fern-api/fdr-sdk"; +import { getLaunchDarklySettings } from "@fern-docs/edge-config"; +import * as ld from "@launchdarkly/node-server-sdk"; +import { isEqual } from "es-toolkit/predicate"; +import { camelCase } from "es-toolkit/string"; +import { AuthState } from "./auth/getAuthState"; + +async function withLaunchDarklyContext( + endpoint: string, + authState: AuthState, + node: FernNavigation.utils.Node, + rawCookie: string | undefined +): Promise { + try { + const url = new URL(endpoint); + url.searchParams.set("anonymous", String(!authState.authed)); + if (node.type === "found") { + if (node.currentVersion) { + url.searchParams.set("version", node.currentVersion.versionId); + } + } + + const headers = new Headers(); + + // TODO: this forwards the cookie to an adapter that should generate a LaunchDarkly context + // if we migrate from edge config to docs.yml, we need to maintain an strict allowlist + if (rawCookie != null) { + headers.set("Cookie", rawCookie); + } + + const response = await fetch(url, { headers }); + if (!response.ok) { + throw new Error( + `Failed to fetch LaunchDarkly context: ${response.statusText}` + ); + } + const context = await response.json(); + + return context; + } catch (error) { + console.error(error); + return { kind: "user", key: "anonymous", anonymous: true }; + } +} + +interface LaunchDarklyInfo { + clientSideId: string; + contextEndpoint: string; + context: ld.LDContext | undefined; + defaultFlags: Record | undefined; +} + +export async function withLaunchDarkly( + domain: string, + authState: AuthState, + node: FernNavigation.utils.Node, + rawCookie: string | undefined +): Promise< + [ + LaunchDarklyInfo | undefined, + (node: FernNavigation.WithFeatureFlags) => boolean, + ] +> { + const launchDarklyConfig = await getLaunchDarklySettings(domain); + if (launchDarklyConfig) { + const context = await withLaunchDarklyContext( + launchDarklyConfig["context-endpoint"], + authState, + node, + rawCookie + ); + const defaultFlags = launchDarklyConfig["sdk-key"] + ? await fetchInitialFlags(launchDarklyConfig["sdk-key"], context) + : undefined; + return [ + { + clientSideId: launchDarklyConfig["client-side-id"], + contextEndpoint: launchDarklyConfig["context-endpoint"], + context, + defaultFlags, + }, + // Note: if sdk-key is set, then middleware will automatically switch to 100% getServerSideProps + // because getServerSideProps must determine whether any given page should be rendered or not. + launchDarklyConfig["sdk-key"] + ? await createLdPredicate({ flags: defaultFlags }) + : createDefaultFeatureFlagPredicate(), + ]; + } + return [undefined, createDefaultFeatureFlagPredicate()]; +} + +function createDefaultFeatureFlagPredicate(): ( + node: FernNavigation.WithFeatureFlags +) => boolean { + return (node) => + node.featureFlags == null || + node.featureFlags.length === 0 || + node.featureFlags.some((flag) => { + const default_ = flag.default ?? false; + const match = flag.match ?? true; + return isEqual(default_, match); + }); +} + +export const createLdPredicate = async ({ + flags = {}, +}: { + flags?: Record; +}): Promise<(node: FernNavigation.WithFeatureFlags) => boolean> => { + return (node) => { + if (node.featureFlags == null || node.featureFlags.length === 0) { + return true; + } + return node.featureFlags.some((flag) => { + const key = camelCase(flag.flag); + const flagValue = flags[key] ?? flag.default ?? false; + const match = flag.match ?? true; + return isEqual(flagValue, match); + }); + }; +}; + +function fetchInitialFlags( + sdkKey: string, + context: ld.LDContext +): Promise> | undefined { + try { + const ldClient = ld.init(sdkKey); + return ldClient.allFlagsState(context).then((flags) => flags.allValues()); + } catch (error) { + console.error(error); + return undefined; + } +} diff --git a/packages/fern-docs/bundle/src/server/withInitialProps.ts b/packages/fern-docs/bundle/src/server/withInitialProps.ts index a75c37d441..725155dab8 100644 --- a/packages/fern-docs/bundle/src/server/withInitialProps.ts +++ b/packages/fern-docs/bundle/src/server/withInitialProps.ts @@ -5,7 +5,6 @@ import { getAuthEdgeConfig, getCustomerAnalytics, getEdgeFlags, - getLaunchDarklySettings, getSeoDisabled, } from "@fern-docs/edge-config"; import { @@ -24,9 +23,10 @@ import { GetServerSidePropsResult, Redirect } from "next"; import { ComponentProps } from "react"; import urlJoin from "url-join"; import { DocsLoader } from "./DocsLoader"; -import { AuthState, getAuthState } from "./auth/getAuthState"; +import { getAuthState } from "./auth/getAuthState"; import { getReturnToQueryParam } from "./auth/return-to"; import { handleLoadDocsError } from "./handleLoadDocsError"; +import { withLaunchDarkly } from "./ld-adapter"; import type { LoadWithUrlResponse } from "./loadWithUrl"; import { isTrailingSlashEnabled } from "./trailingSlash"; import { @@ -156,6 +156,22 @@ export async function withInitialProps({ return withRedirect(authState.authorizationUrl); } + // TODO: parallelize this with the other edge config calls: + const [launchDarkly, flagPredicate] = await withLaunchDarkly( + domain, + authState, + found, + rawCookie + ); + + if ( + ![...found.parents, found.node] + .filter(FernNavigation.hasMetadata) + .every((node) => flagPredicate(node)) + ) { + return { notFound: true }; + } + const content = await withResolvedDocsContent({ domain: docs.baseUrl.domain, found, @@ -330,21 +346,6 @@ export async function withInitialProps({ const engine = edgeFlags.useMdxBundler ? "mdx-bundler" : "next-mdx-remote"; const serializeMdx = await getMdxBundler(engine); - // TODO: parallelize this with the other edge config calls: - const launchDarklyConfig = await getLaunchDarklySettings(docs.baseUrl.domain); - const launchDarkly = launchDarklyConfig - ? { - clientSideId: launchDarklyConfig["client-side-id"], - contextEndpoint: launchDarklyConfig["context-endpoint"], - context: await withLaunchDarklyContext( - launchDarklyConfig["context-endpoint"], - authState, - found, - rawCookie - ), - } - : undefined; - const props: ComponentProps = { baseUrl: docs.baseUrl, layout: docs.definition.config.layout, @@ -435,34 +436,3 @@ function withRedirect(destination: string): { redirect: Redirect } { } return { redirect: { destination, permanent: false } }; } - -async function withLaunchDarklyContext( - endpoint: string, - authState: AuthState, - node: FernNavigation.utils.Node, - rawCookie: string | undefined -) { - try { - const url = new URL(endpoint); - url.searchParams.set("anonymous", String(!authState.authed)); - if (node.type === "found") { - if (node.currentVersion) { - url.searchParams.set("version", node.currentVersion.versionId); - } - } - - const headers = new Headers(); - - // TODO: this forwards the cookie to an adapter that should generate a LaunchDarkly context - // if we migrate from edge config to docs.yml, we need to maintain an strict allowlist - if (rawCookie != null) { - headers.set("Cookie", rawCookie); - } - - const context = await fetch(url, { headers }).then((res) => res.json()); - - return context; - } catch { - return { kind: "user", key: "anonymous", anonymous: true }; - } -} diff --git a/packages/fern-docs/bundle/src/server/withMiddlewareRewrite.ts b/packages/fern-docs/bundle/src/server/withMiddlewareRewrite.ts index db0c92b1f1..d42f8d656e 100644 --- a/packages/fern-docs/bundle/src/server/withMiddlewareRewrite.ts +++ b/packages/fern-docs/bundle/src/server/withMiddlewareRewrite.ts @@ -7,7 +7,8 @@ import { getDocsDomainEdge, getHostEdge } from "./xfernhost/edge"; export function withMiddlewareRewrite( request: NextRequest, - pathname: string + pathname: string, + dynamic = false ): (isLoggedIn: boolean) => NextResponse { const domain = getDocsDomainEdge(request); const host = getHostEdge(request); @@ -22,13 +23,13 @@ export function withMiddlewareRewrite( */ const hasError = request.nextUrl.searchParams.get("error") === "true"; - return (isLoggedIn) => { + return (isLoggedIn: boolean) => { /** * There are two types of pages in the docs bundle: * - static = SSG pages * - dynamic = SSR pages (because fern_token is present or there is an error) */ - const isDynamic = isLoggedIn || hasError; + const isDynamic = dynamic || isLoggedIn || hasError; if (!isDynamic) { search.set("host", host); diff --git a/packages/fern-docs/components/src/index.scss b/packages/fern-docs/components/src/index.scss index b8fa62c37d..115dfae982 100644 --- a/packages/fern-docs/components/src/index.scss +++ b/packages/fern-docs/components/src/index.scss @@ -13,3 +13,4 @@ @import "./badges/index.scss"; @import "./kbd.scss"; @import "./accordion.scss"; +@import "./utilities.scss"; diff --git a/packages/fern-docs/ui/src/css/utilities.scss b/packages/fern-docs/components/src/utilities.scss similarity index 100% rename from packages/fern-docs/ui/src/css/utilities.scss rename to packages/fern-docs/components/src/utilities.scss diff --git a/packages/fern-docs/edge-config/src/getLaunchDarklySettings.ts b/packages/fern-docs/edge-config/src/getLaunchDarklySettings.ts index 4785d4ef82..98dc35b8c7 100644 --- a/packages/fern-docs/edge-config/src/getLaunchDarklySettings.ts +++ b/packages/fern-docs/edge-config/src/getLaunchDarklySettings.ts @@ -5,6 +5,7 @@ import { z } from "zod"; const LaunchDarklyEdgeConfigSchema = z.object({ // NOTE: this is client-side visible, so we should be careful about what we expose here if we add more fields "client-side-id": z.string(), + "sdk-key": z.string().optional(), // IMPORTANT: we will pass cookies to this endpoint, so if we move this to docs.yml, // we should add a check to make sure the target domain is trusted. Trust should always be granted manually by a fern engineer, diff --git a/packages/fern-docs/ui/src/atoms/types.ts b/packages/fern-docs/ui/src/atoms/types.ts index b15f9dfdd4..9de732c5ba 100644 --- a/packages/fern-docs/ui/src/atoms/types.ts +++ b/packages/fern-docs/ui/src/atoms/types.ts @@ -56,6 +56,7 @@ export interface LaunchDarklyInfo { clientSideId: string; contextEndpoint: string; context: LDContext | undefined; + defaultFlags: Record | undefined; } export interface FeatureFlagsConfig { diff --git a/packages/fern-docs/ui/src/css/globals.scss b/packages/fern-docs/ui/src/css/globals.scss index 37181b321e..7677439334 100644 --- a/packages/fern-docs/ui/src/css/globals.scss +++ b/packages/fern-docs/ui/src/css/globals.scss @@ -12,7 +12,6 @@ @import "../components"; @import "../mdx/components"; @import "../changelog"; -@import "./utilities"; @import "../themes"; @import "../header"; @import "../sidebar"; diff --git a/packages/fern-docs/ui/src/feature-flags/Feature.tsx b/packages/fern-docs/ui/src/feature-flags/Feature.tsx index 8f9fc59348..6f18fe9e98 100644 --- a/packages/fern-docs/ui/src/feature-flags/Feature.tsx +++ b/packages/fern-docs/ui/src/feature-flags/Feature.tsx @@ -11,7 +11,7 @@ const LDFeature = dynamic( // TODO: This becomes an indirection point where we can use different feature flag implementations // with different providers depending on config -export const Feature = (props: FeatureProps): React.ReactNode => ( +export const Feature = (props: FeatureProps): React.ReactNode => ( diff --git a/packages/fern-docs/ui/src/feature-flags/FeatureFlagDebug.tsx b/packages/fern-docs/ui/src/feature-flags/FeatureFlagDebug.tsx new file mode 100644 index 0000000000..d5e4ab3611 --- /dev/null +++ b/packages/fern-docs/ui/src/feature-flags/FeatureFlagDebug.tsx @@ -0,0 +1,20 @@ +import type { FernNavigation } from "@fern-api/fdr-sdk"; +import { Badge } from "@fern-docs/components"; +import { PropsWithChildren } from "react"; +import { useIsLocalPreview } from "../contexts/local-preview"; + +export const FeatureFlagDebug = ( + props: PropsWithChildren +) => { + const isLocalPreview = useIsLocalPreview(); + if (!isLocalPreview) { + return props.children; + } + + return ( + <> + Feature flag: {props.flag} + {props.children} + + ); +}; diff --git a/packages/fern-docs/ui/src/feature-flags/FeatureFlagProvider.tsx b/packages/fern-docs/ui/src/feature-flags/FeatureFlagProvider.tsx index 48aa62b26e..dcfc85113a 100644 --- a/packages/fern-docs/ui/src/feature-flags/FeatureFlagProvider.tsx +++ b/packages/fern-docs/ui/src/feature-flags/FeatureFlagProvider.tsx @@ -28,6 +28,7 @@ export const FeatureFlagProvider: FC = ({ clientSideId={launchDarklyInfo.clientSideId} contextEndpoint={launchDarklyInfo.contextEndpoint} defaultContext={launchDarklyInfo.context} + defaultFlags={launchDarklyInfo.defaultFlags} > {children} diff --git a/packages/fern-docs/ui/src/feature-flags/LDFeature.tsx b/packages/fern-docs/ui/src/feature-flags/LDFeature.tsx index f2fd66c424..96e45ccad2 100644 --- a/packages/fern-docs/ui/src/feature-flags/LDFeature.tsx +++ b/packages/fern-docs/ui/src/feature-flags/LDFeature.tsx @@ -1,39 +1,58 @@ +import { isEqual } from "es-toolkit/predicate"; import { camelCase } from "es-toolkit/string"; import { LDFlagSet, useFlags } from "launchdarkly-react-client-sdk"; -import { FeatureProps } from "./types"; +import { FeatureProps, WithFeatureFlagsProps } from "./types"; const ldFlagPredicate = ( flagKey: string, - defaultValue: T -): ((flags: LDFlagSet) => T) => { + defaultValue: T, + match: T +): ((flags: LDFlagSet) => boolean) => { return (flags: LDFlagSet) => { // Camel case the flag key here to match logic from LaunchDarkly's Gatsby plugin. // They use snake-case keys in their UI but camelCase in their React SDK and do this to ease // the developer experience; we're doing the same thing to be consistent with them. // See https://github.com/launchdarkly/LaunchDarkly-Docs/blob/95cebf9ef4841da96ddfec9dd6e3ed76455025d0/src/flags/flagPredicate.ts#L6 const camelKey = camelCase(flagKey); - const flagValue = flags[camelKey]; - - if (typeof flagValue === "undefined") { - return defaultValue; - } - - return flagValue as T; + const flagValue = flags[camelKey] ?? defaultValue; + return isEqual(flagValue as T, match); }; }; -export const LDFeature: (props: FeatureProps) => React.ReactNode = ({ +export const LDFeature: React.FC = ({ flag, default: defaultValue = false, match = true, children, }) => { const flags = useFlags(); - const flagPredicate = ldFlagPredicate(flag, defaultValue); + const flagPredicate = ldFlagPredicate(flag, defaultValue, match); - if (flagPredicate(flags) === match) { + if (flagPredicate(flags)) { return children; } return false; }; + +export const LDFeatures: React.FC = ({ + featureFlags, + children, + fallback = false, +}) => { + const flags = useFlags(); + + if (!featureFlags?.length) { + return children; + } + + const predicates = featureFlags.map((featureFlag) => + ldFlagPredicate(featureFlag.flag, featureFlag.default, featureFlag.match) + ); + + if (predicates.some((predicate) => predicate(flags))) { + return children; + } + + return fallback; +}; diff --git a/packages/fern-docs/ui/src/feature-flags/LDFeatureFlagProvider.tsx b/packages/fern-docs/ui/src/feature-flags/LDFeatureFlagProvider.tsx index 113fe067aa..105eee7c70 100644 --- a/packages/fern-docs/ui/src/feature-flags/LDFeatureFlagProvider.tsx +++ b/packages/fern-docs/ui/src/feature-flags/LDFeatureFlagProvider.tsx @@ -21,16 +21,23 @@ interface Props extends PropsWithChildren { * @default { kind: "user", key: "anonymous", anonymous: true } */ defaultContext?: LDContext; + + defaultFlags?: Record; } export const LDFeatureFlagProvider: FC = ({ clientSideId, contextEndpoint, defaultContext, + defaultFlags, children, }) => { return ( - + import("./LDFeature").then((mod) => mod.LDFeatures), + // however, we do need the default evaluation to be SSR'd + { ssr: true } +); + +export const WithFeatureFlags: React.FC = ({ + featureFlags, + children, + fallback, +}) => { + // do not import LDFeatures if there are no feature flags + if (!featureFlags?.length) { + return children; + } + + return ( + + + {children} + + + ); +}; diff --git a/packages/fern-docs/ui/src/feature-flags/types.ts b/packages/fern-docs/ui/src/feature-flags/types.ts index a5a5541909..ce249745ef 100644 --- a/packages/fern-docs/ui/src/feature-flags/types.ts +++ b/packages/fern-docs/ui/src/feature-flags/types.ts @@ -1,20 +1,11 @@ -export interface FeatureProps { - /** - * The flag to check. This is typically kebab-case but depends on the feature flag provider. - */ - flag: string; - /** - * The default value to use if the flag is not set - * @default false - */ - default?: T; - /** - * The value to match against the flag - * @default true - */ - match?: T; - /** - * The content to render if the flag is set and matches the match value - */ - children: React.ReactNode; -} +import type { FernNavigation } from "@fern-api/fdr-sdk"; +import type { PropsWithChildren, ReactNode } from "react"; + +export type FeatureProps = + PropsWithChildren & { + fallback?: ReactNode; + }; +export type WithFeatureFlagsProps = + PropsWithChildren & { + fallback?: ReactNode; + }; diff --git a/packages/fern-docs/ui/src/mdx/components/feature/index.ts b/packages/fern-docs/ui/src/mdx/components/feature/index.ts deleted file mode 100644 index 5de3a1e301..0000000000 --- a/packages/fern-docs/ui/src/mdx/components/feature/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from "../../../feature-flags/Feature"; diff --git a/packages/fern-docs/ui/src/mdx/components/feature/index.tsx b/packages/fern-docs/ui/src/mdx/components/feature/index.tsx new file mode 100644 index 0000000000..60929b2921 --- /dev/null +++ b/packages/fern-docs/ui/src/mdx/components/feature/index.tsx @@ -0,0 +1,9 @@ +import { Feature as FeatureComponent } from "../../../feature-flags/Feature"; +import { FeatureFlagDebug } from "../../../feature-flags/FeatureFlagDebug"; +import { FeatureProps } from "../../../feature-flags/types"; + +export const Feature = (props: FeatureProps) => ( + + + +); diff --git a/packages/fern-docs/ui/src/playground/code-snippets/__test__/code-snippets.test.ts b/packages/fern-docs/ui/src/playground/code-snippets/__test__/code-snippets.test.ts index 60124b63ce..28ad727f2e 100644 --- a/packages/fern-docs/ui/src/playground/code-snippets/__test__/code-snippets.test.ts +++ b/packages/fern-docs/ui/src/playground/code-snippets/__test__/code-snippets.test.ts @@ -34,6 +34,7 @@ describe("PlaygroundCodeSnippetBuilder", () => { authed: undefined, viewers: undefined, orphaned: undefined, + featureFlags: undefined, }; const endpoint: EndpointDefinition = { diff --git a/packages/fern-docs/ui/src/seo/__test__/getBreadcrumbList.test.ts b/packages/fern-docs/ui/src/seo/__test__/getBreadcrumbList.test.ts index b38c1eee8f..0edd02fba9 100644 --- a/packages/fern-docs/ui/src/seo/__test__/getBreadcrumbList.test.ts +++ b/packages/fern-docs/ui/src/seo/__test__/getBreadcrumbList.test.ts @@ -16,6 +16,7 @@ describe("getBreadcrumbList", () => { authed: undefined, viewers: undefined, orphaned: undefined, + featureFlags: undefined, }; const parents: FernNavigation.NavigationNode[] = [ { @@ -34,6 +35,7 @@ describe("getBreadcrumbList", () => { authed: undefined, viewers: undefined, orphaned: undefined, + featureFlags: undefined, }, ]; expect( diff --git a/packages/fern-docs/ui/src/seo/__test__/getSeoProps.test.ts b/packages/fern-docs/ui/src/seo/__test__/getSeoProps.test.ts index aae083a09d..b193b4df38 100644 --- a/packages/fern-docs/ui/src/seo/__test__/getSeoProps.test.ts +++ b/packages/fern-docs/ui/src/seo/__test__/getSeoProps.test.ts @@ -25,6 +25,7 @@ describe("getSeoProps", () => { authed: undefined, viewers: undefined, orphaned: undefined, + featureFlags: undefined, }, parents: [], currentVersion: undefined, diff --git a/packages/fern-docs/ui/src/sidebar/nodes/SidebarApiLeafNode.tsx b/packages/fern-docs/ui/src/sidebar/nodes/SidebarApiLeafNode.tsx index 18e0b5fb2a..e5056e5563 100644 --- a/packages/fern-docs/ui/src/sidebar/nodes/SidebarApiLeafNode.tsx +++ b/packages/fern-docs/ui/src/sidebar/nodes/SidebarApiLeafNode.tsx @@ -3,6 +3,7 @@ import { HttpMethodBadge } from "@fern-docs/components/badges"; import clsx from "clsx"; import { ReactNode } from "react"; import { useIsSelectedSidebarNode } from "../../atoms"; +import { WithFeatureFlags } from "../../feature-flags/WithFeatureFlags"; import { SidebarSlugLink } from "../SidebarLink"; interface SidebarApiLeafNodeProps { @@ -67,24 +68,26 @@ export function SidebarApiLeafNode({ // }; return ( - - {node.title} - - } - depth={Math.max(0, depth - 1)} - hidden={node.hidden} - authed={node.authed} - icon={renderLeftElement()} - selected={selected} - shallow={shallow} - /> + + + {node.title} + + } + depth={Math.max(0, depth - 1)} + hidden={node.hidden} + authed={node.authed} + icon={renderLeftElement()} + selected={selected} + shallow={shallow} + /> + ); } diff --git a/packages/fern-docs/ui/src/sidebar/nodes/SidebarApiPackageNode.tsx b/packages/fern-docs/ui/src/sidebar/nodes/SidebarApiPackageNode.tsx index 01770c5b35..69b748609b 100644 --- a/packages/fern-docs/ui/src/sidebar/nodes/SidebarApiPackageNode.tsx +++ b/packages/fern-docs/ui/src/sidebar/nodes/SidebarApiPackageNode.tsx @@ -7,6 +7,7 @@ import { useIsSelectedSidebarNode, useToggleExpandedSidebarNode, } from "../../atoms"; +import { WithFeatureFlags } from "../../feature-flags/WithFeatureFlags"; import { CollapsibleSidebarGroup } from "../CollapsibleSidebarGroup"; import { SidebarSlugLink } from "../SidebarLink"; import { SidebarApiPackageChild } from "./SidebarApiPackageChild"; @@ -59,39 +60,41 @@ export function SidebarApiPackageNode({ const showIndicator = childSelected && !expanded; return ( - - open={expanded} - nodes={node.children} - renderNode={renderNode} - > - { - if (e.isDefaultPrevented()) { - return; - } - if (selected && expanded) { + + + open={expanded} + nodes={node.children} + renderNode={renderNode} + > + { + if (e.isDefaultPrevented()) { + return; + } + if (selected && expanded) { + e.preventDefault(); + } + handleToggleExpand(); + }} + onClickIndicator={(e) => { + handleToggleExpand(); e.preventDefault(); - } - handleToggleExpand(); - }} - onClickIndicator={(e) => { - handleToggleExpand(); - e.preventDefault(); - }} - expandable={node.children.length > 0} - showIndicator={showIndicator} - hidden={node.hidden} - authed={node.authed} - slug={node.overviewPageId != null ? node.slug : undefined} - selected={selected} - shallow={shallow} - /> - + }} + expandable={node.children.length > 0} + showIndicator={showIndicator} + hidden={node.hidden} + authed={node.authed} + slug={node.overviewPageId != null ? node.slug : undefined} + selected={selected} + shallow={shallow} + /> + + ); } diff --git a/packages/fern-docs/ui/src/sidebar/nodes/SidebarChangelogNode.tsx b/packages/fern-docs/ui/src/sidebar/nodes/SidebarChangelogNode.tsx index 1a379ae402..acde11d0c7 100644 --- a/packages/fern-docs/ui/src/sidebar/nodes/SidebarChangelogNode.tsx +++ b/packages/fern-docs/ui/src/sidebar/nodes/SidebarChangelogNode.tsx @@ -2,6 +2,7 @@ import type * as FernNavigation from "@fern-api/fdr-sdk/navigation"; import { Calendar } from "iconoir-react"; import { ReactNode } from "react"; import { useIsSelectedSidebarNode } from "../../atoms"; +import { WithFeatureFlags } from "../../feature-flags/WithFeatureFlags"; import { Changelog } from "../../util/dateUtils"; import { SidebarSlugLink } from "../SidebarLink"; @@ -23,18 +24,20 @@ export function SidebarChangelogNode({ } return ( - } - tooltipContent={renderChangelogTooltip(node)} - hidden={node.hidden} - authed={node.authed} - /> + + } + tooltipContent={renderChangelogTooltip(node)} + hidden={node.hidden} + authed={node.authed} + /> + ); } diff --git a/packages/fern-docs/ui/src/sidebar/nodes/SidebarGroupApiReferenceNode.tsx b/packages/fern-docs/ui/src/sidebar/nodes/SidebarGroupApiReferenceNode.tsx index b168b416bc..8710dc9dfd 100644 --- a/packages/fern-docs/ui/src/sidebar/nodes/SidebarGroupApiReferenceNode.tsx +++ b/packages/fern-docs/ui/src/sidebar/nodes/SidebarGroupApiReferenceNode.tsx @@ -1,6 +1,7 @@ import { FernNavigation } from "@fern-api/fdr-sdk"; import { ReactNode } from "react"; import { useIsApiReferenceShallowLink } from "../../atoms"; +import { WithFeatureFlags } from "../../feature-flags/WithFeatureFlags"; import { SidebarApiPackageChild } from "./SidebarApiPackageChild"; interface SidebarGroupApiReferenceNodeProps { @@ -15,16 +16,18 @@ export function SidebarGroupApiReferenceNode({ const shallow = useIsApiReferenceShallowLink(node); return ( -
    - {node.children.map((child) => ( -
  • - -
  • - ))} -
+ +
    + {node.children.map((child) => ( +
  • + +
  • + ))} +
+
); } diff --git a/packages/fern-docs/ui/src/sidebar/nodes/SidebarNavigationChild.tsx b/packages/fern-docs/ui/src/sidebar/nodes/SidebarNavigationChild.tsx index f049563152..5a6a921419 100644 --- a/packages/fern-docs/ui/src/sidebar/nodes/SidebarNavigationChild.tsx +++ b/packages/fern-docs/ui/src/sidebar/nodes/SidebarNavigationChild.tsx @@ -1,21 +1,13 @@ import type * as FernNavigation from "@fern-api/fdr-sdk/navigation"; import clsx from "clsx"; -import dynamic from "next/dynamic"; import { ReactNode } from "react"; import { UnreachableCaseError } from "ts-essentials"; import { SidebarApiPackageNode } from "./SidebarApiPackageNode"; +import { SidebarChangelogNode } from "./SidebarChangelogNode"; import { SidebarLinkNode } from "./SidebarLinkNode"; import { SidebarPageNode } from "./SidebarPageNode"; import { SidebarSectionNode } from "./SidebarSectionNode"; -const SidebarChangelogNode = dynamic( - () => - import("./SidebarChangelogNode").then( - ({ SidebarChangelogNode }) => SidebarChangelogNode - ), - { ssr: true } -); - interface SidebarNavigationChildProps { node: FernNavigation.NavigationChild; depth: number; diff --git a/packages/fern-docs/ui/src/sidebar/nodes/SidebarPageNode.tsx b/packages/fern-docs/ui/src/sidebar/nodes/SidebarPageNode.tsx index 724adc6e5b..1c4116a359 100644 --- a/packages/fern-docs/ui/src/sidebar/nodes/SidebarPageNode.tsx +++ b/packages/fern-docs/ui/src/sidebar/nodes/SidebarPageNode.tsx @@ -1,6 +1,7 @@ import type * as FernNavigation from "@fern-api/fdr-sdk/navigation"; import { ReactNode } from "react"; import { useIsSelectedSidebarNode } from "../../atoms"; +import { WithFeatureFlags } from "../../feature-flags/WithFeatureFlags"; import { SidebarSlugLink } from "../SidebarLink"; interface SidebarPageNodeProps { @@ -25,18 +26,20 @@ export function SidebarPageNode({ } return ( -