diff --git a/packages/ui/fern-docs-edge-config/package.json b/packages/ui/fern-docs-edge-config/package.json index 3da1afc87f..3820952e19 100644 --- a/packages/ui/fern-docs-edge-config/package.json +++ b/packages/ui/fern-docs-edge-config/package.json @@ -37,6 +37,7 @@ "@fern-ui/fern-docs-auth": "workspace:*", "@fern-ui/fern-docs-utils": "workspace:*", "@vercel/edge-config": "^1.1.0", + "ts-essentials": "^10.0.1", "url-join": "5.0.0", "zod": "^3.23.8" }, diff --git a/packages/ui/fern-docs-edge-config/src/getEdgeConfig.ts b/packages/ui/fern-docs-edge-config/src/getEdgeConfig.ts new file mode 100644 index 0000000000..756cc91d7e --- /dev/null +++ b/packages/ui/fern-docs-edge-config/src/getEdgeConfig.ts @@ -0,0 +1,36 @@ +import { withoutStaging } from "@fern-ui/fern-docs-utils"; +import { get } from "@vercel/edge-config"; +import { DeepReadonly } from "ts-essentials"; +import { z } from "zod"; +import { FernDocsEdgeConfigV2 } from "./types"; + +const FernDocsEdgeConfigUnion = z.union([FernDocsEdgeConfigV2, z.string().describe("Alias to another config (host)")]); +type FernDocsEdgeConfigUnion = z.infer; + +async function internalGetEdgeConfig(host: string): Promise | undefined> { + const config = await get(host); + const parse = FernDocsEdgeConfigUnion.safeParse(config); + if (!parse.success) { + // TODO: log error to sentry (and alert the team) + // eslint-disable-next-line no-console + console.error("Failed to parse FernDocsEdgeConfig", parse.error); + } + return parse.data; // returns undefined if parsing failed (swallows error) +} + +export async function getEdgeConfig(host: string): Promise | undefined> { + let config = await internalGetEdgeConfig(host); + + const hostWithoutStaging = withoutStaging(host); + + if (config === undefined && hostWithoutStaging !== host) { + config = await internalGetEdgeConfig(hostWithoutStaging); + } + + // if config is a string, it's an alias to another config + if (typeof config === "string") { + return getEdgeConfig(config); + } + + return config; +} diff --git a/packages/ui/fern-docs-edge-config/src/types.ts b/packages/ui/fern-docs-edge-config/src/types.ts new file mode 100644 index 0000000000..c18be80cab --- /dev/null +++ b/packages/ui/fern-docs-edge-config/src/types.ts @@ -0,0 +1,145 @@ +import { AuthEdgeConfigSchema } from "@fern-ui/fern-docs-auth"; +import { z } from "zod"; + +export const FeatureFlagsConfig = z.object({ + "api-playground-enabled": z.boolean().optional().default(false).describe("Enables the API playground"), + "api-reference-paginated": z + .boolean() + .optional() + .default(false) + .describe( + "By default, all API references are rendered in a single page. This flag forces all API references to be rendered as individual pages.", + ), + whitelabeled: z.boolean().optional().default(false).describe("Enables whitelabeling for the customer."), + "seo-disabled": z.boolean().optional().default(false).describe("Sets noindex, nofollow on all pages."), + "additional-toc-default-enabled": z + .boolean() + .optional() + .default(false) + .describe( + "By default, the table of contents does not include accordions, steps, or tabs by default, though they can be enabled individually. Turning this flag on will enable all of them by default.", + ), + "snippet-template-enabled": z + .boolean() + .optional() + .default(false) + .describe("Enables evaluating snippet templates in the API playground."), + "http-snippets-enabled": z + .boolean() + .optional() + .default(false) + .describe("Enables generating code examples in the api reference using http-snippets-lite."), + "inline-feedback-enabled": z + .boolean() + .optional() + .default(false) + .describe("Enables the inline feedback widget, which adds a toolbar next to highlighted text."), + "dark-code-in-light-mode": z + .boolean() + .optional() + .default(false) + .describe("Enables dark code blocks in light mode."), + "proxy-uses-app-buildwithfern": z + .boolean() + .optional() + .default(false) + .describe("Enables the API Playground proxy to use app.buildwithfern.com instead of the current URL"), + "image-zoom-disabled": z + .boolean() + .optional() + .default(false) + .describe("Disables the image zoom feature in the docs."), + "use-javascript-as-typescript": z.boolean().optional().default(false).describe("Renames TypeScript to JavaScript"), + "always-enable-javascript-fetch": z + .boolean() + .optional() + .default(false) + .describe( + "An additional flag that will always generate a JavaScript fetch example via the http-snippets-lite plugin.", + ), + "use-mdx-bundler": z + .boolean() + .optional() + .default(false) + .describe("Enables the use of mdx-bundler instead of next-mdx-remote for rendering MDX content."), + "batch-stream-toggle-disabled": z + .boolean() + .optional() + .default(false) + .describe("Disables the batch/stream toggle and renders batch and stream examples separately."), + "enabled-auth-in-generated-docs": z + .boolean() + .optional() + .default(false) + .describe("Renders the authentication scheme in the generated docs."), + "ai-chat-preview": z + .boolean() + .optional() + .default(false) + .describe("[Preview] Enables the conversational search plugin."), + "audio-file-download-span-summary": z + .boolean() + .optional() + .default(false) + .describe("[Hack] Renders `audio/mpeg` in the response summary for file downloads."), + "docs-logo-text-enabled": z + .boolean() + .optional() + .default(false) + .describe("[Hack] Renders `Docs` text next to the logo in the header."), + "audio-example-internal": z + .boolean() + .optional() + .default(false) + .describe("[Hack] Enables rendering a hard-coded audio example in the API reference."), + "uses-application-json-in-form-data-value": z + .boolean() + .optional() + .default(false) + .describe( + "Most APIs assume string values in form data are application/json. This flag will actually send application/json as the content type. This affects both code snippet generation, and the actual request sent via the playground proxy", + ), + "binary-octet-stream-audio-player": z + .boolean() + .optional() + .default(false) + .describe("[Hack] Enables an audio player for binary/octet-stream responses."), + "voice-id-playground-form": z + .boolean() + .optional() + .default(false) + .describe("[Hack] Enables a voice ID form in the API playground for Elevenlabs."), + "cohere-theme": z.boolean().optional().default(false).describe("Enables the Cohere theme for the customer."), + "file-forge-hack-enabled": z + .boolean() + .optional() + .default(false) + .describe("[Hack] Enables the file forge hack for the customer."), + "hide-404-page": z + .boolean() + .optional() + .default(false) + .describe("Hides the 404 page and redirects to the root page."), + "new-search-experience": z.boolean().optional().default(false).describe("Enables the new search experience."), +}); + +export const InkeepSettings = z.object({ + replaceSearch: z.boolean().optional().default(false), + baseSettings: z.record(z.any()).optional(), + aiChatSettings: z.record(z.any()).optional(), + searchSettings: z.record(z.any()).optional(), + modalSettings: z.record(z.any()).optional(), +}); + +export const LaunchDarklySettings = z.object({ + "client-side-id": z.string().optional(), +}); + +export const FernDocsEdgeConfigV2 = z.object({ + "feature-flags": FeatureFlagsConfig.optional(), + authentication: AuthEdgeConfigSchema.optional(), + inkeep: InkeepSettings.optional(), + "launch-darkly": LaunchDarklySettings.optional(), +}); + +export type FernDocsEdgeConfigV2 = z.infer; diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 66d43c4f11..5d154a98d8 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -443,7 +443,7 @@ importers: version: 3.3.2 simple-git: specifier: ^3.24.0 - version: 3.24.0 + version: 3.24.0(supports-color@8.1.1) stylelint: specifier: ^16.1.0 version: 16.5.0(typescript@5.4.3) @@ -2142,6 +2142,9 @@ importers: '@vercel/edge-config': specifier: ^1.1.0 version: 1.1.0(@opentelemetry/api@1.9.0)(typescript@5.4.3) + ts-essentials: + specifier: ^10.0.1 + version: 10.0.1(typescript@5.4.3) url-join: specifier: 5.0.0 version: 5.0.0 @@ -2798,7 +2801,7 @@ importers: version: 3.21.0(serverless@3.38.0) simple-git: specifier: ^3.24.0 - version: 3.24.0 + version: 3.24.0(supports-color@8.1.1) tmp-promise: specifier: ^3.0.3 version: 3.0.3 @@ -17512,7 +17515,7 @@ snapshots: '@babel/traverse': 7.24.5 '@babel/types': 7.24.5 convert-source-map: 2.0.0 - debug: 4.3.4(supports-color@5.5.0) + debug: 4.3.4(supports-color@8.1.1) gensync: 1.0.0-beta.2 json5: 2.2.3 semver: 6.3.1 @@ -18311,7 +18314,7 @@ snapshots: '@babel/helper-split-export-declaration': 7.24.5 '@babel/parser': 7.24.5 '@babel/types': 7.24.5 - debug: 4.3.4(supports-color@5.5.0) + debug: 4.3.4(supports-color@8.1.1) globals: 11.12.0 transitivePeerDependencies: - supports-color @@ -18326,7 +18329,7 @@ snapshots: '@babel/helper-split-export-declaration': 7.24.5 '@babel/parser': 7.24.5 '@babel/types': 7.24.5 - debug: 4.3.4(supports-color@5.5.0) + debug: 4.3.4(supports-color@8.1.1) globals: 11.12.0 transitivePeerDependencies: - supports-color @@ -18550,7 +18553,7 @@ snapshots: '@eslint/eslintrc@2.1.4': dependencies: ajv: 6.12.6 - debug: 4.3.4(supports-color@5.5.0) + debug: 4.3.4(supports-color@8.1.1) espree: 9.6.1 globals: 13.24.0 ignore: 5.3.1 @@ -18813,7 +18816,7 @@ snapshots: '@humanwhocodes/config-array@0.11.14': dependencies: '@humanwhocodes/object-schema': 2.0.3 - debug: 4.3.4(supports-color@5.5.0) + debug: 4.3.4(supports-color@8.1.1) minimatch: 3.1.2 transitivePeerDependencies: - supports-color @@ -19281,12 +19284,6 @@ snapshots: '@jridgewell/resolve-uri': 3.1.2 '@jridgewell/sourcemap-codec': 1.4.15 - '@kwsites/file-exists@1.1.1': - dependencies: - debug: 4.3.4(supports-color@5.5.0) - transitivePeerDependencies: - - supports-color - '@kwsites/file-exists@1.1.1(supports-color@8.1.1)': dependencies: debug: 4.3.4(supports-color@8.1.1) @@ -23641,7 +23638,7 @@ snapshots: '@typescript-eslint/type-utils': 7.3.1(eslint@8.57.0)(typescript@5.4.3) '@typescript-eslint/utils': 7.3.1(eslint@8.57.0)(typescript@5.4.3) '@typescript-eslint/visitor-keys': 7.3.1 - debug: 4.3.4(supports-color@5.5.0) + debug: 4.3.4(supports-color@8.1.1) eslint: 8.57.0 graphemer: 1.4.0 ignore: 5.3.1 @@ -23659,7 +23656,7 @@ snapshots: '@typescript-eslint/types': 7.17.0 '@typescript-eslint/typescript-estree': 7.17.0(typescript@5.4.3) '@typescript-eslint/visitor-keys': 7.17.0 - debug: 4.3.4(supports-color@5.5.0) + debug: 4.3.4(supports-color@8.1.1) eslint: 8.57.0 optionalDependencies: typescript: 5.4.3 @@ -23672,7 +23669,7 @@ snapshots: '@typescript-eslint/types': 7.2.0 '@typescript-eslint/typescript-estree': 7.2.0(typescript@5.4.3) '@typescript-eslint/visitor-keys': 7.2.0 - debug: 4.3.4(supports-color@5.5.0) + debug: 4.3.4(supports-color@8.1.1) eslint: 8.57.0 optionalDependencies: typescript: 5.4.3 @@ -23685,7 +23682,7 @@ snapshots: '@typescript-eslint/types': 7.3.1 '@typescript-eslint/typescript-estree': 7.3.1(typescript@5.4.3) '@typescript-eslint/visitor-keys': 7.3.1 - debug: 4.3.4(supports-color@5.5.0) + debug: 4.3.4(supports-color@8.1.1) eslint: 8.57.0 optionalDependencies: typescript: 5.4.3 @@ -23738,7 +23735,7 @@ snapshots: dependencies: '@typescript-eslint/typescript-estree': 7.3.1(typescript@5.4.3) '@typescript-eslint/utils': 7.3.1(eslint@8.57.0)(typescript@5.4.3) - debug: 4.3.4(supports-color@5.5.0) + debug: 4.3.4(supports-color@8.1.1) eslint: 8.57.0 ts-api-utils: 1.3.0(typescript@5.4.3) optionalDependencies: @@ -23821,7 +23818,7 @@ snapshots: dependencies: '@typescript-eslint/types': 7.3.1 '@typescript-eslint/visitor-keys': 7.3.1 - debug: 4.3.4(supports-color@5.5.0) + debug: 4.3.4(supports-color@8.1.1) globby: 11.1.0 is-glob: 4.0.3 minimatch: 9.0.3 @@ -26901,7 +26898,7 @@ snapshots: callsite: 1.0.0 camelcase: 6.3.0 cosmiconfig: 7.1.0 - debug: 4.3.4(supports-color@5.5.0) + debug: 4.3.4(supports-color@8.1.1) deps-regex: 0.2.0 findup-sync: 5.0.0 ignore: 5.3.1 @@ -27391,7 +27388,7 @@ snapshots: eslint-import-resolver-typescript@3.6.1(@typescript-eslint/parser@7.2.0(eslint@8.57.0)(typescript@5.4.3))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.29.1)(eslint@8.57.0): dependencies: - debug: 4.3.4(supports-color@5.5.0) + debug: 4.3.4(supports-color@8.1.1) enhanced-resolve: 5.16.1 eslint: 8.57.0 eslint-module-utils: 2.8.1(@typescript-eslint/parser@7.2.0(eslint@8.57.0)(typescript@5.4.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.1(@typescript-eslint/parser@7.2.0(eslint@8.57.0)(typescript@5.4.3))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.29.1)(eslint@8.57.0))(eslint@8.57.0) @@ -27605,7 +27602,7 @@ snapshots: ajv: 6.12.6 chalk: 4.1.2 cross-spawn: 7.0.3 - debug: 4.3.4(supports-color@5.5.0) + debug: 4.3.4(supports-color@8.1.1) doctrine: 3.0.0 escape-string-regexp: 4.0.0 eslint-scope: 7.2.2 @@ -29014,7 +29011,7 @@ snapshots: dependencies: '@ioredis/commands': 1.2.0 cluster-key-slot: 1.1.2 - debug: 4.3.4(supports-color@5.5.0) + debug: 4.3.4(supports-color@8.1.1) denque: 2.1.0 lodash.defaults: 4.2.0 lodash.isarguments: 3.1.0 @@ -29967,7 +29964,7 @@ snapshots: dependencies: chalk: 5.3.0 commander: 11.0.0 - debug: 4.3.4(supports-color@5.5.0) + debug: 4.3.4(supports-color@8.1.1) execa: 7.2.0 lilconfig: 2.1.0 listr2: 6.6.1 @@ -33359,14 +33356,6 @@ snapshots: once: 1.4.0 simple-concat: 1.0.1 - simple-git@3.24.0: - dependencies: - '@kwsites/file-exists': 1.1.1 - '@kwsites/promise-deferred': 1.1.1 - debug: 4.3.4(supports-color@5.5.0) - transitivePeerDependencies: - - supports-color - simple-git@3.24.0(supports-color@8.1.1): dependencies: '@kwsites/file-exists': 1.1.1(supports-color@8.1.1) @@ -33803,7 +33792,7 @@ snapshots: cosmiconfig: 9.0.0(typescript@5.4.3) css-functions-list: 3.2.2 css-tree: 2.3.1 - debug: 4.3.4(supports-color@5.5.0) + debug: 4.3.4(supports-color@8.1.1) fast-glob: 3.3.2 fastest-levenshtein: 1.0.16 file-entry-cache: 8.0.0 @@ -34438,7 +34427,7 @@ snapshots: bundle-require: 4.1.0(esbuild@0.20.2) cac: 6.7.14 chokidar: 3.6.0 - debug: 4.3.4(supports-color@5.5.0) + debug: 4.3.4(supports-color@8.1.1) esbuild: 0.20.2 execa: 5.1.1 globby: 11.1.0 @@ -34951,7 +34940,7 @@ snapshots: vite-node@1.6.0(@types/node@18.19.33)(less@4.2.0)(sass@1.77.0)(stylus@0.62.0)(terser@5.31.0): dependencies: cac: 6.7.14 - debug: 4.3.4(supports-color@5.5.0) + debug: 4.3.4(supports-color@8.1.1) pathe: 1.1.2 picocolors: 1.0.0 vite: 5.2.11(@types/node@18.19.33)(less@4.2.0)(sass@1.77.0)(stylus@0.62.0)(terser@5.31.0) @@ -34968,7 +34957,7 @@ snapshots: vite-node@1.6.0(@types/node@22.5.5)(less@4.2.0)(sass@1.77.0)(stylus@0.62.0)(terser@5.31.0): dependencies: cac: 6.7.14 - debug: 4.3.4(supports-color@5.5.0) + debug: 4.3.4(supports-color@8.1.1) pathe: 1.1.2 picocolors: 1.0.0 vite: 5.2.11(@types/node@22.5.5)(less@4.2.0)(sass@1.77.0)(stylus@0.62.0)(terser@5.31.0) @@ -35092,7 +35081,7 @@ snapshots: '@vitest/utils': 1.6.0 acorn-walk: 8.3.2 chai: 4.4.1 - debug: 4.3.4(supports-color@5.5.0) + debug: 4.3.4(supports-color@8.1.1) execa: 8.0.1 local-pkg: 0.5.0 magic-string: 0.30.10 @@ -35127,7 +35116,7 @@ snapshots: '@vitest/utils': 1.6.0 acorn-walk: 8.3.2 chai: 4.4.1 - debug: 4.3.4(supports-color@5.5.0) + debug: 4.3.4(supports-color@8.1.1) execa: 8.0.1 local-pkg: 0.5.0 magic-string: 0.30.10