OpenAPI: Support grouping output by folders (per operation)

This commit is contained in:
fuma-nama
2024-08-02 13:41:16 +08:00
parent 5355391378
commit 5acebdd333
5 changed files with 74 additions and 25 deletions
+5
View File
@@ -0,0 +1,5 @@
---
'fumadocs-openapi': minor
---
Support grouping output by folders (per operation)
+9 -15
View File
@@ -2,23 +2,17 @@ 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',
},
const badgeVariants = cva('rounded-full border px-1.5 text-xs font-medium', {
variants: {
color: {
green: 'bg-green-400/20 text-green-600 dark:text-green-400',
yellow: 'bg-yellow-400/20 text-yellow-600 dark:text-yellow-400',
red: 'bg-red-400/20 text-red-600 dark:text-red-400',
blue: 'bg-blue-400/20 text-blue-600 dark:text-blue-400',
orange: 'bg-orange-400/20 text-orange-600 dark:text-orange-400',
},
},
);
});
export function getBadgeColor(
method: string,
+7
View File
@@ -1,6 +1,13 @@
import * as OpenAPI from 'fumadocs-openapi';
import * as Typescript from 'fumadocs-typescript';
import * as path from 'node:path';
import { rimrafSync } from 'rimraf';
rimrafSync('./content/docs/ui/museum', {
filter(v) {
return !v.endsWith('index.mdx');
},
});
void OpenAPI.generateFiles({
input: ['./*.yaml'],
+47 -7
View File
@@ -29,6 +29,13 @@ export interface Config extends GenerateOptions {
*/
name?: (type: 'file' | 'tag', name: string) => string;
/**
* Group output using folders (Only works on `operation` mode)
*
* @defaultValue false
*/
groupByFolder?: boolean;
cwd?: string;
}
@@ -38,6 +45,7 @@ export async function generateFiles({
name: nameFn,
per = 'file',
cwd = process.cwd(),
groupByFolder = false,
...options
}: Config): Promise<void> {
const outputDir = join(cwd, output);
@@ -58,15 +66,38 @@ export async function generateFiles({
}
if (per === 'operation') {
const routeFolders = new Set<string>();
const results = await generateOperations(path, options);
await Promise.all(
results.map(async (result) => {
const outPath = join(
outputDir,
filename,
`${getName(result.id)}.mdx`,
);
const outPath = groupByFolder
? join(
outputDir,
filename,
result.route.summary
? getFilename(result.route.summary)
: getFilenameFromRoute(result.route.path),
`${getFilename(result.id)}.mdx`,
)
: join(outputDir, filename, `${getFilename(result.id)}.mdx`);
if (groupByFolder && !routeFolders.has(dirname(outPath))) {
routeFolders.add(dirname(outPath));
if (result.route.summary) {
const metaFile = join(dirname(outPath), 'meta.json');
await write(
metaFile,
JSON.stringify({
title: result.route.summary,
}),
);
console.log(`Generated Meta: ${metaFile}`);
}
}
await write(outPath, result.content);
console.log(`Generated: ${outPath}`);
}),
@@ -78,7 +109,7 @@ export async function generateFiles({
for (const result of results) {
let tagName = result.tag;
tagName = nameFn?.('tag', tagName) ?? getName(tagName);
tagName = nameFn?.('tag', tagName) ?? getFilename(tagName);
const outPath = join(outputDir, filename, `${tagName}.mdx`);
await write(outPath, result.content);
@@ -88,7 +119,16 @@ export async function generateFiles({
);
}
function getName(s: string): string {
function getFilenameFromRoute(path: string): string {
return (
path
.split('/')
.filter((v) => !v.startsWith('{') && !v.endsWith('}'))
.at(-1) ?? ''
);
}
function getFilename(s: string): string {
return s
.replace(/[A-Z]/g, (match, idx: number) =>
idx === 0 ? match : `-${match.toLowerCase()}`,
+6 -3
View File
@@ -72,6 +72,8 @@ export interface GenerateTagOutput {
export interface GenerateOperationOutput {
id: string;
content: string;
route: RouteInformation;
}
export async function generate(
@@ -113,6 +115,9 @@ export async function generateOperations(
return await Promise.all(
routes.flatMap<Promise<GenerateOperationOutput>>((route) => {
return route.methods.map(async (method) => {
if (!method.operationId)
throw new Error('Operation ID is required for generating docs.');
const content = generateDocument(
ctx.renderer.Root({ baseUrl: ctx.baseUrl }, [
await renderOperation(route.path, method, ctx, false),
@@ -129,12 +134,10 @@ export async function generateOperations(
},
);
if (!method.operationId)
throw new Error('Operation ID is required for generating docs.');
return {
id: method.operationId,
content,
route,
} satisfies GenerateOperationOutput;
});
}),