From 6e6605b9242736a0107b43970029d8af1486836b Mon Sep 17 00:00:00 2001 From: Andrew Jiang Date: Thu, 6 Feb 2025 04:38:15 -0500 Subject: [PATCH] navigable site --- package.json | 4 +- .../core-utils/src/withDefaultProtocol.ts | 4 + packages/fern-docs/bundle/package.json | 4 +- .../page.tsx => [domain]/docs-page.tsx} | 67 +++--- .../bundle/src/app/[domain]/layout.tsx | 190 ++++++++++++++++++ .../[domain]/~dynamic/[[...slug]]/page.tsx | 23 +++ .../app/[domain]/~static/[[...slug]]/page.tsx | 20 ++ .../[api]/endpoint/[endpoint]/route.ts | 12 +- .../[api]/webhook/[webhook]/route.ts | 12 +- .../[api]/websocket/[websocket]/route.ts | 12 +- .../fern-docs/auth/api-key-injection/route.ts | 4 +- .../src/app/api/fern-docs/revalidate/route.ts | 2 +- .../app/api/fern-docs/search/v1/key/route.ts | 9 +- .../app/api/fern-docs/search/v2/key/route.ts | 4 +- packages/fern-docs/bundle/src/app/layout.tsx | 150 +------------- .../analytics/CustomerAnalytics.tsx | 2 + .../src/components/analytics/use-track.ts | 2 +- .../bundle/src/components/atoms/apis.ts | 29 +-- .../bundle/src/components/atoms/navigation.ts | 2 +- .../bundle/src/components/atoms/viewport.ts | 2 +- .../src/components/docs/DocsMainContent.tsx | 19 +- .../bundle/src/components/docs/DocsPage.tsx | 23 +-- .../bundle/src/components/docs/NextApp.tsx | 21 +- .../src/components/hooks/useIsScrolled.ts | 4 +- .../src/components/hooks/useViewportSize.ts | 2 +- .../bundle/src/components/preload.tsx | 10 +- .../src/components/themes/ThemeScript.tsx | 2 + .../src/components/themes/ThemedDocs.tsx | 22 +- packages/fern-docs/bundle/src/middleware.ts | 8 +- .../fern-docs/bundle/src/server/DocsLoader.ts | 7 +- .../bundle/src/server/auth/getAuthState.ts | 27 +-- .../src/server/auth/getAuthStateEdge.ts | 16 +- .../src/server/auth/getAuthStateNode.ts | 18 -- .../bundle/src/server/auth/origin.ts | 13 ++ .../bundle/src/server/auth/workos-handler.ts | 15 +- .../bundle/src/server/docs-loader.ts | 22 +- .../bundle/src/server/env-variables.ts | 2 +- .../fern-docs/bundle/src/server/registry.ts | 5 +- .../bundle/src/server/withMiddlewareAuth.ts | 15 +- .../fern-docs/components/src/FernToast.tsx | 2 + .../search-ui/src/server/env-variables.ts | 2 +- .../src/server/run-reindex-algolia.ts | 6 +- .../src/server/run-reindex-turbopuffer.ts | 8 +- pnpm-lock.yaml | 77 ++++--- 44 files changed, 487 insertions(+), 413 deletions(-) rename packages/fern-docs/bundle/src/app/{[[...slug]]/page.tsx => [domain]/docs-page.tsx} (91%) create mode 100644 packages/fern-docs/bundle/src/app/[domain]/layout.tsx create mode 100644 packages/fern-docs/bundle/src/app/[domain]/~dynamic/[[...slug]]/page.tsx create mode 100644 packages/fern-docs/bundle/src/app/[domain]/~static/[[...slug]]/page.tsx delete mode 100644 packages/fern-docs/bundle/src/server/auth/getAuthStateNode.ts create mode 100644 packages/fern-docs/bundle/src/server/auth/origin.ts diff --git a/package.json b/package.json index c0171cd480..80a2f9c0d0 100644 --- a/package.json +++ b/package.json @@ -43,7 +43,7 @@ "elliptic": "6.6.0", "esbuild": "0.24.2", "eslint": "9.17.0", - "eslint-config-next": "15.1.2", + "eslint-config-next": "14.2.23", "instantsearch.js": "4.75.4", "jsonpath-plus": "10.0.7", "markdown-to-jsx": "7.4.0", @@ -84,7 +84,7 @@ "depcheck": "^1.4.7", "dotenv": "^16.4.5", "eslint": "^9", - "eslint-config-next": "15.1.2", + "eslint-config-next": "14.2.23", "eslint-config-prettier": "^9.1.0", "eslint-config-turbo": "^2.3.3", "eslint-plugin-deprecation": "^3.0.0", diff --git a/packages/commons/core-utils/src/withDefaultProtocol.ts b/packages/commons/core-utils/src/withDefaultProtocol.ts index 901fd18ebb..fa2a75b11e 100644 --- a/packages/commons/core-utils/src/withDefaultProtocol.ts +++ b/packages/commons/core-utils/src/withDefaultProtocol.ts @@ -21,6 +21,10 @@ export function withDefaultProtocol( return undefined; } + if (endpoint === "") { + throw new Error(`URL is empty`); + } + // matches any protocol scheme at the beginning of the string (e.g., "http://", "https://", "ftp://") const protocolRegex = /^[a-z]+:\/\//i; diff --git a/packages/fern-docs/bundle/package.json b/packages/fern-docs/bundle/package.json index 2e4762c909..5ee190d816 100644 --- a/packages/fern-docs/bundle/package.json +++ b/packages/fern-docs/bundle/package.json @@ -17,7 +17,8 @@ "chromatic": "pnpx chromatic --project-token=chpt_48b3c560025e978", "depcheck": "depcheck", "docs:build": "next build", - "docs:dev": "NODE_OPTIONS='--inspect' next dev", + "docs:dev": "next dev", + "docs:dev:inspect": "NODE_OPTIONS='--inspect' next dev", "docs:start": "next start", "format": "prettier --write --ignore-unknown \"**\"", "format:check": "prettier --check --ignore-unknown \"**\"", @@ -79,6 +80,7 @@ "@segment/snippet": "^5.2.1", "@types/qs": "6.9.14", "@upstash/qstash": "^2.7.16", + "@vercel/functions": "^2.0.0", "@vercel/kv": "^2.0.0", "@vercel/otel": "^1.10.0", "@workos-inc/node": "^7.31.0", diff --git a/packages/fern-docs/bundle/src/app/[[...slug]]/page.tsx b/packages/fern-docs/bundle/src/app/[domain]/docs-page.tsx similarity index 91% rename from packages/fern-docs/bundle/src/app/[[...slug]]/page.tsx rename to packages/fern-docs/bundle/src/app/[domain]/docs-page.tsx index 763f1580e0..d5def4a1eb 100644 --- a/packages/fern-docs/bundle/src/app/[[...slug]]/page.tsx +++ b/packages/fern-docs/bundle/src/app/[domain]/docs-page.tsx @@ -9,8 +9,10 @@ import FeedbackPopover from "@/components/feedback/FeedbackPopover"; import { serializeMdx } from "@/components/mdx/bundlers/mdx-bundler"; import { DocsContent } from "@/components/resolver/DocsContent"; import { renderThemeStylesheet } from "@/components/themes/stylesheet/renderThemeStylesheet"; +import { ThemedDocs } from "@/components/themes/ThemedDocs"; import { getApiRouteSupplier } from "@/components/util/getApiRouteSupplier"; import { getGitHubInfo, getGitHubRepo } from "@/components/util/github"; +import { getOrigin } from "@/server/auth/origin"; import { getReturnToQueryParam } from "@/server/auth/return-to"; import { createCachedDocsLoader, DocsLoader } from "@/server/docs-loader"; import { createFileResolver } from "@/server/file-resolver"; @@ -44,13 +46,15 @@ import React from "react"; import urlJoin from "url-join"; import { toImageDescriptor } from "../seo"; -export default async function Page({ +export async function DocsPageComponent({ params, + fern_token, }: { - params: { slug?: string[] }; + params: { slug?: string[]; domain: string }; + fern_token?: string; }) { const slug = FernNavigation.slugjoin(params.slug); - const loader = await createCachedDocsLoader(); + const loader = await createCachedDocsLoader(params.domain, fern_token); const [baseUrl, config, authState, edgeFlags, files, colors] = await Promise.all([ loader.getBaseUrl(), @@ -187,7 +191,7 @@ export default async function Page({ const redirect = new URL(withDefaultProtocol(loader.authConfig.redirect)); redirect.searchParams.set( getReturnToQueryParam(loader.authConfig), - urlJoin(withDefaultProtocol(loader.host), slug) + urlJoin(getOrigin(), slug) ); navbarLinks.push({ @@ -206,11 +210,11 @@ export default async function Page({ if (authState.authed) { const logout = new URL( getApiRoute("/api/fern-docs/auth/logout"), - withDefaultProtocol(loader.host) + withDefaultProtocol(getOrigin()) ); logout.searchParams.set( getReturnToQueryParam(loader.authConfig), - urlJoin(withDefaultProtocol(loader.host), slug) + urlJoin(withDefaultProtocol(getOrigin()), slug) ); navbarLinks.push({ @@ -369,38 +373,43 @@ export default async function Page({ return ( - - - - + + + + + + ); } -export async function generateMetadata({ +export async function generateDocsPageMetadata({ params, + fern_token, }: { - params: { slug?: string[] }; + params: { slug?: string[]; domain: string }; + fern_token?: string; }): Promise { const slug = FernNavigation.slugjoin(params.slug); - const docsLoader = await createCachedDocsLoader(); + const docsLoader = await createCachedDocsLoader(params.domain, fern_token); const findNode = createFindNode(docsLoader); const [files, node, config, isSeoDisabled] = await Promise.all([ docsLoader.getFiles(), diff --git a/packages/fern-docs/bundle/src/app/[domain]/layout.tsx b/packages/fern-docs/bundle/src/app/[domain]/layout.tsx new file mode 100644 index 0000000000..2ff5513b56 --- /dev/null +++ b/packages/fern-docs/bundle/src/app/[domain]/layout.tsx @@ -0,0 +1,190 @@ +"use server"; + +import { CustomerAnalytics } from "@/components/analytics/CustomerAnalytics"; +import Preload, { PreloadHref } from "@/components/preload"; +import { ThemeScript } from "@/components/themes/ThemeScript"; +import { createCachedDocsLoader } from "@/server/docs-loader"; +import { RgbaColor } from "@/server/types"; +import { getDocsDomainApp } from "@/server/xfernhost/app"; +import { DocsV2Read } from "@fern-api/fdr-sdk/client/types"; +import { withDefaultProtocol } from "@fern-api/ui-core-utils"; +import { Toaster } from "@fern-docs/components"; +import { getEdgeFlags } from "@fern-docs/edge-config"; +import { EdgeFlags } from "@fern-docs/utils"; +import { compact, uniqBy } from "es-toolkit/array"; +import { Metadata, Viewport } from "next/types"; +import tinycolor from "tinycolor2"; +import { toImageDescriptor } from "../seo"; + +export default async function Layout({ + children, + params, +}: { + children: React.ReactNode; + params: { + domain: string; + }; +}) { + const docsLoader = await createCachedDocsLoader(params.domain); + const [config, edgeFlags, files, colors] = await Promise.all([ + docsLoader.getConfig(), + getEdgeFlags(params.domain), + docsLoader.getFiles(), + docsLoader.getColors(), + ]); + const preloadHrefs = generatePreloadHrefs( + config?.typographyV2, + files, + edgeFlags + ); + return ( + <> + {preloadHrefs.map((href) => ( + + ))} + + + {children} + + + ); +} + +export async function generateViewport(): Promise { + const domain = getDocsDomainApp(); + const docsLoader = await createCachedDocsLoader(domain); + const colors = await docsLoader.getColors(); + const dark = maybeToHex( + colors.dark?.background ?? colors.dark?.accentPrimary + ); + const light = maybeToHex( + colors.light?.background ?? colors.light?.accentPrimary + ); + return { + themeColor: compact([ + dark ? { color: dark, media: "(prefers-color-scheme: dark)" } : undefined, + light + ? { color: light, media: "(prefers-color-scheme: light)" } + : undefined, + ]), + }; +} + +function maybeToHex(color: RgbaColor | undefined): string | undefined { + if (color == null) { + return undefined; + } + return tinycolor(color).toHexString(); +} + +export async function generateMetadata({ + params, +}: { + params: { + domain: string; + }; +}): Promise { + const docsLoader = await createCachedDocsLoader(params.domain, params.domain); + const [files, config, baseUrl] = await Promise.all([ + docsLoader.getFiles(), + docsLoader.getConfig(), + docsLoader.getBaseUrl(), + ]); + const index = config?.metadata?.noindex ? false : undefined; + const follow = config?.metadata?.nofollow ? false : undefined; + + return { + metadataBase: new URL( + baseUrl.basePath || "/", + withDefaultProtocol(docsLoader.domain) + ), + applicationName: config?.title, + title: { + template: config?.title ? "%s | " + config?.title : "%s", + default: config?.title ?? "Documentation", + }, + robots: { index, follow }, + openGraph: { + title: config?.metadata?.["og:title"], + description: config?.metadata?.["og:description"], + locale: config?.metadata?.["og:locale"], + url: config?.metadata?.["og:url"], + siteName: config?.metadata?.["og:site_name"], + images: toImageDescriptor( + files, + config?.metadata?.["og:image"], + config?.metadata?.["og:image:width"], + config?.metadata?.["og:image:height"] + ), + }, + twitter: { + site: config?.metadata?.["twitter:site"], + creator: config?.metadata?.["twitter:handle"], + title: config?.metadata?.["twitter:title"], + description: config?.metadata?.["twitter:description"], + images: toImageDescriptor(files, config?.metadata?.["twitter:image"]), + }, + icons: { + icon: config?.favicon + ? toImageDescriptor(files, { + type: "fileId", + value: config.favicon, + })?.url + : undefined, + }, + }; +} + +function generatePreloadHrefs( + typography: DocsV2Read.LoadDocsForUrlResponse["definition"]["config"]["typographyV2"], + files: Record, + edgeFlags: EdgeFlags +): PreloadHref[] { + const toReturn: PreloadHref[] = []; + + const fontVariants = compact([ + typography?.bodyFont?.variants, + typography?.headingsFont?.variants, + typography?.codeFont?.variants, + ]).flat(); + + fontVariants.forEach((variant) => { + try { + const file = files[variant.fontFile]; + if (file != null) { + toReturn.push({ + href: file.src, + options: { + as: "font", + crossOrigin: "anonymous", + type: `font/${getFontExtension(file.src)}`, + }, + }); + } + } catch {} + }); + + if (edgeFlags.isApiPlaygroundEnabled) { + toReturn.push({ + href: "/api/fern-docs/auth/api-key-injection", + options: { as: "fetch" }, + }); + } + + toReturn.push({ + href: edgeFlags.isSearchV2Enabled + ? "/api/fern-docs/search/v2/key" + : "/api/fern-docs/search/v1/key", + options: { as: "fetch" }, + }); + + return uniqBy(toReturn, (href) => href.href); +} + +function getFontExtension(url: string): string { + const ext = new URL(url).pathname.split(".").pop(); + if (ext == null) { + throw new Error("No extension found for font: " + url); + } + return ext; +} diff --git a/packages/fern-docs/bundle/src/app/[domain]/~dynamic/[[...slug]]/page.tsx b/packages/fern-docs/bundle/src/app/[domain]/~dynamic/[[...slug]]/page.tsx new file mode 100644 index 0000000000..80a1e08866 --- /dev/null +++ b/packages/fern-docs/bundle/src/app/[domain]/~dynamic/[[...slug]]/page.tsx @@ -0,0 +1,23 @@ +"use server"; + +import { cookies } from "next/headers"; +import { Metadata } from "next/types"; +import { DocsPageComponent, generateDocsPageMetadata } from "../../docs-page"; + +export default async function Page({ + params, +}: { + params: { slug?: string[]; domain: string }; +}) { + const fern_token = cookies().get("fern_token")?.value; + return ; +} + +export async function generateMetadata({ + params, +}: { + params: { slug?: string[]; domain: string }; +}): Promise { + const fern_token = cookies().get("fern_token")?.value; + return generateDocsPageMetadata({ params, fern_token }); +} diff --git a/packages/fern-docs/bundle/src/app/[domain]/~static/[[...slug]]/page.tsx b/packages/fern-docs/bundle/src/app/[domain]/~static/[[...slug]]/page.tsx new file mode 100644 index 0000000000..f041ea7dd6 --- /dev/null +++ b/packages/fern-docs/bundle/src/app/[domain]/~static/[[...slug]]/page.tsx @@ -0,0 +1,20 @@ +"use server"; + +import { Metadata } from "next/types"; +import { DocsPageComponent, generateDocsPageMetadata } from "../../docs-page"; + +export default async function Page({ + params, +}: { + params: { slug?: string[]; domain: string }; +}) { + return ; +} + +export async function generateMetadata({ + params, +}: { + params: { slug?: string[]; domain: string }; +}): Promise { + return generateDocsPageMetadata({ params }); +} diff --git a/packages/fern-docs/bundle/src/app/api/fern-docs/api-definition/[api]/endpoint/[endpoint]/route.ts b/packages/fern-docs/bundle/src/app/api/fern-docs/api-definition/[api]/endpoint/[endpoint]/route.ts index 35632f8e32..9b79adc260 100644 --- a/packages/fern-docs/bundle/src/app/api/fern-docs/api-definition/[api]/endpoint/[endpoint]/route.ts +++ b/packages/fern-docs/bundle/src/app/api/fern-docs/api-definition/[api]/endpoint/[endpoint]/route.ts @@ -1,5 +1,5 @@ import { serializeMdx } from "@/components/mdx/bundlers/mdx-bundler"; -import { getAuthStateEdge } from "@/server/auth/getAuthStateEdge"; +import { createGetAuthStateEdge } from "@/server/auth/getAuthStateEdge"; import * as ApiDefinition from "@fern-api/fdr-sdk/api-definition"; import { ApiDefinitionLoader } from "@fern-docs/cache"; import { getEdgeFlags } from "@fern-docs/edge-config"; @@ -11,7 +11,11 @@ export async function GET( ): Promise { const { api, endpoint } = params; - const authState = await getAuthStateEdge(req); + const { getAuthState, domain } = await createGetAuthStateEdge(req); + const [authState, flags] = await Promise.all([ + getAuthState(), + getEdgeFlags(domain), + ]); if (!authState.ok) { return NextResponse.json( @@ -20,10 +24,8 @@ export async function GET( ); } - const flags = await getEdgeFlags(authState.domain); - const apiDefinition = await ApiDefinitionLoader.create( - authState.domain, + domain, ApiDefinition.ApiDefinitionId(api) ) .withEdgeFlags(flags) diff --git a/packages/fern-docs/bundle/src/app/api/fern-docs/api-definition/[api]/webhook/[webhook]/route.ts b/packages/fern-docs/bundle/src/app/api/fern-docs/api-definition/[api]/webhook/[webhook]/route.ts index ab7614f193..8bddedb2e0 100644 --- a/packages/fern-docs/bundle/src/app/api/fern-docs/api-definition/[api]/webhook/[webhook]/route.ts +++ b/packages/fern-docs/bundle/src/app/api/fern-docs/api-definition/[api]/webhook/[webhook]/route.ts @@ -1,5 +1,5 @@ import { serializeMdx } from "@/components/mdx/bundlers/mdx-bundler"; -import { getAuthStateEdge } from "@/server/auth/getAuthStateEdge"; +import { createGetAuthStateEdge } from "@/server/auth/getAuthStateEdge"; import * as ApiDefinition from "@fern-api/fdr-sdk/api-definition"; import { ApiDefinitionLoader } from "@fern-docs/cache"; import { getEdgeFlags } from "@fern-docs/edge-config"; @@ -11,7 +11,11 @@ export async function GET( ): Promise { const { api, webhook } = params; - const authState = await getAuthStateEdge(req); + const { getAuthState, domain } = await createGetAuthStateEdge(req); + const [authState, flags] = await Promise.all([ + getAuthState(), + getEdgeFlags(domain), + ]); if (!authState.ok) { return NextResponse.json( @@ -20,10 +24,8 @@ export async function GET( ); } - const flags = await getEdgeFlags(authState.domain); - const apiDefinition = await ApiDefinitionLoader.create( - authState.domain, + domain, ApiDefinition.ApiDefinitionId(api) ) .withEdgeFlags(flags) diff --git a/packages/fern-docs/bundle/src/app/api/fern-docs/api-definition/[api]/websocket/[websocket]/route.ts b/packages/fern-docs/bundle/src/app/api/fern-docs/api-definition/[api]/websocket/[websocket]/route.ts index 078d1cb448..f802f65e16 100644 --- a/packages/fern-docs/bundle/src/app/api/fern-docs/api-definition/[api]/websocket/[websocket]/route.ts +++ b/packages/fern-docs/bundle/src/app/api/fern-docs/api-definition/[api]/websocket/[websocket]/route.ts @@ -1,5 +1,5 @@ import { serializeMdx } from "@/components/mdx/bundlers/mdx-bundler"; -import { getAuthStateEdge } from "@/server/auth/getAuthStateEdge"; +import { createGetAuthStateEdge } from "@/server/auth/getAuthStateEdge"; import * as ApiDefinition from "@fern-api/fdr-sdk/api-definition"; import { ApiDefinitionLoader } from "@fern-docs/cache"; import { getEdgeFlags } from "@fern-docs/edge-config"; @@ -11,7 +11,11 @@ export async function GET( ): Promise { const { api, websocket } = params; - const authState = await getAuthStateEdge(req); + const { getAuthState, domain } = await createGetAuthStateEdge(req); + const [authState, flags] = await Promise.all([ + getAuthState(), + getEdgeFlags(domain), + ]); if (!authState.ok) { return NextResponse.json( @@ -20,10 +24,8 @@ export async function GET( ); } - const flags = await getEdgeFlags(authState.domain); - const apiDefinition = await ApiDefinitionLoader.create( - authState.domain, + domain, ApiDefinition.ApiDefinitionId(api) ) .withEdgeFlags(flags) diff --git a/packages/fern-docs/bundle/src/app/api/fern-docs/auth/api-key-injection/route.ts b/packages/fern-docs/bundle/src/app/api/fern-docs/auth/api-key-injection/route.ts index 351f157d9b..683646c8d3 100644 --- a/packages/fern-docs/bundle/src/app/api/fern-docs/auth/api-key-injection/route.ts +++ b/packages/fern-docs/bundle/src/app/api/fern-docs/auth/api-key-injection/route.ts @@ -2,7 +2,7 @@ import { safeVerifyFernJWTConfig } from "@/server/auth/FernJWT"; import { OryOAuth2Client, getOryAuthorizationUrl } from "@/server/auth/ory"; import { getReturnToQueryParam } from "@/server/auth/return-to"; import { withSecureCookie } from "@/server/auth/with-secure-cookie"; -import { fernToken } from "@/server/env-variables"; +import { fernToken_admin } from "@/server/env-variables"; import { getDocsDomainEdge, getHostEdge } from "@/server/xfernhost/edge"; import { withDefaultProtocol } from "@fern-api/ui-core-utils"; import { APIKeyInjectionConfig, OryAccessTokenSchema } from "@fern-docs/auth"; @@ -27,7 +27,7 @@ export async function GET( const returnToQueryParam = getReturnToQueryParam(edgeConfig); - const fern_token = fernToken(); + const fern_token = fernToken_admin(); const access_token = cookieJar.get("access_token")?.value; const refresh_token = cookieJar.get("refresh_token")?.value; const fernUser = await safeVerifyFernJWTConfig(fern_token, edgeConfig); diff --git a/packages/fern-docs/bundle/src/app/api/fern-docs/revalidate/route.ts b/packages/fern-docs/bundle/src/app/api/fern-docs/revalidate/route.ts index c49eba32a1..934bbb58af 100644 --- a/packages/fern-docs/bundle/src/app/api/fern-docs/revalidate/route.ts +++ b/packages/fern-docs/bundle/src/app/api/fern-docs/revalidate/route.ts @@ -16,7 +16,7 @@ export async function GET(req: NextRequest) { revalidateTag(domain); if (req.nextUrl.searchParams.get("regenerate") === "true") { - const docs = await createCachedDocsLoader(); + const docs = await createCachedDocsLoader(domain); const root = await docs.unsafe_getFullRoot(); if (!root) { notFound(); diff --git a/packages/fern-docs/bundle/src/app/api/fern-docs/search/v1/key/route.ts b/packages/fern-docs/bundle/src/app/api/fern-docs/search/v1/key/route.ts index 941533ff40..9cb148117d 100644 --- a/packages/fern-docs/bundle/src/app/api/fern-docs/search/v1/key/route.ts +++ b/packages/fern-docs/bundle/src/app/api/fern-docs/search/v1/key/route.ts @@ -1,4 +1,4 @@ -import { getAuthStateEdge } from "@/server/auth/getAuthStateEdge"; +import { createGetAuthStateEdge } from "@/server/auth/getAuthStateEdge"; import { loadWithUrl } from "@/server/loadWithUrl"; import { provideRegistryService } from "@/server/registry"; import { getInkeepSettings } from "@fern-docs/edge-config"; @@ -10,7 +10,8 @@ export const runtime = "nodejs"; export async function GET( req: NextRequest ): Promise> { - const authState = await getAuthStateEdge(req, req.nextUrl.pathname); + const { getAuthState, domain } = await createGetAuthStateEdge(req); + const authState = await getAuthState(); if (!authState.ok) { return NextResponse.json( @@ -19,13 +20,13 @@ export async function GET( ); } - const docs = await loadWithUrl(authState.domain); + const docs = await loadWithUrl(domain); if (!docs.ok) { return NextResponse.json({ isAvailable: false }, { status: 503 }); } - const inkeepSettings = await getInkeepSettings(authState.domain); + const inkeepSettings = await getInkeepSettings(domain); const searchInfo = docs.body.definition.search; const config = await getSearchConfig( provideRegistryService(), diff --git a/packages/fern-docs/bundle/src/app/api/fern-docs/search/v2/key/route.ts b/packages/fern-docs/bundle/src/app/api/fern-docs/search/v2/key/route.ts index aca73704dc..2e66869c5c 100644 --- a/packages/fern-docs/bundle/src/app/api/fern-docs/search/v2/key/route.ts +++ b/packages/fern-docs/bundle/src/app/api/fern-docs/search/v2/key/route.ts @@ -3,7 +3,7 @@ import { getOrgMetadataForDomain } from "@/server/auth/metadata-for-url"; import { algoliaAppId, algoliaSearchApikey, - fernToken, + fernToken_admin, } from "@/server/env-variables"; import { selectFirst } from "@/server/utils/selectFirst"; import { getDocsDomainEdge } from "@/server/xfernhost/edge"; @@ -34,7 +34,7 @@ export async function GET(req: NextRequest): Promise { }); } - const fern_token = await fernToken(); + const fern_token = await fernToken_admin(); const user = await safeVerifyFernJWTConfig( fern_token, await getAuthEdgeConfig(domain) diff --git a/packages/fern-docs/bundle/src/app/layout.tsx b/packages/fern-docs/bundle/src/app/layout.tsx index bf9173d64f..f17a5ed78c 100644 --- a/packages/fern-docs/bundle/src/app/layout.tsx +++ b/packages/fern-docs/bundle/src/app/layout.tsx @@ -1,29 +1,19 @@ "use server"; -import Preload, { PreloadHref } from "@/components/preload"; -import { createCachedDocsLoader } from "@/server/docs-loader"; -import { RgbaColor } from "@/server/types"; -import { DocsV2Read } from "@fern-api/fdr-sdk/client/types"; +import { getDocsDomainApp } from "@/server/xfernhost/app"; import { withDefaultProtocol } from "@fern-api/ui-core-utils"; import { FernTooltipProvider } from "@fern-docs/components"; -import { getEdgeFlags, getSeoDisabled } from "@fern-docs/edge-config"; -import { EdgeFlags } from "@fern-docs/utils"; -import { compact } from "es-toolkit/array"; +import { getSeoDisabled } from "@fern-docs/edge-config"; import { Provider as JotaiProvider } from "jotai/react"; import { Metadata, Viewport } from "next/types"; -import tinycolor from "tinycolor2"; import "./globals.scss"; import StyledJsxRegistry from "./registry"; -import { toImageDescriptor } from "./seo"; export default async function DashboardLayout({ children, }: { children: React.ReactNode; }) { - const docsLoader = await createCachedDocsLoader(); - const config = await docsLoader.getConfig(); - const edgeFlags = await getEdgeFlags(docsLoader.domain); return ( @@ -32,13 +22,6 @@ export default async function DashboardLayout({ rel="stylesheet" fetchPriority="low" /> - @@ -52,14 +35,6 @@ export default async function DashboardLayout({ } export async function generateViewport(): Promise { - const docsLoader = await createCachedDocsLoader(); - const colors = await docsLoader.getColors(); - const dark = maybeToHex( - colors.dark?.background ?? colors.dark?.accentPrimary - ); - const light = maybeToHex( - colors.light?.background ?? colors.light?.accentPrimary - ); return { width: "device-width", height: "device-height", @@ -67,129 +42,16 @@ export async function generateViewport(): Promise { maximumScale: 1, minimumScale: 1, userScalable: true, - themeColor: compact([ - dark ? { color: dark, media: "(prefers-color-scheme: dark)" } : undefined, - light - ? { color: light, media: "(prefers-color-scheme: light)" } - : undefined, - ]), }; } -function maybeToHex(color: RgbaColor | undefined): string | undefined { - if (color == null) { - return undefined; - } - return tinycolor(color).toHexString(); -} - export async function generateMetadata(): Promise { - const docsLoader = await createCachedDocsLoader(); - const [files, config, baseUrl, isSeoDisabled] = await Promise.all([ - docsLoader.getFiles(), - docsLoader.getConfig(), - docsLoader.getBaseUrl(), - getSeoDisabled(docsLoader.domain), - ]); - const noindex = isSeoDisabled || config?.metadata?.noindex || false; - const nofollow = isSeoDisabled || config?.metadata?.nofollow || false; + const domain = getDocsDomainApp(); + const seoEnabled = !getSeoDisabled(domain); return { generator: "https://buildwithfern.com", - metadataBase: new URL( - baseUrl.basePath || "/", - withDefaultProtocol(docsLoader.domain) - ), - applicationName: config?.title, - title: { - template: config?.title ? "%s | " + config?.title : "%s", - default: config?.title ?? "Documentation", - }, - robots: { - index: !noindex, - follow: !nofollow, - }, - openGraph: { - title: config?.metadata?.["og:title"], - description: config?.metadata?.["og:description"], - locale: config?.metadata?.["og:locale"], - url: config?.metadata?.["og:url"], - siteName: config?.metadata?.["og:site_name"], - images: toImageDescriptor( - files, - config?.metadata?.["og:image"], - config?.metadata?.["og:image:width"], - config?.metadata?.["og:image:height"] - ), - }, - twitter: { - site: config?.metadata?.["twitter:site"], - creator: config?.metadata?.["twitter:handle"], - title: config?.metadata?.["twitter:title"], - description: config?.metadata?.["twitter:description"], - images: toImageDescriptor(files, config?.metadata?.["twitter:image"]), - }, - icons: { - icon: config?.favicon - ? toImageDescriptor(files, { - type: "fileId", - value: config.favicon, - })?.url - : undefined, - }, + metadataBase: new URL("/", withDefaultProtocol(domain)), + robots: { index: seoEnabled, follow: seoEnabled }, }; } - -function generatePreloadHrefs( - typography: DocsV2Read.LoadDocsForUrlResponse["definition"]["config"]["typographyV2"], - files: Record, - edgeFlags: EdgeFlags -): PreloadHref[] { - const toReturn: PreloadHref[] = []; - - const fontVariants = compact([ - typography?.bodyFont?.variants, - typography?.headingsFont?.variants, - typography?.codeFont?.variants, - ]).flat(); - - fontVariants.forEach((variant) => { - try { - const file = files[variant.fontFile]; - if (file != null) { - toReturn.push({ - href: file.src, - options: { - as: "font", - crossOrigin: "anonymous", - type: `font/${getFontExtension(file.src)}`, - }, - }); - } - } catch {} - }); - - if (edgeFlags.isApiPlaygroundEnabled) { - toReturn.push({ - href: "/api/fern-docs/auth/api-key-injection", - options: { as: "fetch" }, - }); - } - - toReturn.push({ - href: edgeFlags.isSearchV2Enabled - ? "/api/fern-docs/search/v2/key" - : "/api/fern-docs/search/v1/key", - options: { as: "fetch" }, - }); - - return toReturn; -} - -function getFontExtension(url: string): string { - const ext = new URL(url).pathname.split(".").pop(); - if (ext == null) { - throw new Error("No extension found for font: " + url); - } - return ext; -} diff --git a/packages/fern-docs/bundle/src/components/analytics/CustomerAnalytics.tsx b/packages/fern-docs/bundle/src/components/analytics/CustomerAnalytics.tsx index 91a62e2fd4..a4151d3156 100644 --- a/packages/fern-docs/bundle/src/components/analytics/CustomerAnalytics.tsx +++ b/packages/fern-docs/bundle/src/components/analytics/CustomerAnalytics.tsx @@ -1,3 +1,5 @@ +"use client"; + import { DocsV1Read } from "@fern-api/fdr-sdk"; import { isEqual } from "es-toolkit/predicate"; import { useAtomValue } from "jotai"; diff --git a/packages/fern-docs/bundle/src/components/analytics/use-track.ts b/packages/fern-docs/bundle/src/components/analytics/use-track.ts index 365316d2f3..8f5f85f0e7 100644 --- a/packages/fern-docs/bundle/src/components/analytics/use-track.ts +++ b/packages/fern-docs/bundle/src/components/analytics/use-track.ts @@ -51,7 +51,7 @@ export function useSafeListenTrackEvents( console.warn("Error emitting track event", error, event); } }; - window.addEventListener(TRACK_EVENT_NAME, handler); + window.addEventListener(TRACK_EVENT_NAME, handler, { passive: true }); return () => window.removeEventListener(TRACK_EVENT_NAME, handler); }, [allowInternal]); } diff --git a/packages/fern-docs/bundle/src/components/atoms/apis.ts b/packages/fern-docs/bundle/src/components/atoms/apis.ts index 14d9b71bf3..7f8581e84d 100644 --- a/packages/fern-docs/bundle/src/components/atoms/apis.ts +++ b/packages/fern-docs/bundle/src/components/atoms/apis.ts @@ -1,13 +1,10 @@ import type * as ApiDefinition from "@fern-api/fdr-sdk/api-definition"; import { joiner } from "@fern-api/fdr-sdk/api-definition"; import type * as FernNavigation from "@fern-api/fdr-sdk/navigation"; -import { atom, useAtomValue, useSetAtom } from "jotai"; +import { atom, useSetAtom } from "jotai"; import { atomFamily } from "jotai/utils"; import { useEffect } from "react"; -import { useMemoOne } from "use-memo-one"; import { useIsLocalPreview } from "../contexts/local-preview"; -import { DOCS_ATOM } from "./docs"; -import { RESOLVED_API_DEFINITION_ATOM } from "./navigation"; const SETTABLE_APIS_ATOM = atom< Record @@ -72,27 +69,17 @@ export function useIsApiReferencePaginated(): boolean { } export function useIsApiReferenceShallowLink( - node: FernNavigation.WithApiDefinitionId + _node: FernNavigation.WithApiDefinitionId ): boolean { - return useAtomValue( - useMemoOne( - () => - atom((get) => { - const isPaginated = true; - const resolvedApi = get(RESOLVED_API_DEFINITION_ATOM); - return !isPaginated && resolvedApi?.id === node.apiDefinitionId; - }), - [node.apiDefinitionId] - ) - ); + return false; } export const ENDPOINT_ID_TO_SLUG_ATOM = atom< Record ->((get) => { - const { content } = get(DOCS_ATOM); - if (content.type === "markdown-page") { - return content.endpointIdsToSlugs; - } +>((_get) => { + // const { content } = get(DOCS_ATOM); + // if (content.type === "markdown-page") { + // return content.endpointIdsToSlugs; + // } return {}; }); diff --git a/packages/fern-docs/bundle/src/components/atoms/navigation.ts b/packages/fern-docs/bundle/src/components/atoms/navigation.ts index 8bf29ae293..4100ea9401 100644 --- a/packages/fern-docs/bundle/src/components/atoms/navigation.ts +++ b/packages/fern-docs/bundle/src/components/atoms/navigation.ts @@ -100,7 +100,7 @@ export const CURRENT_NODE_ATOM = atom((get) => { // this sets the document title to the current node's title when shallow routing // (this will use the navigation node title rather than the page's actual title) if (node && typeof window !== "undefined") { - window.document.title = node.title; + // window.document.title = node.title; } return node; }); diff --git a/packages/fern-docs/bundle/src/components/atoms/viewport.ts b/packages/fern-docs/bundle/src/components/atoms/viewport.ts index 121fd3fcde..73f82d0c06 100644 --- a/packages/fern-docs/bundle/src/components/atoms/viewport.ts +++ b/packages/fern-docs/bundle/src/components/atoms/viewport.ts @@ -42,7 +42,7 @@ VIEWPORT_SIZE_ATOM.onMount = (set) => { set([window.innerWidth, window.innerHeight]); }); }; - window.addEventListener("resize", handleResize); + window.addEventListener("resize", handleResize, { passive: true }); return () => { window.removeEventListener("resize", handleResize); }; diff --git a/packages/fern-docs/bundle/src/components/docs/DocsMainContent.tsx b/packages/fern-docs/bundle/src/components/docs/DocsMainContent.tsx index 247b8443ea..c4b91d6872 100644 --- a/packages/fern-docs/bundle/src/components/docs/DocsMainContent.tsx +++ b/packages/fern-docs/bundle/src/components/docs/DocsMainContent.tsx @@ -8,13 +8,13 @@ import { notFound } from "next/navigation"; // import ApiReferencePage from "../api-reference/ApiReferencePage"; import ChangelogEntryPage from "../changelog/ChangelogEntryPage"; // import ChangelogPage from "../changelog/ChangelogPage"; -import React from "react"; import { LayoutEvaluator } from "../layouts/LayoutEvaluator"; import { serializeMdx } from "../mdx/bundlers/mdx-bundler"; import { FernSerializeMdxOptions } from "../mdx/types"; import type { DocsContent } from "../resolver/DocsContent"; export async function DocsMainContent({ + domain, node, parents, neighbors, @@ -22,6 +22,7 @@ export async function DocsMainContent({ // apiReferenceNodes, scope, }: { + domain: string; node: FernNavigation.NavigationNodePage; // apiReference: FernNavigation.ApiReferenceNode | undefined; parents: readonly FernNavigation.NavigationNodeParent[]; @@ -29,7 +30,7 @@ export async function DocsMainContent({ breadcrumb: readonly FernNavigation.BreadcrumbItem[]; scope?: Record; }) { - const docsLoader = await createCachedDocsLoader(); + const docsLoader = await createCachedDocsLoader(domain); const mdxBundlerFiles = await docsLoader.getMdxBundlerFiles(); const fileResolver = await createFileResolver(docsLoader); const mdxOptions: FernSerializeMdxOptions = { @@ -88,14 +89,12 @@ export async function DocsMainContent({ const mdx = await serializeMdx(content.markdown, mdxOptions); return ( - Loading...}> - - + ); } diff --git a/packages/fern-docs/bundle/src/components/docs/DocsPage.tsx b/packages/fern-docs/bundle/src/components/docs/DocsPage.tsx index 7f9bff2b52..eb375632ab 100644 --- a/packages/fern-docs/bundle/src/components/docs/DocsPage.tsx +++ b/packages/fern-docs/bundle/src/components/docs/DocsPage.tsx @@ -3,15 +3,13 @@ import dynamic from "next/dynamic"; import { usePathname } from "next/navigation"; import { ReactElement, useEffect } from "react"; -import { useEdgeFlag, useMessageHandler, useSetJustNavigated } from "../atoms"; +import { useMessageHandler, useSetJustNavigated } from "../atoms"; import { BgImageGradient } from "../components/BgImageGradient"; import { FernErrorBoundary } from "../components/FernErrorBoundary"; import { JavascriptProvider } from "../components/JavascriptProvider"; -import { LinkPreloadApiRoute } from "../components/LinkPreload"; import { useConsoleMessage } from "../hooks/useConsoleMessage"; import { PlaygroundContextProvider } from "../playground/PlaygroundContext"; import { InitializeTheme } from "../themes"; -import { FernTheme, ThemedDocs } from "../themes/ThemedDocs"; import { scrollToRoute } from "../util/anchor"; const SearchDialog = dynamic( @@ -26,10 +24,8 @@ let timer: number; export function DocsPage({ children, - theme, }: { children: React.ReactNode; - theme: FernTheme; }): ReactElement | null { useConsoleMessage(); useMessageHandler(); @@ -54,27 +50,12 @@ export function DocsPage({ // eslint-disable-next-line react-hooks/exhaustive-deps }, [pathname]); - const isSearchV2Enabled = useEdgeFlag("isSearchV2Enabled"); - const isApiPlaygroundEnabled = useEdgeFlag("isApiPlaygroundEnabled"); - return ( <> - - {isApiPlaygroundEnabled && ( - - )} - - {children} - + {children} diff --git a/packages/fern-docs/bundle/src/components/docs/NextApp.tsx b/packages/fern-docs/bundle/src/components/docs/NextApp.tsx index 1e89438509..1cb40493f6 100644 --- a/packages/fern-docs/bundle/src/components/docs/NextApp.tsx +++ b/packages/fern-docs/bundle/src/components/docs/NextApp.tsx @@ -1,14 +1,10 @@ "use client"; -import { Toaster } from "@fern-docs/components"; import { SyntaxHighlighterEdgeFlagsProvider } from "@fern-docs/syntax-highlighter"; import { ReactElement, ReactNode } from "react"; import { SWRConfig } from "swr"; -import { CustomerAnalytics } from "../analytics/CustomerAnalytics"; import { DocsProps, HydrateAtoms } from "../atoms"; -import { FernErrorBoundary } from "../components/FernErrorBoundary"; import { FeatureFlagProvider } from "../feature-flags/FeatureFlagProvider"; -import { ThemeScript } from "../themes/ThemeScript"; export function NextApp({ children, @@ -20,19 +16,12 @@ export function NextApp({ return ( - - - - - - {children} - - + + {children} + diff --git a/packages/fern-docs/bundle/src/components/hooks/useIsScrolled.ts b/packages/fern-docs/bundle/src/components/hooks/useIsScrolled.ts index f11d98156a..19079705bf 100644 --- a/packages/fern-docs/bundle/src/components/hooks/useIsScrolled.ts +++ b/packages/fern-docs/bundle/src/components/hooks/useIsScrolled.ts @@ -21,13 +21,13 @@ export function useIsScrolled(ref?: RefObject): boolean { if (ref?.current) { const element = ref.current; - ref.current.addEventListener("scroll", handleScroll); + ref.current.addEventListener("scroll", handleScroll, { passive: true }); return () => { element.removeEventListener("scroll", handleScroll); }; } else if (typeof window !== "undefined") { - window.addEventListener("scroll", handleScroll); + window.addEventListener("scroll", handleScroll, { passive: true }); return () => { window.removeEventListener("scroll", handleScroll); diff --git a/packages/fern-docs/bundle/src/components/hooks/useViewportSize.ts b/packages/fern-docs/bundle/src/components/hooks/useViewportSize.ts index 25ada19f97..a5f17970cd 100644 --- a/packages/fern-docs/bundle/src/components/hooks/useViewportSize.ts +++ b/packages/fern-docs/bundle/src/components/hooks/useViewportSize.ts @@ -18,7 +18,7 @@ export function useViewportSize(): { width: number; height: number } { handleResize(); - window.addEventListener("resize", handleResize); + window.addEventListener("resize", handleResize, { passive: true }); return () => { window.removeEventListener("resize", handleResize); }; diff --git a/packages/fern-docs/bundle/src/components/preload.tsx b/packages/fern-docs/bundle/src/components/preload.tsx index 3476072edb..c4bea71340 100644 --- a/packages/fern-docs/bundle/src/components/preload.tsx +++ b/packages/fern-docs/bundle/src/components/preload.tsx @@ -1,5 +1,6 @@ "use client"; +import { useEffect } from "react"; import ReactDOM from "react-dom"; export interface PreloadHref { @@ -7,10 +8,11 @@ export interface PreloadHref { options: ReactDOM.PreloadOptions; } -export default function Preload({ hrefs }: { hrefs: PreloadHref[] }) { - hrefs.forEach((href) => { - ReactDOM.preload(href.href, href.options); - }); +export default function Preload({ href, options }: PreloadHref) { + useEffect(() => { + ReactDOM.preload(href, options); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [href]); return false; } diff --git a/packages/fern-docs/bundle/src/components/themes/ThemeScript.tsx b/packages/fern-docs/bundle/src/components/themes/ThemeScript.tsx index 5df8341af7..d5d3037f02 100644 --- a/packages/fern-docs/bundle/src/components/themes/ThemeScript.tsx +++ b/packages/fern-docs/bundle/src/components/themes/ThemeScript.tsx @@ -1,3 +1,5 @@ +"use client"; + import { ColorsThemeConfig } from "@/server/types"; import Script from "next/script"; import type { ReactElement } from "react"; diff --git a/packages/fern-docs/bundle/src/components/themes/ThemedDocs.tsx b/packages/fern-docs/bundle/src/components/themes/ThemedDocs.tsx index 97587ab744..5f81e48963 100644 --- a/packages/fern-docs/bundle/src/components/themes/ThemedDocs.tsx +++ b/packages/fern-docs/bundle/src/components/themes/ThemedDocs.tsx @@ -1,24 +1,20 @@ -import dynamic from "next/dynamic"; +"use server"; + +import { CohereDocs } from "./cohere/CohereDocs"; +import { DefaultDocs } from "./default/DefaultDocs"; const THEMES = { - default: dynamic( - () => - import("./default/DefaultDocs").then(({ DefaultDocs }) => DefaultDocs), - { ssr: true } - ), - cohere: dynamic( - () => import("./cohere/CohereDocs").then(({ CohereDocs }) => CohereDocs), - { ssr: true } - ), + default: DefaultDocs, + cohere: CohereDocs, }; export type FernTheme = keyof typeof THEMES; -export function ThemedDocs({ - theme, +export async function ThemedDocs({ + theme = "default", children, }: { - theme: FernTheme; + theme: FernTheme | undefined; children: React.ReactNode; }) { const Docs = THEMES[theme]; diff --git a/packages/fern-docs/bundle/src/middleware.ts b/packages/fern-docs/bundle/src/middleware.ts index 61c0fb9512..8c4efd68d9 100644 --- a/packages/fern-docs/bundle/src/middleware.ts +++ b/packages/fern-docs/bundle/src/middleware.ts @@ -3,10 +3,13 @@ import { removeLeadingSlash } from "@fern-docs/utils"; import { NextResponse, type NextMiddleware } from "next/server"; import { MARKDOWN_PATTERN, RSS_PATTERN } from "./server/patterns"; import { withPathname } from "./server/withPathname"; +import { getDocsDomainEdge } from "./server/xfernhost/edge"; const API_FERN_DOCS_PATTERN = /^(?!\/api\/fern-docs\/).*(\/api\/fern-docs\/)/; export const middleware: NextMiddleware = async (request) => { + const domain = getDocsDomainEdge(request); + let pathname = request.nextUrl.pathname; /** @@ -143,7 +146,10 @@ export const middleware: NextMiddleware = async (request) => { ); } - return NextResponse.next({ request: { headers } }); + return NextResponse.rewrite( + withPathname(request, `/${domain}/~static${pathname}`), + { request: { headers } } + ); }; export const config = { diff --git a/packages/fern-docs/bundle/src/server/DocsLoader.ts b/packages/fern-docs/bundle/src/server/DocsLoader.ts index c44a445a48..471e61d0a0 100644 --- a/packages/fern-docs/bundle/src/server/DocsLoader.ts +++ b/packages/fern-docs/bundle/src/server/DocsLoader.ts @@ -7,7 +7,7 @@ import * as FernNavigation from "@fern-api/fdr-sdk/navigation"; import type { AuthEdgeConfig } from "@fern-docs/auth"; import { ApiDefinitionLoader } from "@fern-docs/cache"; import { getAuthEdgeConfig } from "@fern-docs/edge-config"; -import { getAuthState, type AuthState } from "./auth/getAuthState"; +import { createGetAuthState, type AuthState } from "./auth/getAuthState"; import { loadWithUrl } from "./loadWithUrl"; import { pruneWithAuthState } from "./withRbac"; @@ -65,13 +65,12 @@ export class DocsLoader { return [this.authState, this.authConfig]; } return [ - await getAuthState( + await createGetAuthState( this.domain, this.host, this.fernToken, - undefined, this.authConfig - ), + ).then(({ getAuthState }) => getAuthState()), this.authConfig, ]; } diff --git a/packages/fern-docs/bundle/src/server/auth/getAuthState.ts b/packages/fern-docs/bundle/src/server/auth/getAuthState.ts index 7917100fe2..70c7df6004 100644 --- a/packages/fern-docs/bundle/src/server/auth/getAuthState.ts +++ b/packages/fern-docs/bundle/src/server/auth/getAuthState.ts @@ -11,6 +11,7 @@ import urlJoin from "url-join"; import { safeVerifyFernJWTConfig } from "./FernJWT"; import { getAllowedRedirectUrls } from "./allowed-redirects"; import { getOrgMetadataForDomain } from "./metadata-for-url"; +import { getOrigin } from "./origin"; import { getOryAuthorizationUrl } from "./ory"; import { getReturnToQueryParam } from "./return-to"; import { getWebflowAuthorizationUrl } from "./webflow"; @@ -25,11 +26,6 @@ export interface DomainAndHost { */ domain: string; - /** - * The host of the request - */ - host: string; - /** * allowed destinations for redirects */ @@ -79,13 +75,11 @@ export type AuthState = NotLoggedIn | IsLoggedIn; * @internal visible for testing */ export async function getAuthStateInternal({ - host, fernToken, authConfig, previewAuthConfig, setFernToken, }: { - host: string; fernToken: string | undefined; authConfig?: AuthEdgeConfig; previewAuthConfig?: PreviewUrlAuth; @@ -99,7 +93,6 @@ export async function getAuthStateInternal({ handleWorkosAuth({ fernToken, organization: previewAuthConfig.org, - host, pathname, setFernToken, }); @@ -127,7 +120,7 @@ export async function getAuthStateInternal({ return (pathname) => ({ authed: false, ok: true, - authorizationUrl: getAuthorizationUrl(authConfig, host, pathname), + authorizationUrl: getAuthorizationUrl(authConfig, pathname), partner, }); } @@ -139,7 +132,6 @@ export async function getAuthStateInternal({ handleWorkosAuth({ fernToken, organization: authConfig.organization, - host, pathname, setFernToken, authorizationUrl: { @@ -170,7 +162,6 @@ export async function getAuthStateInternal({ */ export async function createGetAuthState( domain: string, - host: string, fernToken: string | undefined, authConfig?: AuthEdgeConfig, setFernToken?: (token: string) => void @@ -187,7 +178,6 @@ export async function createGetAuthState( : undefined; const getAuthState = await getAuthStateInternal({ - host, fernToken, authConfig, setFernToken, @@ -201,7 +191,6 @@ export async function createGetAuthState( return { domain, - host, allowedDestinations, getAuthState, }; @@ -209,13 +198,13 @@ export async function createGetAuthState( function getAuthorizationUrl( authConfig: AuthEdgeConfig, - host: string, pathname?: string ): string | undefined { + const origin = getOrigin(); // TODO: this is currently not a correct implementation of the state parameter b/c it should be signed w/ the jwt secret // however, we should not break existing customers who are consuming the state as a `return_to` param in their auth flows. const state = urlJoin( - removeTrailingSlash(withDefaultProtocol(host)), + removeTrailingSlash(withDefaultProtocol(origin)), pathname ?? "" ); @@ -225,7 +214,7 @@ function getAuthorizationUrl( // note: `redirect` is allowed to override the default redirect uri, and the `return_to` param if (!destination.searchParams.has("redirect_uri")) { const redirectUri = urlJoin( - removeTrailingSlash(withDefaultProtocol(host)), + removeTrailingSlash(origin), "/api/fern-docs/auth/jwt/callback" ); @@ -237,7 +226,7 @@ function getAuthorizationUrl( return destination.toString(); } else if (authConfig.type === "sso" && authConfig.partner === "workos") { const redirectUri = urlJoin( - removeTrailingSlash(withDefaultProtocol(host)), + removeTrailingSlash(origin), "/api/fern-docs/auth/sso/callback" ); return getWorkosSSOAuthorizationUrl({ @@ -254,7 +243,7 @@ function getAuthorizationUrl( return getWebflowAuthorizationUrl(authConfig, { state, redirectUri: urlJoin( - removeTrailingSlash(withDefaultProtocol(host)), + removeTrailingSlash(origin), "/api/fern-docs/oauth/webflow/callback" ), }); @@ -262,7 +251,7 @@ function getAuthorizationUrl( return getOryAuthorizationUrl(authConfig, { state, redirectUri: urlJoin( - removeTrailingSlash(withDefaultProtocol(host)), + removeTrailingSlash(origin), "/api/fern-docs/oauth/ory/callback" ), }); diff --git a/packages/fern-docs/bundle/src/server/auth/getAuthStateEdge.ts b/packages/fern-docs/bundle/src/server/auth/getAuthStateEdge.ts index 72033bb654..d99e9fee96 100644 --- a/packages/fern-docs/bundle/src/server/auth/getAuthStateEdge.ts +++ b/packages/fern-docs/bundle/src/server/auth/getAuthStateEdge.ts @@ -1,26 +1,18 @@ import { COOKIE_FERN_TOKEN } from "@fern-docs/utils"; import { NextRequest } from "next/server"; import { getDocsDomainEdge, getHostEdge } from "../xfernhost/edge"; -import { getAuthState } from "./getAuthState"; +import { createGetAuthState } from "./getAuthState"; /** * @param request - the request to check the headers / cookies * @param pathname - the pathname to check the auth config against. The pathname MUST be provided in the middleware. */ -export async function getAuthStateEdge( +export async function createGetAuthStateEdge( request: NextRequest, - pathname?: string, setFernToken?: (token: string) => void -): ReturnType { +): ReturnType { const domain = getDocsDomainEdge(request); const host = getHostEdge(request); const fern_token = request.cookies.get(COOKIE_FERN_TOKEN)?.value; - return getAuthState( - domain, - host, - fern_token, - pathname, - undefined, - setFernToken - ); + return createGetAuthState(domain, host, fern_token, undefined, setFernToken); } diff --git a/packages/fern-docs/bundle/src/server/auth/getAuthStateNode.ts b/packages/fern-docs/bundle/src/server/auth/getAuthStateNode.ts deleted file mode 100644 index 922a5d9ce6..0000000000 --- a/packages/fern-docs/bundle/src/server/auth/getAuthStateNode.ts +++ /dev/null @@ -1,18 +0,0 @@ -import { COOKIE_FERN_TOKEN } from "@fern-docs/utils"; -import { NextApiRequest } from "next"; -import { getDocsDomainNode, getHostNode } from "../xfernhost/node"; -import { getAuthState } from "./getAuthState"; - -/** - * @param request - the request to check the headers / cookies - * @param pathname - the pathname to check the auth config against. - */ -export async function getAuthStateNode( - request: NextApiRequest, - pathname?: string -): ReturnType { - const domain = getDocsDomainNode(request); - const host = getHostNode(request) ?? domain; - const fern_token = request.cookies[COOKIE_FERN_TOKEN]; - return getAuthState(domain, host, fern_token, pathname); -} diff --git a/packages/fern-docs/bundle/src/server/auth/origin.ts b/packages/fern-docs/bundle/src/server/auth/origin.ts new file mode 100644 index 0000000000..0649f744a3 --- /dev/null +++ b/packages/fern-docs/bundle/src/server/auth/origin.ts @@ -0,0 +1,13 @@ +import { withDefaultProtocol } from "@fern-api/ui-core-utils"; +import { getEnv } from "@vercel/functions"; +import { headers } from "next/headers"; + +export function getOrigin() { + const { VERCEL_ENV } = getEnv(); + return withDefaultProtocol( + (VERCEL_ENV === "preview" ? headers().get("host") : undefined) || + headers().get("x-fern-host") || + headers().get("host") || + "" + ); +} diff --git a/packages/fern-docs/bundle/src/server/auth/workos-handler.ts b/packages/fern-docs/bundle/src/server/auth/workos-handler.ts index f57fa1ab55..52955f3518 100644 --- a/packages/fern-docs/bundle/src/server/auth/workos-handler.ts +++ b/packages/fern-docs/bundle/src/server/auth/workos-handler.ts @@ -1,7 +1,7 @@ -import { withDefaultProtocol } from "@fern-api/ui-core-utils"; import { removeTrailingSlash } from "@fern-docs/utils"; import urlJoin from "url-join"; import { AuthState, getWorkosRbacRoles } from "./getAuthState"; +import { getOrigin } from "./origin"; import { getWorkosSSOAuthorizationUrl } from "./workos"; import { encryptSession, @@ -14,7 +14,6 @@ import { toFernUser } from "./workos-user-to-fern-user"; interface WorkosAuthParams { fernToken: string | undefined; organization: string; - host: string; pathname?: string; setFernToken?: (token: string) => void; authorizationUrl?: { @@ -28,15 +27,12 @@ interface WorkosAuthParams { export async function handleWorkosAuth({ fernToken, organization, - host, pathname, setFernToken, authorizationUrl, }: WorkosAuthParams): Promise { - const state = urlJoin( - removeTrailingSlash(withDefaultProtocol(host)), - pathname ?? "" - ); + const origin = getOrigin(); + const state = urlJoin(removeTrailingSlash(origin), pathname ?? ""); const session = fernToken != null ? await getSessionFromToken(fernToken) : undefined; const workosUserInfo = await toSessionUserInfo(session); @@ -74,10 +70,7 @@ export async function handleWorkosAuth({ } const redirectUri = String( - new URL( - "/api/fern-docs/auth/sso/callback", - withDefaultProtocol(process.env.NEXT_PUBLIC_CDN_URI ?? host) - ) + new URL("/api/fern-docs/auth/sso/callback", origin) ); const authorizationUrlParams = getWorkosSSOAuthorizationUrl({ redirectUri, diff --git a/packages/fern-docs/bundle/src/server/docs-loader.ts b/packages/fern-docs/bundle/src/server/docs-loader.ts index b32a28f025..77e355e681 100644 --- a/packages/fern-docs/bundle/src/server/docs-loader.ts +++ b/packages/fern-docs/bundle/src/server/docs-loader.ts @@ -17,10 +17,8 @@ import { AuthState, createGetAuthState } from "./auth/getAuthState"; import { loadWithUrl } from "./loadWithUrl"; import { ColorsThemeConfig, FileData, RgbaColor } from "./types"; import { pruneWithAuthState } from "./withRbac"; -import { withServerProps } from "./withServerProps"; export interface DocsLoader { - host: string; domain: string; fern_token: string | undefined; authConfig: AuthEdgeConfig | undefined; @@ -101,28 +99,22 @@ export interface DocsLoader { /** * Force cache the loadWithUrl call, so that `JSON.parse()` is called only once. */ -export const createCachedDocsLoader = async (): Promise => { - const { domain, host, fern_token } = withServerProps(); +export const createCachedDocsLoader = async ( + domain: string, + fern_token?: string +): Promise => { const authConfig = await getAuthEdgeConfig(domain); const { getAuthState } = await createGetAuthState( domain, - host, fern_token, authConfig ); - return new CachedDocsLoaderImpl( - domain, - host, - fern_token, - authConfig, - getAuthState - ); + return new CachedDocsLoaderImpl(domain, fern_token, authConfig, getAuthState); }; class CachedDocsLoaderImpl implements DocsLoader { constructor( private _domain: string, - private _host: string, private _fern_token: string | undefined, private _authConfig: AuthEdgeConfig | undefined, private _getAuthState: (pathname?: string) => AsyncOrSync @@ -132,10 +124,6 @@ class CachedDocsLoaderImpl implements DocsLoader { return this._domain; } - public get host() { - return this._host; - } - public get fern_token() { return this._fern_token; } diff --git a/packages/fern-docs/bundle/src/server/env-variables.ts b/packages/fern-docs/bundle/src/server/env-variables.ts index b832731f50..93ed837b9d 100644 --- a/packages/fern-docs/bundle/src/server/env-variables.ts +++ b/packages/fern-docs/bundle/src/server/env-variables.ts @@ -10,7 +10,7 @@ export function algoliaSearchApikey(): string { return getEnvVariable("ALGOLIA_SEARCH_API_KEY"); } -export function fernToken(): string { +export function fernToken_admin(): string { return getEnvVariable("FERN_TOKEN"); } diff --git a/packages/fern-docs/bundle/src/server/registry.ts b/packages/fern-docs/bundle/src/server/registry.ts index 017689a0ab..78787617f5 100644 --- a/packages/fern-docs/bundle/src/server/registry.ts +++ b/packages/fern-docs/bundle/src/server/registry.ts @@ -1,6 +1,6 @@ import { FdrClient } from "@fern-api/fdr-sdk/client"; import { once } from "es-toolkit/function"; -import { fernToken } from "./env-variables"; +import { fernToken_admin } from "./env-variables"; function getEnvironment() { return ( @@ -9,5 +9,6 @@ function getEnvironment() { } export const provideRegistryService = once( - () => new FdrClient({ environment: getEnvironment(), token: fernToken() }) + () => + new FdrClient({ environment: getEnvironment(), token: fernToken_admin() }) ); diff --git a/packages/fern-docs/bundle/src/server/withMiddlewareAuth.ts b/packages/fern-docs/bundle/src/server/withMiddlewareAuth.ts index edf15d3d1a..de937590b2 100644 --- a/packages/fern-docs/bundle/src/server/withMiddlewareAuth.ts +++ b/packages/fern-docs/bundle/src/server/withMiddlewareAuth.ts @@ -3,7 +3,7 @@ import { FernUser } from "@fern-docs/auth"; import { COOKIE_FERN_TOKEN } from "@fern-docs/utils"; import { NextRequest, NextResponse } from "next/server"; import { FernNextResponse } from "./FernNextResponse"; -import { getAuthStateEdge } from "./auth/getAuthStateEdge"; +import { createGetAuthStateEdge } from "./auth/getAuthStateEdge"; import { withSecureCookie } from "./auth/with-secure-cookie"; import { getHostEdge } from "./xfernhost/edge"; @@ -18,9 +18,14 @@ export async function withMiddlewareAuth( next: (isLoggedIn: boolean, user: FernUser | undefined) => NextResponse ): Promise { let fernToken: string | undefined; - const res = await getAuthStateEdge(request, pathname, (token: string) => { - fernToken = token; - }); + const { getAuthState, allowedDestinations } = await createGetAuthStateEdge( + request, + (token: string) => { + fernToken = token; + } + ); + + const res = await getAuthState(pathname); if (res.authed) { const response = next(true, res.user); @@ -44,7 +49,7 @@ export async function withMiddlewareAuth( if (res.authorizationUrl) { return FernNextResponse.redirect(request, { destination: res.authorizationUrl, - allowedDestinations: res.allowedDestinations, + allowedDestinations, }); } diff --git a/packages/fern-docs/components/src/FernToast.tsx b/packages/fern-docs/components/src/FernToast.tsx index 56739f5a46..ec9ae1af96 100644 --- a/packages/fern-docs/components/src/FernToast.tsx +++ b/packages/fern-docs/components/src/FernToast.tsx @@ -1,3 +1,5 @@ +"use client"; + import { CheckCircle, InfoCircle, diff --git a/packages/fern-docs/search-ui/src/server/env-variables.ts b/packages/fern-docs/search-ui/src/server/env-variables.ts index b817efafdf..48ceb74e48 100644 --- a/packages/fern-docs/search-ui/src/server/env-variables.ts +++ b/packages/fern-docs/search-ui/src/server/env-variables.ts @@ -12,7 +12,7 @@ export function algoliaSearchApikey(): string { return getEnvVariable("ALGOLIA_SEARCH_API_KEY"); } -export function fernToken(): string { +export function fernToken_admin(): string { return getEnvVariable("FERN_TOKEN"); } diff --git a/packages/fern-docs/search-ui/src/server/run-reindex-algolia.ts b/packages/fern-docs/search-ui/src/server/run-reindex-algolia.ts index 1f85de6c2c..adb243e72f 100644 --- a/packages/fern-docs/search-ui/src/server/run-reindex-algolia.ts +++ b/packages/fern-docs/search-ui/src/server/run-reindex-algolia.ts @@ -8,13 +8,12 @@ import { algoliaAppId, algoliaWriteApiKey, fdrEnvironment, - fernToken, + fernToken_admin, } from "./env-variables"; export const runReindexAlgolia = async ( domain: string ): Promise => { - // eslint-disable-next-line no-console console.time("reindexing"); await algoliaIndexSettingsTask({ @@ -28,11 +27,10 @@ export const runReindexAlgolia = async ( writeApiKey: algoliaWriteApiKey(), indexName: SEARCH_INDEX, environment: fdrEnvironment(), - fernToken: fernToken(), + fernToken: fernToken_admin(), domain, }); - // eslint-disable-next-line no-console console.timeEnd("reindexing"); return response; diff --git a/packages/fern-docs/search-ui/src/server/run-reindex-turbopuffer.ts b/packages/fern-docs/search-ui/src/server/run-reindex-turbopuffer.ts index f9f57b6416..8a8a2cc1b1 100644 --- a/packages/fern-docs/search-ui/src/server/run-reindex-turbopuffer.ts +++ b/packages/fern-docs/search-ui/src/server/run-reindex-turbopuffer.ts @@ -5,7 +5,11 @@ import { turbopufferUpsertTask, } from "@fern-docs/search-server/turbopuffer"; import { embed, embedMany } from "ai"; -import { fdrEnvironment, fernToken, turbopufferApiKey } from "./env-variables"; +import { + fdrEnvironment, + fernToken_admin, + turbopufferApiKey, +} from "./env-variables"; const openai = createOpenAI({ apiKey: process.env.OPENAI_API_KEY, @@ -21,7 +25,7 @@ export const runReindexTurbopuffer = async ( namespace: `${domain}_${model.modelId}`, payload: { environment: fdrEnvironment(), - fernToken: fernToken(), + fernToken: fernToken_admin(), domain, }, vectorizer: async (chunks) => { diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index d25b917a09..1bc63a63c4 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -13,7 +13,7 @@ overrides: elliptic: 6.6.0 esbuild: 0.24.2 eslint: 9.17.0 - eslint-config-next: 15.1.2 + eslint-config-next: 14.2.23 instantsearch.js: 4.75.4 jsonpath-plus: 10.0.7 markdown-to-jsx: 7.4.0 @@ -109,8 +109,8 @@ importers: specifier: 9.17.0 version: 9.17.0(jiti@1.21.7) eslint-config-next: - specifier: 15.1.2 - version: 15.1.2(eslint@9.17.0(jiti@1.21.7))(typescript@5.7.2) + specifier: 14.2.23 + version: 14.2.23(eslint@9.17.0(jiti@1.21.7))(typescript@5.7.2) eslint-config-prettier: specifier: ^9.1.0 version: 9.1.0(eslint@9.17.0(jiti@1.21.7)) @@ -834,6 +834,9 @@ importers: '@upstash/qstash': specifier: ^2.7.16 version: 2.7.16 + '@vercel/functions': + specifier: ^2.0.0 + version: 2.0.0(@aws-sdk/credential-provider-web-identity@3.723.0(@aws-sdk/client-sts@3.682.0)) '@vercel/kv': specifier: ^2.0.0 version: 2.0.0 @@ -5153,8 +5156,8 @@ packages: '@next/env@15.1.2': resolution: {integrity: sha512-Hm3jIGsoUl6RLB1vzY+dZeqb+/kWPZ+h34yiWxW0dV87l8Im/eMOwpOA+a0L78U0HM04syEjXuRlCozqpwuojQ==} - '@next/eslint-plugin-next@15.1.2': - resolution: {integrity: sha512-sgfw3+WdaYOGPKCvM1L+UucBmRfh8V2Ygefp7ELON0+0vY7uohQwXXnVWg3rY7mXDKharQR3o7uedpfvnU2hlQ==} + '@next/eslint-plugin-next@14.2.23': + resolution: {integrity: sha512-efRC7m39GoiU1fXZRgGySqYbQi6ZyLkuGlvGst7IwkTTczehQTJA/7PoMg4MMjUZvZEGpiSEu+oJBAjPawiC3Q==} '@next/swc-darwin-arm64@14.2.23': resolution: {integrity: sha512-WhtEntt6NcbABA8ypEoFd3uzq5iAnrl9AnZt9dXdO+PZLACE32z3a3qA5OoV20JrbJfSJ6Sd6EqGZTrlRnGxQQ==} @@ -7945,6 +7948,15 @@ packages: '@aws-sdk/credential-provider-web-identity': optional: true + '@vercel/functions@2.0.0': + resolution: {integrity: sha512-BSwIihLHoV18gerKZJyGuqd3rtaYM6rJvET1kOwKktshucyaHXTJel7Cxegs+sdX0NZqsX4LO2MFnMU2jG01Cw==} + engines: {node: '>= 18'} + peerDependencies: + '@aws-sdk/credential-provider-web-identity': '*' + peerDependenciesMeta: + '@aws-sdk/credential-provider-web-identity': + optional: true + '@vercel/kv@2.0.0': resolution: {integrity: sha512-zdVrhbzZBYo5d1Hfn4bKtqCeKf0FuzW8rSHauzQVMUgv1+1JOwof2mWcBuI+YMJy8s0G0oqAUfQ7HgUDzb8EbA==} engines: {node: '>=14.6'} @@ -10435,8 +10447,8 @@ packages: resolution: {integrity: sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw==} engines: {node: '>=12'} - eslint-config-next@15.1.2: - resolution: {integrity: sha512-PrMm1/4zWSJ689wd/ypWIR5ZF1uvmp3EkgpgBV1Yu6PhEobBjXMGgT8bVNelwl17LXojO8D5ePFRiI4qXjsPRA==} + eslint-config-next@14.2.23: + resolution: {integrity: sha512-qtWJzOsDZxnLtXLNtnVjbutHmnEp6QTTSZBTlTCge/Wy0AsUaq8nwR91dBcZZvFg3eY3zKFPBhUkLMHu3Qpauw==} peerDependencies: eslint: 9.17.0 typescript: 5.7.2 @@ -10514,6 +10526,12 @@ packages: peerDependencies: eslint: 9.17.0 + eslint-plugin-react-hooks@5.0.0-canary-7118f5dd7-20230705: + resolution: {integrity: sha512-AZYbMo/NW9chdL7vk6HQzQhT+PvTAEVqWk9ziruUoW2kAOcN5qNyelv70e0F1VNQAbvutOC9oc+xfWycI9FxDw==} + engines: {node: '>=10'} + peerDependencies: + eslint: 9.17.0 + eslint-plugin-react-hooks@5.1.0: resolution: {integrity: sha512-mpJRtPgHN2tNAvZ35AMfqeB3Xqeo273QxrHJsbBEPWODRM4r0yB6jfoROqKEYrOn27UtRPpcpHc2UqyBSuUNTw==} engines: {node: '>=10'} @@ -10765,10 +10783,6 @@ packages: fast-fifo@1.3.2: resolution: {integrity: sha512-/d9sfos4yxzpwkDkuN7k2SqFKtYNmCTzgfEpz82x34IM9/zc8KGxQoXg1liNC/izpRM/MBdt44Nmx41ZWqk+FQ==} - fast-glob@3.3.1: - resolution: {integrity: sha512-kNFPyjhh5cKjrUltxs+wFx+ZkbRaxxmZ+X0ZU31SOsxCEtP9VPgtq2teZw1DebupL5GmDaNQ6yKMMVcM41iqDg==} - engines: {node: '>=8.6.0'} - fast-glob@3.3.2: resolution: {integrity: sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow==} engines: {node: '>=8.6.0'} @@ -11187,6 +11201,11 @@ packages: glob-to-regexp@0.4.1: resolution: {integrity: sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw==} + glob@10.3.10: + resolution: {integrity: sha512-fa46+tv1Ak0UPK1TOy/pZrIybNNt4HCv7SDzwyfiOZkvZLEbjsZkJBPtDHVshZjbecAoAGSC20MjLDG/qr679g==} + engines: {node: '>=16 || 14 >=14.17'} + hasBin: true + glob@10.3.14: resolution: {integrity: sha512-4fkAqu93xe9Mk7le9v0y3VrPDqLKHarNi2s4Pv7f2yOvfhWfhc7hRPHC/JyqMqb8B/Dt/eGS4n7ykwf3fOsl8g==} engines: {node: '>=16 || 14 >=14.17'} @@ -20869,9 +20888,9 @@ snapshots: '@next/env@15.1.2': {} - '@next/eslint-plugin-next@15.1.2': + '@next/eslint-plugin-next@14.2.23': dependencies: - fast-glob: 3.3.1 + glob: 10.3.10 '@next/swc-darwin-arm64@14.2.23': optional: true @@ -24506,6 +24525,10 @@ snapshots: optionalDependencies: '@aws-sdk/credential-provider-web-identity': 3.723.0(@aws-sdk/client-sts@3.682.0) + '@vercel/functions@2.0.0(@aws-sdk/credential-provider-web-identity@3.723.0(@aws-sdk/client-sts@3.682.0))': + optionalDependencies: + '@aws-sdk/credential-provider-web-identity': 3.723.0(@aws-sdk/client-sts@3.682.0) + '@vercel/kv@2.0.0': dependencies: '@upstash/redis': 1.34.0 @@ -28101,9 +28124,9 @@ snapshots: escape-string-regexp@5.0.0: {} - eslint-config-next@15.1.2(eslint@9.17.0(jiti@1.21.7))(typescript@5.7.2): + eslint-config-next@14.2.23(eslint@9.17.0(jiti@1.21.7))(typescript@5.7.2): dependencies: - '@next/eslint-plugin-next': 15.1.2 + '@next/eslint-plugin-next': 14.2.23 '@rushstack/eslint-patch': 1.10.4 '@typescript-eslint/eslint-plugin': 8.18.1(@typescript-eslint/parser@8.18.1(eslint@9.17.0(jiti@1.21.7))(typescript@5.7.2))(eslint@9.17.0(jiti@1.21.7))(typescript@5.7.2) '@typescript-eslint/parser': 8.18.1(eslint@9.17.0(jiti@1.21.7))(typescript@5.7.2) @@ -28113,7 +28136,7 @@ snapshots: eslint-plugin-import: 2.31.0(@typescript-eslint/parser@8.18.1(eslint@9.17.0(jiti@1.21.7))(typescript@5.7.2))(eslint-import-resolver-typescript@3.7.0)(eslint@9.17.0(jiti@1.21.7)) eslint-plugin-jsx-a11y: 6.10.2(eslint@9.17.0(jiti@1.21.7)) eslint-plugin-react: 7.37.2(eslint@9.17.0(jiti@1.21.7)) - eslint-plugin-react-hooks: 5.1.0(eslint@9.17.0(jiti@1.21.7)) + eslint-plugin-react-hooks: 5.0.0-canary-7118f5dd7-20230705(eslint@9.17.0(jiti@1.21.7)) optionalDependencies: typescript: 5.7.2 transitivePeerDependencies: @@ -28133,7 +28156,7 @@ snapshots: eslint-import-resolver-node@0.3.9: dependencies: debug: 3.2.7 - is-core-module: 2.13.1 + is-core-module: 2.16.0 resolve: 1.22.8 transitivePeerDependencies: - supports-color @@ -28223,6 +28246,10 @@ snapshots: safe-regex-test: 1.1.0 string.prototype.includes: 2.0.1 + eslint-plugin-react-hooks@5.0.0-canary-7118f5dd7-20230705(eslint@9.17.0(jiti@1.21.7)): + dependencies: + eslint: 9.17.0(jiti@1.21.7) + eslint-plugin-react-hooks@5.1.0(eslint@9.17.0(jiti@1.21.7)): dependencies: eslint: 9.17.0(jiti@1.21.7) @@ -28589,14 +28616,6 @@ snapshots: fast-fifo@1.3.2: {} - fast-glob@3.3.1: - dependencies: - '@nodelib/fs.stat': 2.0.5 - '@nodelib/fs.walk': 1.2.8 - glob-parent: 5.1.2 - merge2: 1.4.1 - micromatch: 4.0.8 - fast-glob@3.3.2: dependencies: '@nodelib/fs.stat': 2.0.5 @@ -29036,6 +29055,14 @@ snapshots: glob-to-regexp@0.4.1: {} + glob@10.3.10: + dependencies: + foreground-child: 3.1.1 + jackspeak: 2.3.6 + minimatch: 9.0.4 + minipass: 7.1.1 + path-scurry: 1.11.0 + glob@10.3.14: dependencies: foreground-child: 3.1.1