mirror of
https://github.com/hanzo-docs/docs.git
synced 2026-10-26 06:31:49 +00:00
OpenAPI: Support accessing context information on custom frontmatter
This commit is contained in:
@@ -0,0 +1,5 @@
|
||||
---
|
||||
'fumadocs-openapi': patch
|
||||
---
|
||||
|
||||
Fix generate files on `operation` mode
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
'fumadocs-openapi': minor
|
||||
---
|
||||
|
||||
Support accessing context information on custom frontmatter
|
||||
+2
-3
@@ -10,9 +10,8 @@ apps/docs/shaders/*
|
||||
**/test/out/**/*
|
||||
**/test/fixtures/*.output.*
|
||||
|
||||
apps/docs/content/docs/ui/museum/events.mdx
|
||||
apps/docs/content/docs/ui/museum/operations.mdx
|
||||
apps/docs/content/docs/ui/museum/tickets.mdx
|
||||
apps/docs/content/docs/ui/museum/*.mdx
|
||||
apps/docs/content/docs/ui/museum/!index.mdx
|
||||
|
||||
apps/docs/content/docs/ui/typescript.mdx
|
||||
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
out
|
||||
content/docs/ui/museum/events.mdx
|
||||
content/docs/ui/museum/operations.mdx
|
||||
content/docs/ui/museum/tickets.mdx
|
||||
content/docs/ui/museum/*.mdx
|
||||
!content/docs/ui/museum/index.mdx
|
||||
content/docs/ui/(integrations)/typescript.mdx
|
||||
@@ -1,9 +1,48 @@
|
||||
import { type ReactNode } from 'react';
|
||||
import { cva, type VariantProps } from 'class-variance-authority';
|
||||
import { cn } from '@/utils/cn';
|
||||
|
||||
const badgeVariants = cva(
|
||||
'rounded-full border px-2 py-0.5 text-xs font-medium',
|
||||
{
|
||||
variants: {
|
||||
color: {
|
||||
green:
|
||||
'border-green-400/50 bg-green-400/20 text-green-600 dark:text-green-400',
|
||||
yellow:
|
||||
'border-yellow-400/50 bg-yellow-400/20 text-yellow-600 dark:text-yellow-400',
|
||||
red: 'border-red-400/50 bg-red-400/20 text-red-600 dark:text-red-400',
|
||||
blue: 'border-blue-400/50 bg-blue-400/20 text-blue-600 dark:text-blue-400',
|
||||
orange:
|
||||
'border-orange-400/50 bg-orange-400/20 text-orange-600 dark:text-orange-400',
|
||||
},
|
||||
},
|
||||
},
|
||||
);
|
||||
|
||||
export function getBadgeColor(
|
||||
method: string,
|
||||
): VariantProps<typeof badgeVariants>['color'] {
|
||||
switch (method) {
|
||||
case 'PUT':
|
||||
return 'yellow';
|
||||
case 'PATCH':
|
||||
return 'orange';
|
||||
case 'POST':
|
||||
return 'blue';
|
||||
case 'DELETE':
|
||||
return 'red';
|
||||
default:
|
||||
return 'green';
|
||||
}
|
||||
}
|
||||
|
||||
export function ApiIndicator({ method }: { method: string }): ReactNode {
|
||||
const color = getBadgeColor(method);
|
||||
|
||||
export function ApiIndicator(): ReactNode {
|
||||
return (
|
||||
<span className="ms-auto rounded-full bg-fd-primary px-2 py-0.5 text-xs font-medium text-fd-primary-foreground">
|
||||
API
|
||||
<span className={cn(badgeVariants({ className: 'ms-auto', color }))}>
|
||||
{method}
|
||||
</span>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -5,7 +5,7 @@ import * as path from 'node:path';
|
||||
void OpenAPI.generateFiles({
|
||||
input: ['./*.yaml'],
|
||||
output: './content/docs/ui',
|
||||
per: 'tag',
|
||||
per: 'operation',
|
||||
renderer: {
|
||||
Root(props, child) {
|
||||
return OpenAPI.createElement(
|
||||
|
||||
@@ -6,10 +6,9 @@ import type { SearchIndex } from 'fumadocs-mdx';
|
||||
|
||||
env.loadEnvConfig(process.cwd());
|
||||
|
||||
const path = '.next/server/chunks/fumadocs_search.json';
|
||||
async function main() {
|
||||
const indexes = JSON.parse(
|
||||
(await readFile(path)).toString(),
|
||||
(await readFile('.next/server/chunks/fumadocs_search.json')).toString(),
|
||||
) as SearchIndex[];
|
||||
|
||||
await Promise.all([writeOgImages(indexes), updateSearchIndexes(indexes)]);
|
||||
|
||||
@@ -10,6 +10,10 @@ import { ApiIndicator } from '@/components/ui/api-indicator';
|
||||
const frontmatter = defaultSchemas.frontmatter.extend({
|
||||
preview: z.string().optional(),
|
||||
index: z.boolean().default(false),
|
||||
/**
|
||||
* API routes only
|
||||
*/
|
||||
method: z.string().optional(),
|
||||
});
|
||||
|
||||
export const utils = loader({
|
||||
@@ -30,7 +34,11 @@ export const utils = loader({
|
||||
const data = file.data.data as z.output<typeof frontmatter>;
|
||||
if (!data.full) return node;
|
||||
|
||||
node.name = [node.name, ' ', ApiIndicator()];
|
||||
node.name = [
|
||||
node.name,
|
||||
' ',
|
||||
ApiIndicator({ method: data.method ?? 'API' }),
|
||||
];
|
||||
return node;
|
||||
},
|
||||
},
|
||||
|
||||
@@ -71,6 +71,7 @@ export async function generateFiles({
|
||||
console.log(`Generated: ${outPath}`);
|
||||
}),
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
const results = await generateTags(path, options);
|
||||
|
||||
@@ -3,10 +3,38 @@ import type { OpenAPIV3 as OpenAPI } from 'openapi-types';
|
||||
import { buildRoutes } from '@/build-routes';
|
||||
import { generateDocument } from '@/utils/generate-document';
|
||||
import { idToTitle } from '@/utils/id-to-title';
|
||||
import type { RenderContext } from './types';
|
||||
import type {
|
||||
MethodInformation,
|
||||
RenderContext,
|
||||
RouteInformation,
|
||||
} from './types';
|
||||
import { defaultRenderer, type Renderer } from './render/renderer';
|
||||
import { renderOperation } from './render/operation';
|
||||
|
||||
export type DocumentContext =
|
||||
| {
|
||||
type: 'tag';
|
||||
tag: OpenAPI.TagObject | undefined;
|
||||
routes: RouteInformation[];
|
||||
}
|
||||
| {
|
||||
type: 'operation';
|
||||
|
||||
/**
|
||||
* information of the route
|
||||
*/
|
||||
route: RouteInformation;
|
||||
|
||||
/**
|
||||
* information of the method (API Endpoint)
|
||||
*/
|
||||
endpoint: MethodInformation;
|
||||
}
|
||||
| {
|
||||
type: 'file';
|
||||
routes: RouteInformation[];
|
||||
};
|
||||
|
||||
export interface GenerateOptions
|
||||
extends Pick<
|
||||
RenderContext,
|
||||
@@ -30,6 +58,7 @@ export interface GenerateOptions
|
||||
frontmatter?: (
|
||||
title: string,
|
||||
description: string | undefined,
|
||||
context: DocumentContext,
|
||||
) => Record<string, unknown>;
|
||||
|
||||
renderer?: Partial<Renderer>;
|
||||
@@ -61,10 +90,15 @@ export async function generate(
|
||||
}
|
||||
|
||||
return generateDocument(
|
||||
document.info.title,
|
||||
document.info.description,
|
||||
ctx.renderer.Root({ baseUrl: ctx.baseUrl }, child),
|
||||
options,
|
||||
{
|
||||
...document.info,
|
||||
context: {
|
||||
type: 'file',
|
||||
routes,
|
||||
},
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
@@ -80,12 +114,19 @@ export async function generateOperations(
|
||||
routes.flatMap<Promise<GenerateOperationOutput>>((route) => {
|
||||
return route.methods.map(async (method) => {
|
||||
const content = generateDocument(
|
||||
method.summary ?? method.method,
|
||||
method.description,
|
||||
ctx.renderer.Root({ baseUrl: ctx.baseUrl }, [
|
||||
await renderOperation(route.path, method, ctx, true),
|
||||
await renderOperation(route.path, method, ctx, false),
|
||||
]),
|
||||
options,
|
||||
{
|
||||
title: method.summary ?? method.method,
|
||||
description: method.description,
|
||||
context: {
|
||||
type: 'operation',
|
||||
endpoint: method,
|
||||
route,
|
||||
},
|
||||
},
|
||||
);
|
||||
|
||||
if (!method.operationId)
|
||||
@@ -124,10 +165,17 @@ export async function generateTags(
|
||||
return {
|
||||
tag,
|
||||
content: generateDocument(
|
||||
idToTitle(tag),
|
||||
info?.description,
|
||||
ctx.renderer.Root({ baseUrl: ctx.baseUrl }, child),
|
||||
options,
|
||||
{
|
||||
title: idToTitle(tag),
|
||||
description: info?.description,
|
||||
context: {
|
||||
type: 'tag',
|
||||
tag: info,
|
||||
routes,
|
||||
},
|
||||
},
|
||||
),
|
||||
} satisfies GenerateTagOutput;
|
||||
}),
|
||||
|
||||
@@ -26,7 +26,7 @@ export async function renderOperation(
|
||||
path: string,
|
||||
method: MethodInformation,
|
||||
ctx: RenderContext,
|
||||
noTitle = false,
|
||||
hasHead = true,
|
||||
): Promise<string> {
|
||||
let level = 2;
|
||||
const body = noRef(method.requestBody);
|
||||
@@ -34,7 +34,7 @@ export async function renderOperation(
|
||||
const info: string[] = [];
|
||||
const example: string[] = [];
|
||||
|
||||
if (!noTitle) {
|
||||
if (hasHead) {
|
||||
info.push(
|
||||
heading(
|
||||
level,
|
||||
@@ -43,8 +43,9 @@ export async function renderOperation(
|
||||
),
|
||||
);
|
||||
level++;
|
||||
|
||||
if (method.description) info.push(p(method.description));
|
||||
}
|
||||
if (method.description) info.push(p(method.description));
|
||||
|
||||
info.push(renderPlayground(path, method, ctx));
|
||||
|
||||
|
||||
@@ -1,18 +1,31 @@
|
||||
import { dump } from 'js-yaml';
|
||||
import { defaultRenderer } from '@/render/renderer';
|
||||
import { type GenerateOptions } from '@/generate';
|
||||
import type { DocumentContext, GenerateOptions } from '@/generate';
|
||||
|
||||
export function generateDocument(
|
||||
title: string,
|
||||
description: string | undefined,
|
||||
content: string,
|
||||
options: GenerateOptions,
|
||||
frontmatter: {
|
||||
title: string;
|
||||
description?: string;
|
||||
context: DocumentContext;
|
||||
},
|
||||
): string {
|
||||
const banner = dump({
|
||||
title,
|
||||
description,
|
||||
title: frontmatter.title,
|
||||
description: frontmatter.description,
|
||||
full: true,
|
||||
...options.frontmatter?.(title, description),
|
||||
...(frontmatter.context.type === 'operation'
|
||||
? {
|
||||
method: frontmatter.context.endpoint.method,
|
||||
route: frontmatter.context.route.path,
|
||||
}
|
||||
: undefined),
|
||||
...options.frontmatter?.(
|
||||
frontmatter.title,
|
||||
frontmatter.description,
|
||||
frontmatter.context,
|
||||
),
|
||||
}).trim();
|
||||
|
||||
const finalImports = (
|
||||
|
||||
Reference in New Issue
Block a user