117 lines
4.3 KiB
TypeScript
117 lines
4.3 KiB
TypeScript
import { defineConfig, defineDocs } from "fumadocs-mdx/config";
|
|
import { remarkMdxMermaid } from "fumadocs-core/mdx-plugins/remark-mdx-mermaid";
|
|
import lastModified from "fumadocs-mdx/plugins/last-modified";
|
|
import { metaSchema, pageSchema } from "fumadocs-core/source/schema";
|
|
import { z } from "zod";
|
|
import remarkMath from "remark-math";
|
|
import remarkDirective from "remark-directive";
|
|
import rehypeKatex from "rehype-katex";
|
|
import { remarkAdmonitions } from "./lib/remark-admonitions";
|
|
import { AUTHOR_IDS } from "./lib/authors";
|
|
import { BLOG_CATEGORY_IDS } from "./lib/blog-categories";
|
|
|
|
/**
|
|
* Extend Fumadocs' default page frontmatter with a Docusaurus-style
|
|
* `sidebar_label`. When set, the page's sidebar label is overridden
|
|
* (see the tree transformer in `lib/source.ts`); the page's H1 still
|
|
* uses the regular `title` field.
|
|
*
|
|
* Note: fumadocs-mdx only allows collection/config exports from this
|
|
* file, so this schema stays internal (non-exported).
|
|
*/
|
|
const extendedPageSchema = pageSchema.extend({
|
|
sidebar_label: z.string().optional(),
|
|
});
|
|
|
|
const commonOptions = {
|
|
docs: {
|
|
schema: extendedPageSchema,
|
|
postprocess: {
|
|
includeProcessedMarkdown: true,
|
|
},
|
|
},
|
|
meta: {
|
|
schema: metaSchema,
|
|
},
|
|
} as const;
|
|
|
|
/**
|
|
* Blog-specific frontmatter. Kept separate from `commonOptions` so
|
|
* docs/guides/etc don't silently accept `authors`/`date` fields they
|
|
* would ignore. `z.enum(AUTHOR_IDS)` locks `authors` to known IDs
|
|
* from `lib/authors.ts` — a typo fails the build with a clear path.
|
|
*/
|
|
const blogPageSchema = extendedPageSchema.extend({
|
|
authors: z.array(z.enum(AUTHOR_IDS)).min(1).optional(),
|
|
date: z.coerce.date().optional(),
|
|
image: z.string().url().optional(),
|
|
// Optional — pins a post to one of the known categories in
|
|
// `lib/blog-categories.ts`. Kept as a single value (not an array)
|
|
// because the sidebar groupings are also single-section.
|
|
category: z.enum(BLOG_CATEGORY_IDS).optional(),
|
|
});
|
|
|
|
const blogOptions = {
|
|
docs: {
|
|
schema: blogPageSchema,
|
|
postprocess: {
|
|
includeProcessedMarkdown: true,
|
|
},
|
|
},
|
|
meta: {
|
|
schema: metaSchema,
|
|
},
|
|
} as const;
|
|
|
|
export const docs = defineDocs({ dir: "content/docs", ...commonOptions });
|
|
export const guides = defineDocs({ dir: "content/guides", ...commonOptions });
|
|
export const tutorials = defineDocs({
|
|
dir: "content/tutorials",
|
|
...commonOptions,
|
|
});
|
|
export const integrations = defineDocs({
|
|
dir: "content/integrations",
|
|
...commonOptions,
|
|
});
|
|
export const changelog = defineDocs({
|
|
dir: "content/changelog",
|
|
...commonOptions,
|
|
});
|
|
export const blog = defineDocs({ dir: "content/blog", ...blogOptions });
|
|
|
|
export default defineConfig({
|
|
// `lastModified` reads each file's latest git commit timestamp at build
|
|
// time and injects it as `page.data.lastModified`. Outside a git tree
|
|
// (e.g. fresh checkouts before first commit) the value is `null`, which
|
|
// `<PageLastUpdate>` silently no-ops on — safe to always enable.
|
|
plugins: [lastModified()],
|
|
mdxOptions: {
|
|
// remarkDirective parses `:::type[title]` container directives;
|
|
// remarkAdmonitions rewrites the recognized ones into <Callout>.
|
|
remarkPlugins: [
|
|
remarkMath,
|
|
remarkDirective,
|
|
remarkAdmonitions,
|
|
remarkMdxMermaid,
|
|
],
|
|
// rehypeKatex must run before the syntax highlighter
|
|
rehypePlugins: (v) => [rehypeKatex, ...v],
|
|
// Fumadocs' default `remark-image` plugin tries to fetch every
|
|
// remote image at build time to precompute `width`/`height` for
|
|
// `next/image`. Our blog + tutorial content references dozens of
|
|
// images on `deepeval-docs.s3.us-east-1.amazonaws.com` — those
|
|
// fetches intermittently time out (see the "Failed obtain image
|
|
// size" build error), which hard-fails the entire build.
|
|
//
|
|
// Setting `external: false` keeps the plugin running for LOCAL
|
|
// images (under `public/` — still get proper dimensions + blur
|
|
// placeholders) but skips remote URLs: they pass through as plain
|
|
// `<img src="https://…">` tags. Runtime rendering still works
|
|
// because `next.config.mjs` whitelists the S3 hostname for
|
|
// `next/image`, and Next's lowering of `![]()` to `<Image>` at
|
|
// the MDX layer is happy without precomputed dimensions as long
|
|
// as an `<img>` fallback is acceptable in the HTML output.
|
|
remarkImageOptions: { external: false },
|
|
},
|
|
});
|