mirror of
https://github.com/hanzo-docs/docs.git
synced 2026-10-26 23:49:51 +00:00
MDX: Drop support for Zod 3 schemas
This commit is contained in:
@@ -0,0 +1,7 @@
|
||||
---
|
||||
'fumadocs-mdx': major
|
||||
---
|
||||
|
||||
**Drop support for Zod 3 schemas**
|
||||
|
||||
Zod 3 schemas are still allowed, but you cannot no longer extend on the Zod 4 schemas provided by Fumadocs.
|
||||
@@ -18,11 +18,6 @@
|
||||
"types": "./dist/config/index.d.ts",
|
||||
"require": "./dist/config/index.cjs"
|
||||
},
|
||||
"./config/zod-3": {
|
||||
"import": "./dist/config/zod-3.js",
|
||||
"types": "./dist/config/zod-3.d.ts",
|
||||
"require": "./dist/config/zod-3.cjs"
|
||||
},
|
||||
"./next": {
|
||||
"import": "./dist/next/index.js",
|
||||
"types": "./dist/next/index.d.ts",
|
||||
|
||||
@@ -105,9 +105,9 @@ export function defineDocs<
|
||||
DocSchema extends StandardSchemaV1 = typeof frontmatterSchema,
|
||||
MetaSchema extends StandardSchemaV1 = typeof metaSchema,
|
||||
Async extends boolean = false,
|
||||
>(options?: {
|
||||
>(options: {
|
||||
/**
|
||||
* The directory to scan files
|
||||
* The content directory to scan files
|
||||
*
|
||||
* @defaultValue 'content/docs'
|
||||
*/
|
||||
@@ -116,11 +116,7 @@ export function defineDocs<
|
||||
docs?: Omit<DocCollection<DocSchema, Async>, 'dir' | 'type'>;
|
||||
meta?: Omit<MetaCollection<MetaSchema>, 'dir' | 'type'>;
|
||||
}): DocsCollection<DocSchema, MetaSchema, Async> {
|
||||
if (!options)
|
||||
console.warn(
|
||||
'[`source.config.ts`] Deprecated: please pass options to `defineDocs()` and specify a `dir`.',
|
||||
);
|
||||
const dir = options?.dir ?? 'content/docs';
|
||||
const dir = options.dir ?? 'content/docs';
|
||||
|
||||
return {
|
||||
type: 'docs',
|
||||
|
||||
@@ -1,27 +0,0 @@
|
||||
// for backward compatibility
|
||||
// while Fumadocs expose Zod v4 schemas, some Zod v3 users use `.extend()` on schemas
|
||||
// we can expose v3 schema instead
|
||||
import { z } from 'zod/v3';
|
||||
|
||||
export const metaSchema = z.object({
|
||||
title: z.string().optional(),
|
||||
pages: z.array(z.string()).optional(),
|
||||
description: z.string().optional(),
|
||||
root: z.boolean().optional(),
|
||||
defaultOpen: z.boolean().optional(),
|
||||
icon: z.string().optional(),
|
||||
});
|
||||
|
||||
export const frontmatterSchema = z.object({
|
||||
title: z.string(),
|
||||
description: z.string().optional(),
|
||||
icon: z.string().optional(),
|
||||
full: z.boolean().optional(),
|
||||
|
||||
// Fumadocs OpenAPI generated
|
||||
_openapi: z.object({}).passthrough().optional(),
|
||||
});
|
||||
|
||||
export * from './define';
|
||||
export { getDefaultMDXOptions, type DefaultMDXOptions } from '../mdx/preset';
|
||||
export * from '@/mdx/remark-include';
|
||||
@@ -5,7 +5,7 @@ import type { LoadedConfig } from '@/utils/config';
|
||||
import type { DocCollection, MetaCollection } from '@/config';
|
||||
import { validate } from '@/utils/validation';
|
||||
import { fileCache } from '@/map/file-cache';
|
||||
import type { AsyncRuntimeFile, FileInfo } from '@/runtime/next/types';
|
||||
import type { AsyncRuntimeFile } from '@/runtime/next/types';
|
||||
import { load } from 'js-yaml';
|
||||
import { getGitTimestamp } from '@/utils/git-timestamp';
|
||||
import { fumaMatter } from '@/utils/fuma-matter';
|
||||
@@ -15,6 +15,7 @@ import {
|
||||
toImportPath,
|
||||
} from '@/utils/import-formatter';
|
||||
import { getGlobPatterns, isFileSupported } from '@/utils/collections';
|
||||
import type { FileInfo } from '@/runtime/shared';
|
||||
|
||||
async function readFileWithCache(file: string): Promise<string> {
|
||||
const cached = fileCache.read<string>('read-file', file);
|
||||
@@ -57,7 +58,7 @@ export async function generateJS(
|
||||
getImportCode({
|
||||
type: 'namespace',
|
||||
name: importId,
|
||||
specifier: `${toImportPath(file.absolutePath, importPath)}?${params.join('&')}`,
|
||||
specifier: `${toImportPath(file.fullPath, importPath)}?${params.join('&')}`,
|
||||
}),
|
||||
);
|
||||
|
||||
@@ -69,9 +70,9 @@ export async function generateJS(
|
||||
|
||||
async function getMetaEntries(collection: MetaCollection, files: FileInfo[]) {
|
||||
const items = files.map(async (file) => {
|
||||
const source = await readFileWithCache(file.absolutePath).catch(() => '');
|
||||
const source = await readFileWithCache(file.fullPath).catch(() => '');
|
||||
let data =
|
||||
source.length === 0 ? {} : parseMetaEntry(file.absolutePath, source);
|
||||
source.length === 0 ? {} : parseMetaEntry(file.fullPath, source);
|
||||
|
||||
if (collection?.schema) {
|
||||
data = await validate(
|
||||
@@ -79,9 +80,9 @@ export async function generateJS(
|
||||
data,
|
||||
{
|
||||
source,
|
||||
path: file.absolutePath,
|
||||
path: file.fullPath,
|
||||
},
|
||||
`invalid data in ${file.absolutePath}`,
|
||||
`invalid data in ${file.fullPath}`,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -110,7 +111,7 @@ export async function generateJS(
|
||||
|
||||
const entries = files.map(async (file) => {
|
||||
const parsed = fumaMatter(
|
||||
await readFileWithCache(file.absolutePath).catch(() => ''),
|
||||
await readFileWithCache(file.fullPath).catch(() => ''),
|
||||
);
|
||||
let data = parsed.data;
|
||||
|
||||
@@ -118,14 +119,14 @@ export async function generateJS(
|
||||
data = await validate(
|
||||
collection.schema,
|
||||
parsed.data,
|
||||
{ path: file.absolutePath, source: parsed.content },
|
||||
`invalid frontmatter in ${file.absolutePath}`,
|
||||
{ path: file.fullPath, source: parsed.content },
|
||||
`invalid frontmatter in ${file.fullPath}`,
|
||||
);
|
||||
}
|
||||
|
||||
let lastModified: Date | undefined;
|
||||
if (config.global?.lastModifiedTime === 'git') {
|
||||
lastModified = await getGitTimestamp(file.absolutePath);
|
||||
lastModified = await getGitTimestamp(file.fullPath);
|
||||
}
|
||||
|
||||
return JSON.stringify({
|
||||
@@ -191,15 +192,15 @@ async function getCollectionFiles(
|
||||
dirs.map(async (dir) => {
|
||||
const result = await glob(patterns, {
|
||||
cwd: path.resolve(dir),
|
||||
absolute: true,
|
||||
});
|
||||
|
||||
for (const item of result) {
|
||||
if (!isFileSupported(item, collection)) continue;
|
||||
const fullPath = path.join(dir, item);
|
||||
|
||||
files.set(item, {
|
||||
path: path.relative(dir, item),
|
||||
absolutePath: item,
|
||||
files.set(fullPath, {
|
||||
path: item,
|
||||
fullPath,
|
||||
});
|
||||
}
|
||||
}),
|
||||
|
||||
@@ -4,7 +4,7 @@ import {
|
||||
type PageData,
|
||||
type Source,
|
||||
} from 'fumadocs-core/source';
|
||||
import type { DocOut, Runtime } from './types';
|
||||
import type { DocOut, MetaOut, Runtime } from './types';
|
||||
import type { CompiledMDXProperties } from '@/mdx/build-mdx';
|
||||
import * as fs from 'node:fs/promises';
|
||||
import { type FileInfo, missingProcessedMarkdown } from '@/runtime/shared';
|
||||
@@ -38,9 +38,9 @@ export const _runtime: Runtime = {
|
||||
meta(files) {
|
||||
return files.map((file) => {
|
||||
return {
|
||||
info: file.info,
|
||||
...file.data,
|
||||
_file: file.info,
|
||||
};
|
||||
} satisfies MetaOut;
|
||||
}) as any;
|
||||
},
|
||||
docs(docs, metas) {
|
||||
|
||||
@@ -24,9 +24,8 @@ export interface AsyncRuntimeFile {
|
||||
export type DocOut<Schema extends StandardSchemaV1 = StandardSchemaV1> =
|
||||
DocCollectionEntry<StandardSchemaV1.InferOutput<Schema>>;
|
||||
|
||||
export type MetaOut<Schema extends StandardSchemaV1> = MetaCollectionEntry<
|
||||
StandardSchemaV1.InferOutput<Schema>
|
||||
>;
|
||||
export type MetaOut<Schema extends StandardSchemaV1 = StandardSchemaV1> =
|
||||
MetaCollectionEntry<StandardSchemaV1.InferOutput<Schema>>;
|
||||
|
||||
export interface Runtime {
|
||||
doc: <C>(
|
||||
|
||||
@@ -12,6 +12,7 @@ import {
|
||||
DocCollectionEntry,
|
||||
DocData,
|
||||
FileInfo,
|
||||
MetaCollectionEntry,
|
||||
missingProcessedMarkdown,
|
||||
} from '@/runtime/shared';
|
||||
import fs from 'node:fs/promises';
|
||||
@@ -29,7 +30,7 @@ export interface ServerCreate<Config> extends BaseCreate<Config> {
|
||||
) => Promise<
|
||||
Source<{
|
||||
pageData: DocCollectionEntry<DocOut>;
|
||||
metaData: MetaOut;
|
||||
metaData: MetaCollectionEntry<MetaOut>;
|
||||
}>
|
||||
>;
|
||||
|
||||
@@ -39,13 +40,20 @@ export interface ServerCreate<Config> extends BaseCreate<Config> {
|
||||
) => Promise<
|
||||
Source<{
|
||||
pageData: AsyncDocCollectionEntry<DocOut>;
|
||||
metaData: MetaOut;
|
||||
metaData: MetaCollectionEntry<MetaOut>;
|
||||
}>
|
||||
>;
|
||||
}
|
||||
|
||||
export function fromConfig<Config>(): ServerCreate<Config> {
|
||||
const base = fromConfigBase<Config>();
|
||||
function fileInfo(file: string, base: string): FileInfo {
|
||||
return {
|
||||
path: file,
|
||||
fullPath: path.join(base, file),
|
||||
};
|
||||
}
|
||||
|
||||
function mapDocData(entry: CompiledMDXFile<any>): DocData {
|
||||
return {
|
||||
body: entry.default,
|
||||
@@ -99,16 +107,23 @@ export function fromConfig<Config>(): ServerCreate<Config> {
|
||||
};
|
||||
}
|
||||
|
||||
function mapMetaData<Data>(
|
||||
info: FileInfo,
|
||||
content: Data,
|
||||
): MetaCollectionEntry<Data> {
|
||||
return {
|
||||
info,
|
||||
...content,
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
...base,
|
||||
|
||||
async sourceAsync(doc, meta) {
|
||||
const virtualFiles: Promise<VirtualFile>[] = [
|
||||
...Object.entries(doc).map(async ([file, content]) => {
|
||||
const info: FileInfo = {
|
||||
path: file,
|
||||
fullPath: path.join(content.base, file),
|
||||
};
|
||||
const info = fileInfo(file, content.base);
|
||||
|
||||
return {
|
||||
type: 'page',
|
||||
@@ -118,11 +133,13 @@ export function fromConfig<Config>(): ServerCreate<Config> {
|
||||
} satisfies VirtualFile;
|
||||
}),
|
||||
...Object.entries(meta).map(async ([file, content]) => {
|
||||
const info = fileInfo(file, content.base);
|
||||
|
||||
return {
|
||||
type: 'meta',
|
||||
path: file,
|
||||
absolutePath: path.join(content.base, file),
|
||||
data: await content(),
|
||||
path: info.path,
|
||||
absolutePath: info.fullPath,
|
||||
data: mapMetaData(info, await content()),
|
||||
} satisfies VirtualFile;
|
||||
}),
|
||||
];
|
||||
@@ -132,24 +149,23 @@ export function fromConfig<Config>(): ServerCreate<Config> {
|
||||
async sourceLazy(doc, meta) {
|
||||
const virtualFiles: Promise<VirtualFile>[] = [
|
||||
...Object.entries(doc.head).map(async ([file, frontmatter]) => {
|
||||
const info: FileInfo = {
|
||||
path: file,
|
||||
fullPath: path.join(doc.base, file),
|
||||
};
|
||||
const info = fileInfo(file, doc.base);
|
||||
|
||||
return {
|
||||
type: 'page',
|
||||
path: file,
|
||||
path: info.path,
|
||||
absolutePath: info.fullPath,
|
||||
data: mapPageDataLazy(info, await frontmatter(), doc.body[file]),
|
||||
} satisfies VirtualFile;
|
||||
}),
|
||||
...Object.entries(meta).map(async ([file, content]) => {
|
||||
const info = fileInfo(file, content.base);
|
||||
|
||||
return {
|
||||
type: 'meta',
|
||||
path: file,
|
||||
absolutePath: path.join(content.base, file),
|
||||
data: await content(),
|
||||
path: info.path,
|
||||
absolutePath: info.fullPath,
|
||||
data: mapMetaData(info, await content()),
|
||||
} satisfies VirtualFile;
|
||||
}),
|
||||
];
|
||||
|
||||
@@ -9,7 +9,6 @@ import type {
|
||||
import type { ProcessorOptions } from '@mdx-js/mdx';
|
||||
import { pathToFileURL } from 'node:url';
|
||||
import { buildConfig } from '@/config/build';
|
||||
import type { Plugin } from 'esbuild';
|
||||
|
||||
export function findConfigFile(): string {
|
||||
return path.resolve('source.config.ts');
|
||||
@@ -28,39 +27,6 @@ let cache: {
|
||||
config: Promise<LoadedConfig>;
|
||||
} | null = null;
|
||||
|
||||
async function isZod3() {
|
||||
try {
|
||||
const content = JSON.parse(
|
||||
(await fs.readFile('node_modules/zod/package.json')).toString(),
|
||||
);
|
||||
const version = content.version;
|
||||
|
||||
return typeof version === 'string' && version.startsWith('3.');
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
function createCompatZodPlugin(): Plugin {
|
||||
return {
|
||||
name: 'replace-zod-import',
|
||||
async setup(build) {
|
||||
const usingZod3 = await isZod3();
|
||||
if (!usingZod3) return;
|
||||
|
||||
console.warn(
|
||||
'[Fumadocs MDX] Noticed Zod v3 in your node_modules, we recommend upgrading to Zod v4 for better compatibility.',
|
||||
);
|
||||
build.onResolve({ filter: /^fumadocs-mdx\/config$/ }, () => {
|
||||
return {
|
||||
path: 'fumadocs-mdx/config/zod-3',
|
||||
external: true,
|
||||
};
|
||||
});
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
async function compileConfig(configPath: string, outDir: string) {
|
||||
const { build } = await import('esbuild');
|
||||
|
||||
@@ -73,7 +39,6 @@ async function compileConfig(configPath: string, outDir: string) {
|
||||
platform: 'node',
|
||||
format: 'esm',
|
||||
packages: 'external',
|
||||
plugins: [createCompatZodPlugin()],
|
||||
outExtension: {
|
||||
'.js': '.mjs',
|
||||
},
|
||||
|
||||
@@ -5,7 +5,6 @@ export default defineConfig({
|
||||
'./src/{index,loader-mdx,bin}.ts',
|
||||
'./src/{config,next,vite,bun}/index.ts',
|
||||
'./src/node/loader.ts',
|
||||
'./src/config/zod-3.ts',
|
||||
'./src/runtime/next/{index,async}.ts',
|
||||
'./src/runtime/vite/{browser,server}.ts',
|
||||
],
|
||||
|
||||
Reference in New Issue
Block a user