mirror of
https://github.com/hanzo-docs/docs.git
synced 2026-10-25 04:20:02 +00:00
OpenAPI: Support grouping output by folders (per operation)
This commit is contained in:
@@ -0,0 +1,5 @@
|
||||
---
|
||||
'fumadocs-openapi': minor
|
||||
---
|
||||
|
||||
Support grouping output by folders (per operation)
|
||||
@@ -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,
|
||||
|
||||
@@ -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'],
|
||||
|
||||
@@ -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()}`,
|
||||
|
||||
@@ -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;
|
||||
});
|
||||
}),
|
||||
|
||||
Reference in New Issue
Block a user