diff --git a/.github/workflows/publish-fdr-sdk.yml b/.github/workflows/publish-fdr-sdk.yml index 5307b290e1..04947b39cf 100644 --- a/.github/workflows/publish-fdr-sdk.yml +++ b/.github/workflows/publish-fdr-sdk.yml @@ -19,7 +19,7 @@ jobs: - uses: actions/checkout@v4 with: fetch-depth: 0 - ref: ${{ github.event.inputs.branch || "main" }} + ref: ${{ github.event.inputs.branch }} - name: πŸ“₯ Install uses: ./.github/actions/install @@ -40,7 +40,7 @@ jobs: - uses: actions/checkout@v4 with: fetch-depth: 0 - ref: ${{ github.event.inputs.branch || "main" }} + ref: ${{ github.event.inputs.branch }} - name: πŸ“₯ Install uses: ./.github/actions/install diff --git a/.github/workflows/test-fern-bot.yml b/.github/workflows/test-fern-bot.yml index 18d901f07b..10db25dfac 100644 --- a/.github/workflows/test-fern-bot.yml +++ b/.github/workflows/test-fern-bot.yml @@ -26,8 +26,21 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 + - name: πŸ“₯ Install uses: ./.github/actions/install + + - name: Codegen + run: | + npm install -g fern-api + + - name: Compile and build + run: pnpm turbo compile codegen build + env: + FERN_TOKEN: ${{ secrets.FERN_TOKEN }} + WORKOS_API_KEY: ${{ secrets.WORKOS_API_KEY }} + WORKOS_CLIENT_ID: ${{ secrets.WORKOS_CLIENT_ID }} + - name: πŸš€ serverless function run-thru env: CI: false diff --git a/clis/docs-migrator/src/index.ts b/clis/docs-migrator/src/index.ts index c5cd6484f9..a2fe763fa6 100644 --- a/clis/docs-migrator/src/index.ts +++ b/clis/docs-migrator/src/index.ts @@ -136,17 +136,18 @@ export class MigrateFromMintlify { ); // create openapi folder - if (mint.openapi != null && mint.openapi.length > 0) { + if (mint.openapi != null) { const openapiDir = path.join(fernDir, "openapi"); fs.mkdirSync(openapiDir, { recursive: true }); - if (mint.openapi.length > 1) { + const firstOpenapi = typeof mint.openapi === "string" ? mint.openapi : mint.openapi[0]; + if (firstOpenapi != null) { + const openapiFilePath = path.join(this.dir, firstOpenapi); + const newOpenapiFilePath = path.join(openapiDir, `openapi${path.extname(firstOpenapi)}`); + await fs.promises.copyFile(openapiFilePath, newOpenapiFilePath); + } + if (typeof mint.openapi !== "string" && mint.openapi.length > 1) { console.warn("Multiple OpenAPI files are not supported yet in this migrator."); } - - const openapi = mint.openapi[0]; - const openapiFilePath = path.join(this.dir, openapi); - const newOpenapiFilePath = path.join(openapiDir, `openapi${path.extname(openapi)}`); - await fs.promises.copyFile(openapiFilePath, newOpenapiFilePath); } } diff --git a/packages/commons/github/src/createOrUpdatePullRequest.ts b/packages/commons/github/src/createOrUpdatePullRequest.ts index 39094c549e..a48fda5601 100644 --- a/packages/commons/github/src/createOrUpdatePullRequest.ts +++ b/packages/commons/github/src/createOrUpdatePullRequest.ts @@ -15,6 +15,9 @@ interface RepoMetadata { function parseRepository(repository: string): RepoMetadata { const [owner, repo] = repository.split("/"); + if (owner == null || repo == null) { + throw new Error(`Failed to parse repository into owner and repo: ${repository}`); + } return { owner, repo, @@ -74,9 +77,15 @@ export async function createOrUpdatePullRequest( base: inputs.base, }); console.log("Attempting update of pull request"); + + const pullNumber = pulls[0]?.number; + if (pullNumber == null) { + throw new Error(`Failed to retrieve pull request number: ${JSON.stringify(pulls)}`); + } + const { data: pull } = await octokit.rest.pulls.update({ ...parseRepository(baseRepository), - pull_number: pulls[0].number, + pull_number: pullNumber, title: inputs.title, body: inputs.body, }); diff --git a/packages/commons/github/src/getLatestTag.ts b/packages/commons/github/src/getLatestTag.ts index 398fd772b8..030c4dc0e9 100644 --- a/packages/commons/github/src/getLatestTag.ts +++ b/packages/commons/github/src/getLatestTag.ts @@ -15,5 +15,5 @@ export async function getLatestTag(githubRepository: string): Promise = {}; Object.keys(object).forEach((key) => { - result[key] = iteratee(object[key], key, object); + const val = object[key]; + if (val == null) { + return; // Should this be a hard failure? + } + result[key] = iteratee(val, key, object); }); return result; } diff --git a/packages/ui/app/src/api-page/endpoints/EndpointContentCodeSnippets.tsx b/packages/ui/app/src/api-page/endpoints/EndpointContentCodeSnippets.tsx index d41847717b..3f99a657f0 100644 --- a/packages/ui/app/src/api-page/endpoints/EndpointContentCodeSnippets.tsx +++ b/packages/ui/app/src/api-page/endpoints/EndpointContentCodeSnippets.tsx @@ -72,7 +72,9 @@ const UnmemoizedEndpointContentCodeSnippets: React.FC(null); useResizeObserver(ref, ([entry]) => { - measureHeight(entry.contentRect.height); + if (entry != null) { + measureHeight(entry.contentRect.height); + } }); const [internalSelectedErrorExample, setSelectedErrorExample] = useState( diff --git a/packages/ui/app/src/api-page/endpoints/ErrorExampleSelect.tsx b/packages/ui/app/src/api-page/endpoints/ErrorExampleSelect.tsx index cff7a58bb8..446cad8a5c 100644 --- a/packages/ui/app/src/api-page/endpoints/ErrorExampleSelect.tsx +++ b/packages/ui/app/src/api-page/endpoints/ErrorExampleSelect.tsx @@ -27,7 +27,7 @@ export const ErrorExampleSelect: FC> children, }) => { const handleValueChange = (value: string) => { - const [errorIndex, exampleIndex] = value.split(":").map((v) => parseInt(v, 10)); + const [errorIndex = 0, exampleIndex = 0] = value.split(":").map((v) => parseInt(v, 10)); setSelectedErrorAndExample(errors[errorIndex], errors[errorIndex]?.examples[exampleIndex]); }; const selectedErrorIndex = selectedError != null ? errors.indexOf(selectedError) : -1; diff --git a/packages/ui/app/src/api-page/examples/CodeSnippetExample.tsx b/packages/ui/app/src/api-page/examples/CodeSnippetExample.tsx index 06f1c222b2..198fc97f40 100644 --- a/packages/ui/app/src/api-page/examples/CodeSnippetExample.tsx +++ b/packages/ui/app/src/api-page/examples/CodeSnippetExample.tsx @@ -40,7 +40,9 @@ const CodeSnippetExampleInternal: FC = ({ const viewportRef = createRef(); useResizeObserver(codeBlockRef, ([entry]) => { - measureHeight?.(entry.contentRect.height); + if (entry != null) { + measureHeight?.(entry.contentRect.height); + } }); const requestHighlightLines = useMemo(() => { diff --git a/packages/ui/app/src/api-page/types/type-shorthand/TypeShorthand.tsx b/packages/ui/app/src/api-page/types/type-shorthand/TypeShorthand.tsx index 3fca11e1e9..eb38d1f4ac 100644 --- a/packages/ui/app/src/api-page/types/type-shorthand/TypeShorthand.tsx +++ b/packages/ui/app/src/api-page/types/type-shorthand/TypeShorthand.tsx @@ -1,9 +1,9 @@ import { APIV1Read } from "@fern-api/fdr-sdk"; import { visitDiscriminatedUnion } from "@fern-ui/core-utils"; import clsx from "clsx"; -import numeral from "numeral"; import { ReactNode } from "react"; import { + DereferencedTypeShape, ResolvedTypeDefinition, ResolvedTypeShape, unwrapAlias, @@ -25,10 +25,7 @@ export function renderTypeShorthandRoot( ): ReactNode { const typeShorthand = renderTypeShorthand(unwrapOptional(shape, types), { nullable: isResponse }, types); const unaliasedShape = unwrapAlias(shape, types); - const defaultsTo = - unaliasedShape.type === "optional" && unaliasedShape.shape.type === "primitive" - ? renderDefaultTo(unaliasedShape.shape.value) - : null; + const defaultsTo = renderDefaultsTo(unaliasedShape); return ( {typeShorthand} @@ -37,30 +34,46 @@ export function renderTypeShorthandRoot( ) : !isResponse ? ( Required ) : null} - {defaultsTo != null && {`Defaults to ${defaultsTo}`}} + {defaultsTo != null && ( + + {"Defaults to "} + {defaultsTo} + + )} ); } -function renderDefaultTo(shape: APIV1Read.PrimitiveType): string | undefined { +function renderDefaultsTo(shape: DereferencedTypeShape): string | undefined { + if (shape.type !== "optional") { + return undefined; + } + + if (shape.shape.type === "primitive") { + return renderDefaultToPrimitive(shape.shape.value); + } + + if (shape.shape.type === "enum") { + return shape.shape.default; + } + + return undefined; +} + +function renderDefaultToPrimitive(shape: APIV1Read.PrimitiveType): string | undefined { return visitDiscriminatedUnion(shape, "type")._visit({ - string: (string) => string.default, - integer: (integer) => { - if (integer.default == null) { - return undefined; - } - return numeral(integer.default).format("0,0"); - }, - double: (double) => double.default?.toString(), + string: (value) => value.default, + integer: (value) => value.default?.toString(), + double: (value) => value.default?.toString(), uint: () => undefined, uint64: () => undefined, - boolean: () => undefined, - long: () => undefined, - datetime: () => undefined, - uuid: () => undefined, - base64: () => undefined, - date: () => undefined, - bigInteger: () => undefined, + boolean: (value) => value.default?.toString(), + long: (value) => value.default?.toString(), + datetime: (datetime) => datetime.default, + uuid: (uuid) => uuid.default, + base64: (base64) => base64.default, + date: (value) => value.default, + bigInteger: (value) => value.default, _other: () => undefined, }); } diff --git a/packages/ui/app/src/api-playground/PlaygroundAuthorizationForm.tsx b/packages/ui/app/src/api-playground/PlaygroundAuthorizationForm.tsx index 5738d98be3..7b2fee53a0 100644 --- a/packages/ui/app/src/api-playground/PlaygroundAuthorizationForm.tsx +++ b/packages/ui/app/src/api-playground/PlaygroundAuthorizationForm.tsx @@ -157,7 +157,9 @@ function HeaderAuthForm({ header, disabled }: { header: APIV1Read.HeaderAuth; di headers: { ...headers, [header.headerWireValue]: - typeof change === "function" ? change(headers[header.headerWireValue]) : change, + typeof change === "function" + ? change(headers[header.headerWireValue] ?? "") + : change, }, })); }, @@ -405,7 +407,7 @@ function isAuthed(auth: APIV1Read.ApiAuth, authState: PlaygroundAuthState): bool bearerAuth: () => !isEmpty(authState.bearerAuth?.token.trim()), basicAuth: () => !isEmpty(authState.basicAuth?.username.trim()) && !isEmpty(authState.basicAuth?.password.trim()), - header: (header) => !isEmpty(authState.header?.headers[header.headerWireValue].trim()), + header: (header) => !isEmpty(authState.header?.headers[header.headerWireValue]?.trim()), _other: () => false, }); } diff --git a/packages/ui/app/src/api-playground/PlaygroundEndpointDescription.tsx b/packages/ui/app/src/api-playground/PlaygroundEndpointDescription.tsx index a4aa3b7e18..79a4897dac 100644 --- a/packages/ui/app/src/api-playground/PlaygroundEndpointDescription.tsx +++ b/packages/ui/app/src/api-playground/PlaygroundEndpointDescription.tsx @@ -18,7 +18,7 @@ export function PlaygroundEndpointDescription({ endpoint }: PlaygroundEndpointDe const [descriptionIsClamped, setDescriptionIsClamped] = useState(false); useResizeObserver(descriptionRef, ([entry]) => { - if (!showFullDescription) { + if (!showFullDescription && entry != null) { setDescriptionIsClamped(entry.target.scrollHeight > entry.target.clientHeight); } }); diff --git a/packages/ui/app/src/api-playground/PlaygroundEndpointSelectorContent.tsx b/packages/ui/app/src/api-playground/PlaygroundEndpointSelectorContent.tsx index 09297d5bb0..df100c5412 100644 --- a/packages/ui/app/src/api-playground/PlaygroundEndpointSelectorContent.tsx +++ b/packages/ui/app/src/api-playground/PlaygroundEndpointSelectorContent.tsx @@ -1,6 +1,6 @@ import { FernNavigation } from "@fern-api/fdr-sdk"; import { FernButton, FernInput, FernScrollArea, FernTooltip, FernTooltipProvider } from "@fern-ui/components"; -import { isNonNullish } from "@fern-ui/core-utils"; +import { EMPTY_ARRAY, isNonNullish } from "@fern-ui/core-utils"; import { Cross1Icon, MagnifyingGlassIcon, SlashIcon } from "@radix-ui/react-icons"; import cn, { clsx } from "clsx"; import dynamic from "next/dynamic"; @@ -77,7 +77,7 @@ export function flattenApiSection(root: FernNavigation.SidebarRootNode | undefin const allBreadcrumbs = result.map((group) => group.breadcrumbs); const sharedBreadcrumbs = allBreadcrumbs.reduce((acc, breadcrumbs) => { return acc.filter((breadcrumb, idx) => breadcrumb === breadcrumbs[idx]); - }, allBreadcrumbs[0]); + }, allBreadcrumbs[0] ?? EMPTY_ARRAY); return result.map((group) => ({ ...group, diff --git a/packages/ui/app/src/api-playground/form/PlaygroundMapForm.tsx b/packages/ui/app/src/api-playground/form/PlaygroundMapForm.tsx index c16200766a..f182519026 100644 --- a/packages/ui/app/src/api-playground/form/PlaygroundMapForm.tsx +++ b/packages/ui/app/src/api-playground/form/PlaygroundMapForm.tsx @@ -52,16 +52,22 @@ export const PlaygroundMapForm = memo((props) => { const handleChangeKey = useCallback((idx: number, newKey: unknown) => { setInternalState((oldState) => { - const nextState = { ...oldState[idx] }; - nextState.key = typeof newKey === "function" ? newKey(nextState.key) : newKey; + const nextState = { + key: oldState[idx]?.key ?? "", + value: oldState[idx]?.value ?? "", + }; + nextState.key = (typeof newKey === "function" ? newKey(nextState.key) : newKey) ?? ""; return [...oldState.slice(0, idx), nextState, ...oldState.slice(idx + 1)]; }); }, []); const handleChangeValue = useCallback((idx: number, newValue: unknown) => { setInternalState((oldState) => { - const nextState = { ...oldState[idx] }; - nextState.value = typeof newValue === "function" ? newValue(nextState.value) : newValue; + const nextState = { + key: oldState[idx]?.key ?? "", + value: oldState[idx]?.value ?? "", + }; + nextState.value = (typeof newValue === "function" ? newValue(nextState.value) : newValue) ?? ""; return [...oldState.slice(0, idx), nextState, ...oldState.slice(idx + 1)]; }); }, []); diff --git a/packages/ui/app/src/atoms/sidebar.ts b/packages/ui/app/src/atoms/sidebar.ts index 7964ce56b7..a41e7011e9 100644 --- a/packages/ui/app/src/atoms/sidebar.ts +++ b/packages/ui/app/src/atoms/sidebar.ts @@ -75,8 +75,10 @@ export const SIDEBAR_DISMISSABLE_ATOM = atom((get) => { // this is useful for tabs that only have one page if ( sidebar.children.length === 1 && + sidebar.children[0] != null && sidebar.children[0].type === "sidebarGroup" && sidebar.children[0].children.length === 1 && + sidebar.children[0].children[0] != null && sidebar.children[0].children[0].type === "page" ) { return true; diff --git a/packages/ui/app/src/docs/ChangelogEntryPage.tsx b/packages/ui/app/src/docs/ChangelogEntryPage.tsx index d21a98973b..174c966347 100644 --- a/packages/ui/app/src/docs/ChangelogEntryPage.tsx +++ b/packages/ui/app/src/docs/ChangelogEntryPage.tsx @@ -8,7 +8,7 @@ import { ResolvedPath } from "../resolver/ResolvedPath"; export function ChangelogEntryPage({ resolvedPath }: { resolvedPath: ResolvedPath.ChangelogEntryPage }): ReactElement { const page = resolvedPath.pages[resolvedPath.node.pageId]; const title = typeof page !== "string" ? page?.frontmatter.title : undefined; - const excerpt = typeof page !== "string" ? page?.frontmatter.subtitle ?? page.frontmatter.excerpt : undefined; + const excerpt = typeof page !== "string" ? page?.frontmatter.subtitle ?? page?.frontmatter.excerpt : undefined; return (
@@ -38,9 +38,12 @@ export function ChangelogEntryPage({ resolvedPath }: { resolvedPath: ResolvedPat
)} -
- -
+ {/* TODO: alert if the page is null */} + {page != null && ( +
+ +
+ )}
diff --git a/packages/ui/app/src/docs/ChangelogPage.tsx b/packages/ui/app/src/docs/ChangelogPage.tsx index c9b33650d7..888b49a593 100644 --- a/packages/ui/app/src/docs/ChangelogPage.tsx +++ b/packages/ui/app/src/docs/ChangelogPage.tsx @@ -73,7 +73,8 @@ export function ChangelogPage({ resolvedPath }: { resolvedPath: ResolvedPath.Cha
{title != null &&

{title}

} - + {/* TODO: alert if the page is null */} + {page != null && }
@@ -85,7 +86,8 @@ export function ChangelogPage({ resolvedPath }: { resolvedPath: ResolvedPath.Cha
{title != null &&

{title}

} - + {/* TODO: alert if the page is null */} + {page != null && }
diff --git a/packages/ui/app/src/docs/HeaderLogoSection.tsx b/packages/ui/app/src/docs/HeaderLogoSection.tsx index 27a3270339..eeed93d8ab 100644 --- a/packages/ui/app/src/docs/HeaderLogoSection.tsx +++ b/packages/ui/app/src/docs/HeaderLogoSection.tsx @@ -1,7 +1,7 @@ import cn from "clsx"; import { useAtomValue } from "jotai"; import { PropsWithChildren, ReactElement } from "react"; -import { LOGO_HREF_ATOM, LOGO_TEXT_ATOM, VERSIONS_ATOM, useColors, useFile, useLogoHeight } from "../atoms"; +import { DOCS_ATOM, LOGO_HREF_ATOM, LOGO_TEXT_ATOM, VERSIONS_ATOM, useColors, useFile, useLogoHeight } from "../atoms"; import { FernImage } from "../components/FernImage"; import { FernLink } from "../components/FernLink"; import { VersionDropdown } from "./VersionDropdown"; @@ -40,11 +40,14 @@ function FernLogoImage(): ReactElement | null { const colors = useColors(); const logoImageHeight = useLogoHeight(); const imageClassName = "max-h-full object-contain"; + const title = useAtomValue(DOCS_ATOM).title ?? "Logo"; + if (colors.dark != null && colors.light != null) { return ( <> {colors.light.logo != null && ( return element.type === Image; } -export const Image: FC = ({ className, src, width: w, height: h, noZoom, enableZoom, style, ...rest }) => { +export const Image: FC = ({ src, width: w, height: h, noZoom, enableZoom, style, ...rest }) => { const files = useAtomValue(FILES_ATOM); const { "no-image-zoom": noImageZoom } = useFrontmatter(); diff --git a/packages/ui/app/src/mdx/components/snippets/utils.tsx b/packages/ui/app/src/mdx/components/snippets/utils.tsx index e9fddec834..7651c584d7 100644 --- a/packages/ui/app/src/mdx/components/snippets/utils.tsx +++ b/packages/ui/app/src/mdx/components/snippets/utils.tsx @@ -10,7 +10,7 @@ export function useSelectedClient( ): [CodeExample | undefined, (nextClient: CodeExample) => void] { const [selectedLanguage, setSelectedLanguage] = useAtom(FERN_LANGUAGE_ATOM); const client = clients.find((c) => c.language === selectedLanguage) ?? clients[0]; - const selectedClient = exampleName ? client.examples.find((e) => e.name === exampleName) : client.examples[0]; + const selectedClient = exampleName ? client?.examples.find((e) => e.name === exampleName) : client?.examples[0]; const handleClickClient = useCallback( (nextClient: CodeExample) => { diff --git a/packages/ui/app/src/mdx/plugins/rehypeSanitizeJSX.ts b/packages/ui/app/src/mdx/plugins/rehypeSanitizeJSX.ts index 67fabec84d..1838effb3c 100644 --- a/packages/ui/app/src/mdx/plugins/rehypeSanitizeJSX.ts +++ b/packages/ui/app/src/mdx/plugins/rehypeSanitizeJSX.ts @@ -41,11 +41,11 @@ export function rehypeSanitizeJSX({ showErrors = false }: { showErrors?: boolean !SUPPORTED_JSX_TAGS.includes(esnode.openingElement.name.name) ) { const ancestor = ancestors[ancestors.length - 1]; - if (ancestor.type === "ExpressionStatement") { + if (ancestor?.type === "ExpressionStatement") { ancestor.expression = jsxFragment(); return ESTREE_SKIP; } - if (ancestor.type === "JSXFragment" && i != null) { + if (ancestor?.type === "JSXFragment" && i != null) { ancestor.children[i] = jsxFragment(); } } diff --git a/packages/ui/app/src/next-app/utils/getBreadcrumbList.ts b/packages/ui/app/src/next-app/utils/getBreadcrumbList.ts index af821aa9a6..4e08e10eb3 100644 --- a/packages/ui/app/src/next-app/utils/getBreadcrumbList.ts +++ b/packages/ui/app/src/next-app/utils/getBreadcrumbList.ts @@ -17,24 +17,27 @@ export function getBreadcrumbList( if (FernNavigation.isPage(node)) { const pageId = FernNavigation.utils.getPageId(node); - if (pageId != null && pages[pageId] != null) { - const { data: frontmatter } = getFrontmatter(pages[pageId].markdown); + if (pageId != null) { + const page = pages[pageId]; + if (page != null) { + const { data: frontmatter } = getFrontmatter(page.markdown); - // if the frontmatter has a jsonld:breadcrumb, use that - if (frontmatter["jsonld:breadcrumb"] != null) { - const breadcrumb = JsonLd.BreadcrumbListSchema.safeParse(frontmatter["jsonld:breadcrumb"]); - if (breadcrumb.success) { - return breadcrumb.data; - } else { - // eslint-disable-next-line no-console - console.error("Invalid jsonld:breadcrumb", breadcrumb.error.toString()); + // if the frontmatter has a jsonld:breadcrumb, use that + if (frontmatter["jsonld:breadcrumb"] != null) { + const breadcrumb = JsonLd.BreadcrumbListSchema.safeParse(frontmatter["jsonld:breadcrumb"]); + if (breadcrumb.success) { + return breadcrumb.data; + } else { + // eslint-disable-next-line no-console + console.error("Invalid jsonld:breadcrumb", breadcrumb.error.toString()); + } } - } - // override the title used in the breadcrumb's last item. - // for example, if the sidebar's title is "Overview" but the page title is "This API Overview" - if (frontmatter.title != null) { - title = frontmatter.title; + // override the title used in the breadcrumb's last item. + // for example, if the sidebar's title is "Overview" but the page title is "This API Overview" + if (frontmatter.title != null) { + title = frontmatter.title; + } } } } diff --git a/packages/ui/app/src/next-app/utils/getSeoProp.ts b/packages/ui/app/src/next-app/utils/getSeoProp.ts index 8d91232d1a..f9471a73e5 100644 --- a/packages/ui/app/src/next-app/utils/getSeoProp.ts +++ b/packages/ui/app/src/next-app/utils/getSeoProp.ts @@ -1,5 +1,5 @@ import { APIV1Read, DocsV1Read, FernNavigation } from "@fern-api/fdr-sdk"; -import { visitDiscriminatedUnion } from "@fern-ui/core-utils"; +import { assertNonNullish, visitDiscriminatedUnion } from "@fern-ui/core-utils"; import type { LinkTag, MetaTag, NextSeoProps } from "@fern-ui/next-seo"; import { trim } from "lodash-es"; import { fromMarkdown } from "mdast-util-from-markdown"; @@ -12,7 +12,11 @@ import { getBreadcrumbList } from "./getBreadcrumbList"; function getFile(fileOrUrl: DocsV1Read.FileIdOrUrl, files: Record): DocsV1Read.File_ { return visitDiscriminatedUnion(fileOrUrl)._visit({ - fileId: ({ value: fileId }) => files[fileId], + fileId: ({ value: fileId }) => { + const file = files[fileId]; + assertNonNullish(file, `File with id ${fileId} not found`); + return file; + }, url: ({ value: url }) => ({ type: "url", url }), }); } @@ -54,45 +58,51 @@ export function getSeoProps( let ogMetadata: DocsV1Read.MetadataConfig = metadata ?? {}; - if (pageId != null && pages[pageId]) { - const { data: frontmatter } = getFrontmatter(pages[pageId].markdown); - ogMetadata = { ...ogMetadata, ...frontmatter }; + if (pageId != null) { + const page = pages[pageId]; + if (page != null) { + const { data: frontmatter } = getFrontmatter(page.markdown); + ogMetadata = { ...ogMetadata, ...frontmatter }; - // retrofit og:image - if (frontmatter.image != null) { - ogMetadata["og:image"] ??= { - type: "url", - value: FernNavigation.utils.convertRelativeToAbsoluteUrl(domain, node.slug, frontmatter.image), - }; - } + // retrofit og:image + if (frontmatter.image != null) { + ogMetadata["og:image"] ??= { + type: "url", + value: FernNavigation.utils.convertRelativeToAbsoluteUrl(domain, node.slug, frontmatter.image), + }; + } - seo.title ??= frontmatter.title; - seo.description ??= frontmatter.description ?? frontmatter.subtitle ?? frontmatter.excerpt; + seo.title ??= frontmatter.title; + seo.description ??= frontmatter.description ?? frontmatter.subtitle ?? frontmatter.excerpt; + } } if (FernNavigation.isApiLeaf(node) && apis[node.apiDefinitionId] != null) { - const api = FernNavigation.ApiDefinitionHolder.create(apis[node.apiDefinitionId]); - - visitDiscriminatedUnion(node)._visit({ - endpoint: ({ endpointId }) => { - const endpoint = api.endpoints.get(endpointId); - if (endpoint?.description != null) { - seo.description ??= endpoint.description; - } - }, - webSocket: ({ webSocketId }) => { - const webSocket = api.webSockets.get(webSocketId); - if (webSocket?.description != null) { - seo.description ??= webSocket.description; - } - }, - webhook: ({ webhookId }) => { - const webhook = api.webhooks.get(webhookId); - if (webhook?.description != null) { - seo.description ??= webhook.description; - } - }, - }); + const definition = apis[node.apiDefinitionId]; + if (definition != null) { + const api = FernNavigation.ApiDefinitionHolder.create(definition); + + visitDiscriminatedUnion(node)._visit({ + endpoint: ({ endpointId }) => { + const endpoint = api.endpoints.get(endpointId); + if (endpoint?.description != null) { + seo.description ??= endpoint.description; + } + }, + webSocket: ({ webSocketId }) => { + const webSocket = api.webSockets.get(webSocketId); + if (webSocket?.description != null) { + seo.description ??= webSocket.description; + } + }, + webhook: ({ webhookId }) => { + const webhook = api.webhooks.get(webhookId); + if (webhook?.description != null) { + seo.description ??= webhook.description; + } + }, + }); + } } if (seo.title != null && stringHasMarkdown(seo.title)) { @@ -171,7 +181,8 @@ export function getSeoProps( } if (favicon != null && files[favicon] != null) { - const image = files[favicon]; + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + const image = files[favicon]!; additionalLinkTags.push({ rel: "icon", href: image.url, diff --git a/packages/ui/app/src/resolver/ApiDefinitionResolver.ts b/packages/ui/app/src/resolver/ApiDefinitionResolver.ts index 96506f6e43..19d2d75640 100644 --- a/packages/ui/app/src/resolver/ApiDefinitionResolver.ts +++ b/packages/ui/app/src/resolver/ApiDefinitionResolver.ts @@ -133,24 +133,28 @@ export class ApiDefinitionResolver { if (node.overviewPageId != null && this.pages[node.overviewPageId] != null) { const pageContent = this.pages[node.overviewPageId]; - items.unshift({ - type: "page", - id: node.overviewPageId, - slug: node.slug, - title: node.title, - markdown: await serializeMdx(pageContent.markdown, { - ...this.mdxOptions, - filename: node.overviewPageId, - frontmatterDefaults: { - title: node.title, - breadcrumbs: [], // TODO: implement breadcrumbs - "edit-this-page-url": pageContent.editThisPageUrl, - "hide-nav-links": true, - layout: "reference", - "force-toc": this.featureFlags.isTocDefaultEnabled, - }, - }), - }); + if (pageContent != null) { + items.unshift({ + type: "page", + id: node.overviewPageId, + slug: node.slug, + title: node.title, + markdown: await serializeMdx(pageContent.markdown, { + ...this.mdxOptions, + filename: node.overviewPageId, + frontmatterDefaults: { + title: node.title, + breadcrumbs: [], // TODO: implement breadcrumbs + "edit-this-page-url": pageContent.editThisPageUrl, + "hide-nav-links": true, + layout: "reference", + "force-toc": this.featureFlags.isTocDefaultEnabled, + }, + }), + }); + } else { + // TODO: alert if the page is null + } } return { diff --git a/packages/ui/app/src/resolver/ApiTypeResolver.ts b/packages/ui/app/src/resolver/ApiTypeResolver.ts index 399f293f6f..6d1b173085 100644 --- a/packages/ui/app/src/resolver/ApiTypeResolver.ts +++ b/packages/ui/app/src/resolver/ApiTypeResolver.ts @@ -69,6 +69,7 @@ export class ApiTypeResolver { files: this.mdxOptions?.files, }), availability, + default: enum_.default, }), undiscriminatedUnion: async (undiscriminatedUnion) => ({ type: "undiscriminatedUnion", diff --git a/packages/ui/app/src/resolver/resolveCodeSnippets.ts b/packages/ui/app/src/resolver/resolveCodeSnippets.ts index 6209db623d..bf61dd172f 100644 --- a/packages/ui/app/src/resolver/resolveCodeSnippets.ts +++ b/packages/ui/app/src/resolver/resolveCodeSnippets.ts @@ -129,13 +129,19 @@ export async function resolveCodeSnippets( } } - const code = await snippet.convert(targetId, clientId); + const convertedCode = await snippet.convert(targetId, clientId); + const code = + typeof convertedCode === "string" + ? convertedCode + : convertedCode != null + ? convertedCode[0] + : undefined; if (code != null) { toRet.push({ name: alwaysEnableJavaScriptFetch ? "HTTP Request" : undefined, language: targetId, install: undefined, - code: typeof code === "string" ? code : code[0], + code, generated: true, }); } diff --git a/packages/ui/app/src/resolver/types.ts b/packages/ui/app/src/resolver/types.ts index b7caf67bb2..030a2b14a5 100644 --- a/packages/ui/app/src/resolver/types.ts +++ b/packages/ui/app/src/resolver/types.ts @@ -528,6 +528,7 @@ interface ResolvedEnumShape extends WithMetadata { name: string | undefined; type: "enum"; values: ResolvedEnumValue[]; + default: string | undefined; } export interface ResolvedEnumValue extends WithMetadata { diff --git a/packages/ui/app/src/syntax-highlighting/fernShiki.ts b/packages/ui/app/src/syntax-highlighting/fernShiki.ts index 0a2f2bfa44..3e547274b0 100644 --- a/packages/ui/app/src/syntax-highlighting/fernShiki.ts +++ b/packages/ui/app/src/syntax-highlighting/fernShiki.ts @@ -28,7 +28,14 @@ export const getHighlighterInstance: (language: string) => Promise highlighter = await highlighterPromise; if (!highlighter.getLoadedLanguages().includes(lang)) { - await highlighter.loadLanguage(additionalLanguages[lang] ?? lang); + try { + await highlighter.loadLanguage( + additionalLanguages[lang] ?? (lang as BundledLanguage | SpecialLanguage), + ); + } catch (e) { + // eslint-disable-next-line no-console + console.error(`Failed to load language: ${lang}`, e); + } } return highlighter; diff --git a/packages/ui/app/src/themes/cohere/CohereDocs.tsx b/packages/ui/app/src/themes/cohere/CohereDocs.tsx index 4cbf295dae..e87127f831 100644 --- a/packages/ui/app/src/themes/cohere/CohereDocs.tsx +++ b/packages/ui/app/src/themes/cohere/CohereDocs.tsx @@ -48,7 +48,9 @@ function UnmemoizedCohereDocs(): ReactElement { const setContentHeight = useSetAtom(CONTENT_HEIGHT_ATOM); useResizeObserver(mainRef, ([entry]) => { - setContentHeight(entry.contentRect.height); + if (entry != null) { + setContentHeight(entry.contentRect.height); + } }); useEffect(() => { diff --git a/packages/ui/app/src/util/convertNavigatableToResolvedPath.ts b/packages/ui/app/src/util/convertNavigatableToResolvedPath.ts index 6f4327117c..b586be5ac0 100644 --- a/packages/ui/app/src/util/convertNavigatableToResolvedPath.ts +++ b/packages/ui/app/src/util/convertNavigatableToResolvedPath.ts @@ -222,9 +222,26 @@ async function resolveMarkdownPage( } const resolvedApis = Object.fromEntries( await Promise.all( - apiNodes.map(async (apiNode): Promise<[string, ResolvedRootPackage]> => { - const holder = FernNavigation.ApiDefinitionHolder.create(apis[apiNode.apiDefinitionId]); - const typeResolver = new ApiTypeResolver(apis[apiNode.apiDefinitionId].types, mdxOptions); + apiNodes.map(async (apiNode): Promise<[title: string, ResolvedRootPackage]> => { + const definition = apis[apiNode.apiDefinitionId]; + if (definition == null) { + // eslint-disable-next-line no-console + console.error("API not found", apiNode.apiDefinitionId); + return [ + apiNode.title, + { + // TODO: alert if the API is not found β€” this is a bug + type: "rootPackage", + api: apiNode.apiDefinitionId, + auth: undefined, + types: {}, + items: [], + slug: FernNavigation.Slug(""), + }, + ]; + } + const holder = FernNavigation.ApiDefinitionHolder.create(definition); + const typeResolver = new ApiTypeResolver(definition.types, mdxOptions); return [ apiNode.title, await ApiDefinitionResolver.resolve( diff --git a/packages/ui/app/src/util/generateRadixColors.ts b/packages/ui/app/src/util/generateRadixColors.ts index bdb5d0b1a9..9bd2c480be 100644 --- a/packages/ui/app/src/util/generateRadixColors.ts +++ b/packages/ui/app/src/util/generateRadixColors.ts @@ -1,3 +1,4 @@ +/* eslint-disable @typescript-eslint/no-non-null-assertion */ // adapted from: https://github.com/radix-ui/website/blob/main/components/generateRadixColors.tsx import * as RadixColors from "@radix-ui/colors"; import BezierEasing from "bezier-easing"; @@ -222,7 +223,7 @@ export function getClosestGrayScale(source: string): (typeof grayScaleNames)[num allColors.sort((a, b) => a.distance - b.distance); - return allColors[0].scale as (typeof grayScaleNames)[number]; + return allColors[0]!.scale as (typeof grayScaleNames)[number]; } catch (e) { // eslint-disable-next-line no-console console.error(e); @@ -253,14 +254,14 @@ function getScaleFromColor(source: Color, scales: Record grayScaleNamesStr.includes(color.scale)); - if (!allAreGrays && grayScaleNamesStr.includes(closestColors[0].scale)) { - while (grayScaleNamesStr.includes(closestColors[1].scale)) { + if (!allAreGrays && grayScaleNamesStr.includes(closestColors[0]!.scale)) { + while (grayScaleNamesStr.includes(closestColors[1]!.scale)) { closestColors.splice(1, 1); } } - const colorA = closestColors[0]; - const colorB = closestColors[1]; + const colorA = closestColors[0]!; + const colorB = closestColors[1]!; // Light trigonometry ahead. // @@ -328,14 +329,14 @@ function getScaleFromColor(source: Color, scales: Record new Color(Color.mix(scaleA[i], scaleB[i], ratio)).to("oklch"), ) as ArrayOf12; // Get the closest color from the pre-mixed scale we created - const baseColor = scale.slice().sort((a, b) => source.deltaEOK(a) - source.deltaEOK(b))[0]; + const baseColor = scale.slice().sort((a, b) => source.deltaEOK(a) - source.deltaEOK(b))[0]!; // Note the chroma difference between the source color and the base color const ratioC = source.coords[1] / baseColor.coords[1]; @@ -361,7 +362,7 @@ function getScaleFromColor(source: Color, scales: Record { - scale[i].coords[0] = lightness; + scale[i]!.coords[0] = lightness; }); return scale; @@ -380,7 +381,7 @@ function getScaleFromColor(source: Color, scales: Record maxRatio ? 0 : Math.max(0, ease[i] * (1 - metaRatio)); + ease[i] = ratioL > maxRatio ? 0 : Math.max(0, ease[i]! * (1 - metaRatio)); } } @@ -389,7 +390,7 @@ function getScaleFromColor(source: Color, scales: Record { - scale[i].coords[0] = lightness; + scale[i]!.coords[0] = lightness; }); return scale; @@ -589,7 +590,7 @@ export function transposeProgressionStart( ): number[] { return arr.map((n, i, arr) => { const lastIndex = arr.length - 1; - const diff = arr[0] - to; + const diff = arr[0]! - to; const fn = BezierEasing(...curve); return n - diff * fn(1 - i / lastIndex); }); @@ -598,7 +599,7 @@ export function transposeProgressionStart( export function transposeProgressionEnd(to: number, arr: number[], curve: [number, number, number, number]): number[] { return arr.map((n, i, arr) => { const lastIndex = arr.length - 1; - const diff = arr[lastIndex] - to; + const diff = arr[lastIndex]! - to; const fn = BezierEasing(...curve); return n - diff * fn(i / lastIndex); }); diff --git a/packages/ui/docs-bundle/src/pages/api/fern-docs/changelog.ts b/packages/ui/docs-bundle/src/pages/api/fern-docs/changelog.ts index a81d590689..beb648917e 100644 --- a/packages/ui/docs-bundle/src/pages/api/fern-docs/changelog.ts +++ b/packages/ui/docs-bundle/src/pages/api/fern-docs/changelog.ts @@ -101,7 +101,7 @@ function toFeedItem( date: new Date(entry.date), }; - const markdown = pages[entry.pageId].markdown; + const markdown = pages[entry.pageId]?.markdown; if (markdown != null) { const { data: frontmatter, content } = getFrontmatter(markdown); item.description = frontmatter.description ?? frontmatter.subtitle ?? frontmatter.excerpt; diff --git a/packages/ui/docs-bundle/src/utils/getDocsPageProps.ts b/packages/ui/docs-bundle/src/utils/getDocsPageProps.ts index 29a38bee22..506f09cc58 100644 --- a/packages/ui/docs-bundle/src/utils/getDocsPageProps.ts +++ b/packages/ui/docs-bundle/src/utils/getDocsPageProps.ts @@ -111,7 +111,7 @@ export async function getDynamicDocsPageProps( xFernHost: string, slug: string[], cookies: NextApiRequestCookies, - res: ServerResponse, + _res: ServerResponse, ): Promise { const url = buildUrl({ host: xFernHost, pathname: slug.join("/") }); if (cookies.fern_token == null) { @@ -122,6 +122,7 @@ export async function getDynamicDocsPageProps( const config = await getAuthEdgeConfig(xFernHost); let user: FernUser | undefined = undefined; + // using custom auth (e.g. qlty, propexo, etc) if (config?.type === "basic_token_verification") { try { user = await verifyFernJWT(cookies.fern_token, config.secret, config.issuer); @@ -141,6 +142,7 @@ export async function getDynamicDocsPageProps( user = await verifyFernJWT(cookies.fern_token); } + // SSO if (user.partner === "workos") { const registryService = getRegistryServiceWithToken(`workos_${cookies.fern_token}`); @@ -153,8 +155,6 @@ export async function getDynamicDocsPageProps( console.log(`[getDynamicDocsPageProps] Fetch completed in ${end - start}ms for ${url}`); if (!docs.ok) { - res.setHeader("Set-Cookie", "fern_token=; Path=/; HttpOnly; SameSite=Lax; Max-Age=0"); - if (docs.error.error === "UnauthorizedError") { return { redirect: await getUnauthenticatedRedirect(xFernHost, `/${slug.join("/")}`), @@ -168,6 +168,7 @@ export async function getDynamicDocsPageProps( return convertDocsToDocsPageProps({ docs: docs.body, slug, url, xFernHost }); } else if (user.partner === "ory" || user.partner === "custom") { + // rightbrain's api key injection const docs = await REGISTRY_SERVICE.docs.v2.read.getDocsForUrl({ url }); if (!docs.ok) { @@ -194,14 +195,8 @@ export async function getDynamicDocsPageProps( console.log(error); } - // Clear the token if it's invalid, then redirect to `/` to reset the login flow - res.setHeader("Set-Cookie", "fern_token=; Path=/; HttpOnly; SameSite=Lax; Max-Age=0"); - return { - redirect: { - destination: `/${slug.join("/")}`, - permanent: false, - }, - }; + // fallback to public docs + return convertStaticToServerSidePropsResult(await getDocsPageProps(xFernHost, slug)); } async function convertDocsToDocsPageProps({ diff --git a/packages/ui/docs-bundle/src/utils/xFernHost.ts b/packages/ui/docs-bundle/src/utils/xFernHost.ts index e8d99c705e..708154d3d5 100644 --- a/packages/ui/docs-bundle/src/utils/xFernHost.ts +++ b/packages/ui/docs-bundle/src/utils/xFernHost.ts @@ -79,7 +79,7 @@ export function cleanHost(host: string | null | undefined): string | undefined { } // strip trailing slash from the host, if present - if (host.endsWith("/")) { + if (host?.endsWith("/")) { host = host.slice(0, -1); } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 2560c03026..4fc108ef56 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -2408,6 +2408,9 @@ importers: specifier: ^3.22.4 version: 3.23.8 devDependencies: + '@fern-platform/configs': + specifier: workspace:* + version: link:../../packages/configs '@octokit/types': specifier: ^13.4.1 version: 13.5.0 @@ -15862,10 +15865,10 @@ snapshots: '@aws-crypto/sha1-browser': 3.0.0 '@aws-crypto/sha256-browser': 3.0.0 '@aws-crypto/sha256-js': 3.0.0 - '@aws-sdk/client-sso-oidc': 3.572.0 - '@aws-sdk/client-sts': 3.572.0(@aws-sdk/client-sso-oidc@3.572.0) + '@aws-sdk/client-sso-oidc': 3.572.0(@aws-sdk/client-sts@3.572.0) + '@aws-sdk/client-sts': 3.572.0 '@aws-sdk/core': 3.572.0 - '@aws-sdk/credential-provider-node': 3.572.0(@aws-sdk/client-sso-oidc@3.572.0)(@aws-sdk/client-sts@3.572.0(@aws-sdk/client-sso-oidc@3.572.0)) + '@aws-sdk/credential-provider-node': 3.572.0(@aws-sdk/client-sso-oidc@3.572.0(@aws-sdk/client-sts@3.572.0))(@aws-sdk/client-sts@3.572.0) '@aws-sdk/middleware-bucket-endpoint': 3.568.0 '@aws-sdk/middleware-expect-continue': 3.572.0 '@aws-sdk/middleware-flexible-checksums': 3.572.0 @@ -15926,7 +15929,7 @@ snapshots: '@aws-crypto/sha256-js': 3.0.0 '@aws-sdk/client-sts': 3.572.0 '@aws-sdk/core': 3.572.0 - '@aws-sdk/credential-provider-node': 3.572.0(@aws-sdk/client-sso-oidc@3.572.0)(@aws-sdk/client-sts@3.572.0(@aws-sdk/client-sso-oidc@3.572.0)) + '@aws-sdk/credential-provider-node': 3.572.0(@aws-sdk/client-sso-oidc@3.572.0)(@aws-sdk/client-sts@3.572.0) '@aws-sdk/middleware-host-header': 3.567.0 '@aws-sdk/middleware-logger': 3.568.0 '@aws-sdk/middleware-recursion-detection': 3.567.0 @@ -16060,52 +16063,7 @@ snapshots: '@aws-crypto/sha256-js': 3.0.0 '@aws-sdk/client-sso-oidc': 3.572.0 '@aws-sdk/core': 3.572.0 - '@aws-sdk/credential-provider-node': 3.572.0(@aws-sdk/client-sso-oidc@3.572.0)(@aws-sdk/client-sts@3.572.0) - '@aws-sdk/middleware-host-header': 3.567.0 - '@aws-sdk/middleware-logger': 3.568.0 - '@aws-sdk/middleware-recursion-detection': 3.567.0 - '@aws-sdk/middleware-user-agent': 3.572.0 - '@aws-sdk/region-config-resolver': 3.572.0 - '@aws-sdk/types': 3.567.0 - '@aws-sdk/util-endpoints': 3.572.0 - '@aws-sdk/util-user-agent-browser': 3.567.0 - '@aws-sdk/util-user-agent-node': 3.568.0 - '@smithy/config-resolver': 2.2.0 - '@smithy/core': 1.4.2 - '@smithy/fetch-http-handler': 2.5.0 - '@smithy/hash-node': 2.2.0 - '@smithy/invalid-dependency': 2.2.0 - '@smithy/middleware-content-length': 2.2.0 - '@smithy/middleware-endpoint': 2.5.1 - '@smithy/middleware-retry': 2.3.1 - '@smithy/middleware-serde': 2.3.0 - '@smithy/middleware-stack': 2.2.0 - '@smithy/node-config-provider': 2.3.0 - '@smithy/node-http-handler': 2.5.0 - '@smithy/protocol-http': 3.3.0 - '@smithy/smithy-client': 2.5.1 - '@smithy/types': 2.12.0 - '@smithy/url-parser': 2.2.0 - '@smithy/util-base64': 2.3.0 - '@smithy/util-body-length-browser': 2.2.0 - '@smithy/util-body-length-node': 2.3.0 - '@smithy/util-defaults-mode-browser': 2.2.1 - '@smithy/util-defaults-mode-node': 2.3.1 - '@smithy/util-endpoints': 1.2.0 - '@smithy/util-middleware': 2.2.0 - '@smithy/util-retry': 2.2.0 - '@smithy/util-utf8': 2.3.0 - tslib: 2.6.2 - transitivePeerDependencies: - - aws-crt - - '@aws-sdk/client-sts@3.572.0(@aws-sdk/client-sso-oidc@3.572.0)': - dependencies: - '@aws-crypto/sha256-browser': 3.0.0 - '@aws-crypto/sha256-js': 3.0.0 - '@aws-sdk/client-sso-oidc': 3.572.0 - '@aws-sdk/core': 3.572.0 - '@aws-sdk/credential-provider-node': 3.572.0(@aws-sdk/client-sso-oidc@3.572.0)(@aws-sdk/client-sts@3.572.0(@aws-sdk/client-sso-oidc@3.572.0)) + '@aws-sdk/credential-provider-node': 3.572.0(@aws-sdk/client-sso-oidc@3.572.0(@aws-sdk/client-sts@3.572.0))(@aws-sdk/client-sts@3.572.0) '@aws-sdk/middleware-host-header': 3.567.0 '@aws-sdk/middleware-logger': 3.568.0 '@aws-sdk/middleware-recursion-detection': 3.567.0 @@ -16142,7 +16100,6 @@ snapshots: '@smithy/util-utf8': 2.3.0 tslib: 2.6.2 transitivePeerDependencies: - - '@aws-sdk/client-sso-oidc' - aws-crt '@aws-sdk/core@3.572.0': @@ -16191,23 +16148,6 @@ snapshots: - '@aws-sdk/client-sso-oidc' - aws-crt - '@aws-sdk/credential-provider-ini@3.572.0(@aws-sdk/client-sso-oidc@3.572.0)(@aws-sdk/client-sts@3.572.0(@aws-sdk/client-sso-oidc@3.572.0))': - dependencies: - '@aws-sdk/client-sts': 3.572.0(@aws-sdk/client-sso-oidc@3.572.0) - '@aws-sdk/credential-provider-env': 3.568.0 - '@aws-sdk/credential-provider-process': 3.572.0 - '@aws-sdk/credential-provider-sso': 3.572.0(@aws-sdk/client-sso-oidc@3.572.0) - '@aws-sdk/credential-provider-web-identity': 3.568.0(@aws-sdk/client-sts@3.572.0) - '@aws-sdk/types': 3.567.0 - '@smithy/credential-provider-imds': 2.3.0 - '@smithy/property-provider': 2.2.0 - '@smithy/shared-ini-file-loader': 2.4.0 - '@smithy/types': 2.12.0 - tslib: 2.6.2 - transitivePeerDependencies: - - '@aws-sdk/client-sso-oidc' - - aws-crt - '@aws-sdk/credential-provider-ini@3.572.0(@aws-sdk/client-sso-oidc@3.572.0)(@aws-sdk/client-sts@3.572.0)': dependencies: '@aws-sdk/client-sts': 3.572.0 @@ -16244,25 +16184,6 @@ snapshots: - '@aws-sdk/client-sts' - aws-crt - '@aws-sdk/credential-provider-node@3.572.0(@aws-sdk/client-sso-oidc@3.572.0)(@aws-sdk/client-sts@3.572.0(@aws-sdk/client-sso-oidc@3.572.0))': - dependencies: - '@aws-sdk/credential-provider-env': 3.568.0 - '@aws-sdk/credential-provider-http': 3.568.0 - '@aws-sdk/credential-provider-ini': 3.572.0(@aws-sdk/client-sso-oidc@3.572.0)(@aws-sdk/client-sts@3.572.0(@aws-sdk/client-sso-oidc@3.572.0)) - '@aws-sdk/credential-provider-process': 3.572.0 - '@aws-sdk/credential-provider-sso': 3.572.0(@aws-sdk/client-sso-oidc@3.572.0) - '@aws-sdk/credential-provider-web-identity': 3.568.0(@aws-sdk/client-sts@3.572.0) - '@aws-sdk/types': 3.567.0 - '@smithy/credential-provider-imds': 2.3.0 - '@smithy/property-provider': 2.2.0 - '@smithy/shared-ini-file-loader': 2.4.0 - '@smithy/types': 2.12.0 - tslib: 2.6.2 - transitivePeerDependencies: - - '@aws-sdk/client-sso-oidc' - - '@aws-sdk/client-sts' - - aws-crt - '@aws-sdk/credential-provider-node@3.572.0(@aws-sdk/client-sso-oidc@3.572.0)(@aws-sdk/client-sts@3.572.0)': dependencies: '@aws-sdk/credential-provider-env': 3.568.0 @@ -16318,7 +16239,7 @@ snapshots: '@aws-sdk/credential-provider-web-identity@3.568.0(@aws-sdk/client-sts@3.572.0)': dependencies: - '@aws-sdk/client-sts': 3.572.0(@aws-sdk/client-sso-oidc@3.572.0) + '@aws-sdk/client-sts': 3.572.0 '@aws-sdk/types': 3.567.0 '@smithy/property-provider': 2.2.0 '@smithy/types': 2.12.0 diff --git a/servers/fdr/src/db/snippets/EndpointSnippetCollectors.ts b/servers/fdr/src/db/snippets/EndpointSnippetCollectors.ts index ade70253fb..1b9700c7f3 100644 --- a/servers/fdr/src/db/snippets/EndpointSnippetCollectors.ts +++ b/servers/fdr/src/db/snippets/EndpointSnippetCollectors.ts @@ -19,7 +19,7 @@ export class EndpointSnippetCollector { if (this.snippetsByEndpointId[identifierOverride] == null) { this.snippetsByEndpointId[identifierOverride] = []; } - this.snippetsByEndpointId[identifierOverride].push(snippet); + this.snippetsByEndpointId[identifierOverride]?.push(snippet); } if (this.snippetsByEndpoint[endpointPath] == null) { this.snippetsByEndpoint[endpointPath] = { diff --git a/servers/fdr/src/db/snippets/SnippetTemplate.ts b/servers/fdr/src/db/snippets/SnippetTemplate.ts index 6ba6701bd4..ff505e8b1d 100644 --- a/servers/fdr/src/db/snippets/SnippetTemplate.ts +++ b/servers/fdr/src/db/snippets/SnippetTemplate.ts @@ -106,10 +106,11 @@ export class SnippetTemplateDaoImpl implements SnippetTemplateDao { }, }); if (result != null && result.endpointId.identifierOverride != null) { - if (toRet[result.endpointId.identifierOverride] == null) { - toRet[result.endpointId.identifierOverride] = {}; - } - toRet[result.endpointId.identifierOverride][sdk.type] = result.snippetTemplate; + const template = { + [sdk.type]: result.snippetTemplate, + ...(toRet[result.endpointId.identifierOverride] ?? {}), + }; + toRet[result.endpointId.identifierOverride] = template; } } } @@ -271,6 +272,14 @@ export class SnippetTemplateDaoImpl implements SnippetTemplateDao { }); } + public DEFAULT_ENDPOINT_SNIPPET_TEMPLATES: Record = { + PATCH: {}, + POST: {}, + PUT: {}, + GET: {}, + DELETE: {}, + }; + public async loadSnippetTemplatesByEndpoint({ orgId, apiId, @@ -314,16 +323,15 @@ export class SnippetTemplateDaoImpl implements SnippetTemplateDao { }, }); if (result != null) { - if (toRet[result.endpointId.path] == null) { - toRet[result.endpointId.path] = { - PATCH: {}, - POST: {}, - PUT: {}, - GET: {}, - DELETE: {}, - }; - } - toRet[result.endpointId.path][result.endpointId.method][sdk.type] = result.snippetTemplate; + const value = { + ...(toRet[result.endpointId.path] ?? this.DEFAULT_ENDPOINT_SNIPPET_TEMPLATES), + [result.endpointId.method]: { + ...(toRet[result.endpointId.path]?.[result.endpointId.method] ?? {}), + [sdk.type]: result.snippetTemplate, + }, + }; + + toRet[result.endpointId.path] = value; } } } diff --git a/servers/fdr/src/services/docs-cache/DocsDefinitionCache.ts b/servers/fdr/src/services/docs-cache/DocsDefinitionCache.ts index f9d367e92a..87c83d8a88 100644 --- a/servers/fdr/src/services/docs-cache/DocsDefinitionCache.ts +++ b/servers/fdr/src/services/docs-cache/DocsDefinitionCache.ts @@ -249,7 +249,7 @@ export class DocsDefinitionCacheImpl implements DocsDefinitionCache { if (this.redisDocsCache) { return await this.redisDocsCache.get({ url }); } - return this.localDocsCache.get({ url }); + return this.localDocsCache.get({ url }) ?? null; } private async getDocsForUrlFromDatabase({ url }: { url: URL }): Promise { diff --git a/servers/fdr/src/services/docs-cache/LocalDocsDefinitionStore.ts b/servers/fdr/src/services/docs-cache/LocalDocsDefinitionStore.ts index 0df6bd53a5..1092d0200f 100644 --- a/servers/fdr/src/services/docs-cache/LocalDocsDefinitionStore.ts +++ b/servers/fdr/src/services/docs-cache/LocalDocsDefinitionStore.ts @@ -7,7 +7,7 @@ export default class LocalDocsDefinitionStore { this.localCache = {}; } - get({ url }: { url: URL }): CachedDocsResponse { + get({ url }: { url: URL }): CachedDocsResponse | undefined { return this.localCache[url.hostname]; } diff --git a/servers/fern-bot/package.json b/servers/fern-bot/package.json index 2cddeebdb8..81ca6fbc7d 100644 --- a/servers/fern-bot/package.json +++ b/servers/fern-bot/package.json @@ -34,6 +34,7 @@ "zod": "^3.22.4" }, "devDependencies": { + "@fern-platform/configs": "workspace:*", "@octokit/types": "^13.4.1", "@serverless/typescript": "^3.0.0", "@types/aws-lambda": "^8.10.71", diff --git a/servers/fern-bot/tsconfig.json b/servers/fern-bot/tsconfig.json index 1165ad9d5b..a0d87f8188 100644 --- a/servers/fern-bot/tsconfig.json +++ b/servers/fern-bot/tsconfig.json @@ -1,7 +1,12 @@ { - "extends": "./tsconfig.paths.json", "compilerOptions": { + "baseUrl": ".", + "paths": { + "@functions/*": ["src/functions/*"], + "@libs/*": ["src/libs/*"] + }, "lib": ["ESNext", "DOM"], + "module": "esnext", "moduleResolution": "node", "noUnusedLocals": true, "noUnusedParameters": true, @@ -9,11 +14,13 @@ "sourceMap": true, "target": "ES2020", "outDir": "lib", + "esModuleInterop": true, "allowSyntheticDefaultImports": true }, "include": ["src/**/*.ts", "serverless.ts"], "exclude": ["node_modules/**/*", ".serverless/**/*", ".webpack/**/*", "_warmup/**/*", ".vscode/**/*"], "ts-node": { "require": ["tsconfig-paths/register"] - } + }, + "references": [{ "path": "../../packages/commons/github" }] } diff --git a/servers/fern-bot/tsconfig.paths.json b/servers/fern-bot/tsconfig.paths.json deleted file mode 100644 index 2e66463593..0000000000 --- a/servers/fern-bot/tsconfig.paths.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "compilerOptions": { - "baseUrl": ".", - "paths": { - "@functions/*": ["src/functions/*"], - "@libs/*": ["src/libs/*"] - } - } -}