mirror of
https://github.com/hanzo-docs/docs.git
synced 2026-10-26 23:49:51 +00:00
Support Standard Schema for collection schema
This commit is contained in:
@@ -0,0 +1,5 @@
|
||||
---
|
||||
'fumadocs-mdx': minor
|
||||
---
|
||||
|
||||
Support Standard Schema for collection `schema`
|
||||
@@ -62,6 +62,7 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"@fumadocs/mdx-remote": "workspace:*",
|
||||
"@standard-schema/spec": "^1.0.0",
|
||||
"@types/cross-spawn": "^6.0.6",
|
||||
"@types/mdast": "^4.0.3",
|
||||
"@types/mdx": "^2.0.13",
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import { type ZodTypeAny, type z } from 'zod';
|
||||
import { type ProcessorOptions } from '@mdx-js/mdx';
|
||||
import { type MDXOptions } from '@/utils/build-mdx';
|
||||
import {
|
||||
@@ -7,6 +6,7 @@ import {
|
||||
type MarkdownProps,
|
||||
} from '@/config/types';
|
||||
import { frontmatterSchema, metaSchema } from '@/utils/schema';
|
||||
import { StandardSchemaV1 } from '@standard-schema/spec';
|
||||
|
||||
export interface TransformContext {
|
||||
path: string;
|
||||
@@ -34,13 +34,14 @@ export interface BaseCollection<Schema> {
|
||||
schema?: Schema | ((ctx: TransformContext) => Schema);
|
||||
}
|
||||
|
||||
export interface MetaCollection<Schema extends ZodTypeAny = ZodTypeAny>
|
||||
extends BaseCollection<Schema> {
|
||||
export interface MetaCollection<
|
||||
Schema extends StandardSchemaV1 = StandardSchemaV1,
|
||||
> extends BaseCollection<Schema> {
|
||||
type: 'meta';
|
||||
}
|
||||
|
||||
export interface DocCollection<
|
||||
Schema extends ZodTypeAny = ZodTypeAny,
|
||||
Schema extends StandardSchemaV1 = StandardSchemaV1,
|
||||
Async extends boolean = boolean,
|
||||
> extends BaseCollection<Schema> {
|
||||
type: 'doc';
|
||||
@@ -55,7 +56,7 @@ export interface DocCollection<
|
||||
|
||||
export function defineCollections<
|
||||
T extends 'doc' | 'meta',
|
||||
Schema extends ZodTypeAny = ZodTypeAny,
|
||||
Schema extends StandardSchemaV1 = StandardSchemaV1,
|
||||
Async extends boolean = false,
|
||||
>(
|
||||
options: { type: T } & (T extends 'doc'
|
||||
@@ -70,15 +71,15 @@ export function defineCollections<
|
||||
|
||||
runtime: T extends 'doc'
|
||||
? Async extends true
|
||||
? z.infer<Schema> &
|
||||
? StandardSchemaV1.InferOutput<Schema> &
|
||||
BaseCollectionEntry & {
|
||||
load: () => Promise<MarkdownProps>;
|
||||
}
|
||||
: Omit<MarkdownProps, keyof z.infer<Schema>> &
|
||||
z.infer<Schema> &
|
||||
: Omit<MarkdownProps, keyof StandardSchemaV1.InferOutput<Schema>> &
|
||||
StandardSchemaV1.InferOutput<Schema> &
|
||||
BaseCollectionEntry
|
||||
: typeof options extends MetaCollection
|
||||
? z.infer<Schema> & BaseCollectionEntry
|
||||
? StandardSchemaV1.InferOutput<Schema> & BaseCollectionEntry
|
||||
: never;
|
||||
};
|
||||
} {
|
||||
@@ -91,8 +92,8 @@ export function defineCollections<
|
||||
}
|
||||
|
||||
export function defineDocs<
|
||||
DocData extends ZodTypeAny = typeof frontmatterSchema,
|
||||
MetaData extends ZodTypeAny = typeof metaSchema,
|
||||
DocData extends StandardSchemaV1 = typeof frontmatterSchema,
|
||||
MetaData extends StandardSchemaV1 = typeof metaSchema,
|
||||
DocAsync extends boolean = false,
|
||||
>(options?: {
|
||||
/**
|
||||
|
||||
@@ -6,6 +6,7 @@ import { getConfigHash, loadConfigCached } from '@/utils/config-cache';
|
||||
import { buildMDX } from '@/utils/build-mdx';
|
||||
import { formatError } from '@/utils/format-error';
|
||||
import { getGitTimestamp } from './utils/git-timestamp';
|
||||
import type { StandardSchemaV1 } from '@standard-schema/spec';
|
||||
|
||||
export interface Options {
|
||||
/**
|
||||
@@ -70,7 +71,7 @@ export default async function loader(
|
||||
if (collection?.schema) {
|
||||
let schema = collection.schema;
|
||||
|
||||
if (typeof schema === 'function') {
|
||||
if (typeof schema === 'function' && !('~standard' in schema)) {
|
||||
schema = schema({
|
||||
async buildMDX(v, options = mdxOptions) {
|
||||
const res = await buildMDX(
|
||||
@@ -86,17 +87,21 @@ export default async function loader(
|
||||
});
|
||||
}
|
||||
|
||||
const result = await schema.safeParseAsync(matter.data);
|
||||
if (result.error) {
|
||||
callback(
|
||||
new Error(
|
||||
formatError(`invalid frontmatter in ${filePath}:`, result.error),
|
||||
),
|
||||
if ('~standard' in schema) {
|
||||
const result = await (schema as StandardSchemaV1)['~standard'].validate(
|
||||
matter.data,
|
||||
);
|
||||
return;
|
||||
}
|
||||
if (result.issues) {
|
||||
callback(
|
||||
new Error(
|
||||
formatError(`invalid frontmatter in ${filePath}:`, result.issues),
|
||||
),
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
matter.data = result.data as Record<string, unknown>;
|
||||
matter.data = result.value as Record<string, unknown>;
|
||||
}
|
||||
}
|
||||
|
||||
let timestamp: number | undefined;
|
||||
|
||||
@@ -1,32 +1,8 @@
|
||||
import { type ZodError, type ZodFormattedError } from 'zod';
|
||||
import { StandardSchemaV1 } from '@standard-schema/spec';
|
||||
|
||||
export function formatError(message: string, error: ZodError): string {
|
||||
const lines: string[] = [];
|
||||
|
||||
function walk(
|
||||
key: string | undefined,
|
||||
{ _errors, ...rest }: ZodFormattedError<any>,
|
||||
padStart = 0,
|
||||
): void {
|
||||
if (key !== undefined || _errors.length > 0) {
|
||||
const text = key
|
||||
? `${key}: ${_errors.join('\n ')}`
|
||||
: _errors.join('\n');
|
||||
|
||||
lines.push(
|
||||
text
|
||||
.split('\n')
|
||||
.map((line) => `${' '.repeat(padStart)}${line}`)
|
||||
.join('\n'),
|
||||
);
|
||||
}
|
||||
|
||||
for (const [k, v] of Object.entries(rest)) {
|
||||
walk(key ? `${key}.${k}` : k, v as ZodFormattedError<any>, padStart + 2);
|
||||
}
|
||||
}
|
||||
|
||||
walk(undefined, error.format());
|
||||
|
||||
return [message, ...lines].join('\n');
|
||||
export function formatError(
|
||||
message: string,
|
||||
issues: readonly StandardSchemaV1.Issue[],
|
||||
): string {
|
||||
return `${message}:\n${issues.map((issue) => ` ${issue.path}: ${issue.message}`).join('\n')}`;
|
||||
}
|
||||
|
||||
@@ -6,27 +6,30 @@ import { formatError } from '@/utils/format-error';
|
||||
import { generateJS } from '@/map/generate';
|
||||
import { defineCollections } from '@/config';
|
||||
|
||||
test('format errors', () => {
|
||||
test('format errors', async () => {
|
||||
const schema = z.object({
|
||||
text: z.string(),
|
||||
obj: z.object({
|
||||
key: z.number(),
|
||||
value: z.number(),
|
||||
}),
|
||||
value: z.string().max(4),
|
||||
});
|
||||
|
||||
const result = schema.safeParse({
|
||||
const result = await schema['~standard'].validate({
|
||||
text: 4,
|
||||
obj: {},
|
||||
obj: {
|
||||
value: 'string',
|
||||
},
|
||||
value: 'asfdfsdfsdfsd',
|
||||
});
|
||||
|
||||
if (result.error)
|
||||
expect(formatError('in index.mdx:', result.error)).toMatchInlineSnapshot(`
|
||||
"in index.mdx:
|
||||
if (result.issues)
|
||||
expect(formatError('in index.mdx:', result.issues)).toMatchInlineSnapshot(`
|
||||
"in index.mdx::
|
||||
text: Expected string, received number
|
||||
obj:
|
||||
obj.key: Required
|
||||
obj,key: Required
|
||||
obj,value: Expected number, received string
|
||||
value: String must contain at most 4 character(s)"
|
||||
`);
|
||||
});
|
||||
|
||||
Generated
+18
-11
@@ -40,7 +40,7 @@ importers:
|
||||
version: 6.0.1
|
||||
tsup:
|
||||
specifier: 8.3.6
|
||||
version: 8.3.6(@swc/[email protected](@swc/[email protected]))([email protected])([email protected])([email protected])([email protected])([email protected])
|
||||
version: 8.3.6(@swc/[email protected])([email protected])([email protected])([email protected])([email protected])([email protected])
|
||||
turbo:
|
||||
specifier: 2.4.0
|
||||
version: 2.4.0
|
||||
@@ -810,6 +810,9 @@ importers:
|
||||
'@fumadocs/mdx-remote':
|
||||
specifier: workspace:*
|
||||
version: link:../mdx-remote
|
||||
'@standard-schema/spec':
|
||||
specifier: ^1.0.0
|
||||
version: 1.0.0
|
||||
'@types/cross-spawn':
|
||||
specifier: ^6.0.6
|
||||
version: 6.0.6
|
||||
@@ -848,7 +851,7 @@ importers:
|
||||
version: 6.0.3
|
||||
webpack:
|
||||
specifier: ^5.97.1
|
||||
version: 5.97.1(@swc/[email protected](@swc/[email protected]))([email protected])
|
||||
version: 5.97.1(@swc/[email protected])([email protected])
|
||||
|
||||
packages/mdx-remote:
|
||||
dependencies:
|
||||
@@ -3090,6 +3093,9 @@ packages:
|
||||
resolution: {integrity: sha512-tlqY9xq5ukxTUZBmoOp+m61cqwQD5pHJtFY3Mn8CA8ps6yghLH/Hw8UPdqg4OLmFW3IFlcXnQNmo/dh8HzXYIQ==}
|
||||
engines: {node: '>=18'}
|
||||
|
||||
'@standard-schema/[email protected]':
|
||||
resolution: {integrity: sha512-m2bOd0f2RT9k8QJx1JN85cZYyH1RqFBdlwtkSlf4tBDYLCiiZnv1fIIwacK6cqwXavOydf0NPToMQgpKq+dVlA==}
|
||||
|
||||
'@swc/[email protected]':
|
||||
resolution: {integrity: sha512-pOANQegUTAriW7jq3SSMZGM5l89yLVMs48R0F2UG6UZsH04SiViCnDctOGlA/Sa++25C+rL9MGMYM1jDLylBbg==}
|
||||
engines: {node: '>=10'}
|
||||
@@ -8947,6 +8953,8 @@ snapshots:
|
||||
|
||||
'@sindresorhus/[email protected]': {}
|
||||
|
||||
'@standard-schema/[email protected]': {}
|
||||
|
||||
'@swc/[email protected]':
|
||||
optional: true
|
||||
|
||||
@@ -8977,7 +8985,7 @@ snapshots:
|
||||
'@swc/[email protected]':
|
||||
optional: true
|
||||
|
||||
'@swc/[email protected](@swc/[email protected])':
|
||||
'@swc/[email protected]':
|
||||
dependencies:
|
||||
'@swc/counter': 0.1.3
|
||||
'@swc/types': 0.1.17
|
||||
@@ -8992,7 +9000,6 @@ snapshots:
|
||||
'@swc/core-win32-arm64-msvc': 1.10.12
|
||||
'@swc/core-win32-ia32-msvc': 1.10.12
|
||||
'@swc/core-win32-x64-msvc': 1.10.12
|
||||
'@swc/helpers': 0.5.15
|
||||
optional: true
|
||||
|
||||
'@swc/[email protected]': {}
|
||||
@@ -13140,16 +13147,16 @@ snapshots:
|
||||
|
||||
[email protected]: {}
|
||||
|
||||
[email protected](@swc/[email protected](@swc/[email protected]))([email protected])([email protected](@swc/[email protected](@swc/[email protected]))([email protected])):
|
||||
[email protected](@swc/[email protected])([email protected])([email protected](@swc/[email protected])([email protected])):
|
||||
dependencies:
|
||||
'@jridgewell/trace-mapping': 0.3.25
|
||||
jest-worker: 27.5.1
|
||||
schema-utils: 3.3.0
|
||||
serialize-javascript: 6.0.2
|
||||
terser: 5.37.0
|
||||
webpack: 5.97.1(@swc/[email protected](@swc/[email protected]))([email protected])
|
||||
webpack: 5.97.1(@swc/[email protected])([email protected])
|
||||
optionalDependencies:
|
||||
'@swc/core': 1.10.12(@swc/[email protected])
|
||||
'@swc/core': 1.10.12
|
||||
esbuild: 0.24.2
|
||||
|
||||
[email protected]:
|
||||
@@ -13234,7 +13241,7 @@ snapshots:
|
||||
|
||||
[email protected]: {}
|
||||
|
||||
[email protected](@swc/[email protected](@swc/[email protected]))([email protected])([email protected])([email protected])([email protected])([email protected]):
|
||||
[email protected](@swc/[email protected])([email protected])([email protected])([email protected])([email protected])([email protected]):
|
||||
dependencies:
|
||||
bundle-require: 5.1.0([email protected])
|
||||
cac: 6.7.14
|
||||
@@ -13253,7 +13260,7 @@ snapshots:
|
||||
tinyglobby: 0.2.10
|
||||
tree-kill: 1.2.2
|
||||
optionalDependencies:
|
||||
'@swc/core': 1.10.12(@swc/[email protected])
|
||||
'@swc/core': 1.10.12
|
||||
postcss: 8.5.1
|
||||
typescript: 5.7.3
|
||||
transitivePeerDependencies:
|
||||
@@ -13610,7 +13617,7 @@ snapshots:
|
||||
|
||||
[email protected]: {}
|
||||
|
||||
[email protected](@swc/[email protected](@swc/[email protected]))([email protected]):
|
||||
[email protected](@swc/[email protected])([email protected]):
|
||||
dependencies:
|
||||
'@types/eslint-scope': 3.7.7
|
||||
'@types/estree': 1.0.6
|
||||
@@ -13632,7 +13639,7 @@ snapshots:
|
||||
neo-async: 2.6.2
|
||||
schema-utils: 3.3.0
|
||||
tapable: 2.2.1
|
||||
terser-webpack-plugin: 5.3.10(@swc/[email protected](@swc/[email protected]))([email protected])([email protected](@swc/[email protected](@swc/[email protected]))([email protected]))
|
||||
terser-webpack-plugin: 5.3.10(@swc/[email protected])([email protected])([email protected](@swc/[email protected])([email protected]))
|
||||
watchpack: 2.4.2
|
||||
webpack-sources: 3.2.3
|
||||
transitivePeerDependencies:
|
||||
|
||||
Reference in New Issue
Block a user