OpenAPI: support webhook badge

This commit is contained in:
Fuma Nama
2025-11-25 21:24:28 +08:00
parent 4b2a7e4ec2
commit 196d71c6db
3 changed files with 65 additions and 29 deletions
+5
View File
@@ -0,0 +1,5 @@
---
'fumadocs-openapi': patch
---
support webhook badge
+21 -12
View File
@@ -16,12 +16,15 @@ declare module 'fumadocs-core/source' {
/**
* Added by Fumadocs OpenAPI
*/
_openapi?: {
method?: string;
};
_openapi?: InternalOpenAPIMeta;
}
}
export interface InternalOpenAPIMeta {
method?: string;
webhook?: boolean;
}
/**
* Fumadocs Source API integration, pass this to `plugins` array in `loader()`.
*/
@@ -35,19 +38,24 @@ export function openapiPlugin(): LoaderPlugin {
const file = this.storage.read(filePath);
if (!file || file.format !== 'page') return node;
const data = file.data;
let method: string | undefined;
const openApiData = file.data._openapi;
if (!openApiData || typeof openApiData !== 'object') return node;
if ('_openapi' in data && typeof data._openapi === 'object') {
method = data._openapi.method;
}
if (method) {
if (openApiData.webhook) {
node.name = (
<>
{node.name}{' '}
<span className="ms-auto border border-current px-1 rounded-lg text-xs text-nowrap font-mono">
Webhook
</span>
</>
);
} else if (openApiData.method) {
node.name = (
<>
{node.name}{' '}
<MethodLabel className="ms-auto text-xs text-nowrap">
{method}
{openApiData.method}
</MethodLabel>
</>
);
@@ -93,7 +101,7 @@ export async function openapiSource(
path: `${baseDir}/${entry.path}`,
data: {
...entry.info,
getAPIPageProps: () => {
getAPIPageProps() {
const props = toBody(entry);
props.showDescription ??= true;
return props;
@@ -103,6 +111,7 @@ export async function openapiSource(
entry.type === 'operation' || entry.type === 'webhook'
? entry.item.method
: undefined,
webhook: entry.type === 'webhook',
},
},
});
+39 -17
View File
@@ -9,6 +9,7 @@ import type { NoReference } from '@/utils/schema';
import Slugger from 'github-slugger';
import { removeUndefined } from '@/utils/remove-undefined';
import type { OutputEntry } from '@/utils/pages/builder';
import type { InternalOpenAPIMeta } from '@/server/source-api';
export interface PagesToTextOptions {
/**
@@ -195,13 +196,19 @@ function generatePage(
document: schemaId,
};
let meta: object | undefined;
let meta: InternalOpenAPIMeta | undefined;
if (page.operations?.length === 1) {
const operation = page.operations[0];
meta = {
method: operation.method.toUpperCase(),
route: operation.path,
};
} else if (page.webhooks?.length === 1) {
const webhook = page.webhooks[0];
meta = {
method: webhook.method.toUpperCase(),
webhook: true,
};
}
@@ -272,23 +279,38 @@ function pageContent({
showTitle,
showDescription,
document,
...props
webhooks,
operations,
}: ApiPageProps): string {
// filter extra properties in props
const operations: OperationItem[] = (props.operations ?? []).map((item) => ({
path: item.path,
method: item.method,
}));
const webhooks: WebhookItem[] = (props.webhooks ?? []).map((item) => ({
name: item.name,
method: item.method,
}));
const propStrs: string[] = [`document={${JSON.stringify(document)}}`];
const propStrs: string[] = [
`document={${JSON.stringify(document)}}`,
`operations={${JSON.stringify(operations)}}`,
`webhooks={${JSON.stringify(webhooks)}}`,
];
// filter extra properties in props
if (webhooks) {
propStrs.push(
`webhooks={${JSON.stringify(
webhooks.map(
(item) =>
({
name: item.name,
method: item.method,
}) satisfies WebhookItem,
),
)}}`,
);
}
if (operations) {
propStrs.push(
`operations={${JSON.stringify(
operations.map(
(item) =>
({
path: item.path,
method: item.method,
}) satisfies OperationItem,
),
)}}`,
);
}
if (showTitle) {
propStrs.push(`showTitle={${JSON.stringify(showTitle)}}`);
}