mirror of
https://github.com/hanzo-docs/docs.git
synced 2026-10-25 04:20:02 +00:00
OpenAPI: Improve cURL generator
This commit is contained in:
@@ -0,0 +1,5 @@
|
||||
---
|
||||
'fumadocs-openapi': minor
|
||||
---
|
||||
|
||||
Support to group pages by tags
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
'fumadocs-openapi': patch
|
||||
---
|
||||
|
||||
Improve Curl example generator
|
||||
@@ -32,10 +32,18 @@ export interface Config extends GenerateOptions {
|
||||
/**
|
||||
* Group output using folders (Only works on `operation` mode)
|
||||
*
|
||||
* @deprecated Use `groupBy` instead
|
||||
* @defaultValue false
|
||||
*/
|
||||
groupByFolder?: boolean;
|
||||
|
||||
/**
|
||||
* Group output using folders (Only works on `operation` mode)
|
||||
*
|
||||
* @defaultValue 'none'
|
||||
*/
|
||||
groupBy?: 'tag' | 'route' | 'none';
|
||||
|
||||
cwd?: string;
|
||||
}
|
||||
|
||||
@@ -45,7 +53,7 @@ export async function generateFiles({
|
||||
name: nameFn,
|
||||
per = 'file',
|
||||
cwd = process.cwd(),
|
||||
groupByFolder = false,
|
||||
groupBy = 'none',
|
||||
...options
|
||||
}: Config): Promise<void> {
|
||||
const outputDir = join(cwd, output);
|
||||
@@ -66,27 +74,46 @@ export async function generateFiles({
|
||||
}
|
||||
|
||||
if (per === 'operation') {
|
||||
const routeFolders = new Set<string>();
|
||||
const metaFiles = new Set<string>();
|
||||
const results = await generateOperations(path, options);
|
||||
|
||||
await Promise.all(
|
||||
results.map(async (result) => {
|
||||
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`);
|
||||
let outPath;
|
||||
if (!result.method.operationId) return;
|
||||
const id =
|
||||
result.method.operationId.split('.').at(-1) ??
|
||||
result.method.operationId;
|
||||
|
||||
if (groupByFolder && !routeFolders.has(dirname(outPath))) {
|
||||
routeFolders.add(dirname(outPath));
|
||||
if (
|
||||
groupBy === 'tag' &&
|
||||
result.method.tags &&
|
||||
result.method.tags.length > 0
|
||||
) {
|
||||
if (result.method.tags.length > 1)
|
||||
console.warn(
|
||||
`${result.route.path} has more than 1 tag, which isn't allowed under 'groupBy: tag'. Only the first tag will be considered.`,
|
||||
);
|
||||
|
||||
if (result.route.summary) {
|
||||
const metaFile = join(dirname(outPath), 'meta.json');
|
||||
outPath = join(
|
||||
outputDir,
|
||||
filename,
|
||||
result.method.tags[0],
|
||||
`${getFilename(id)}.mdx`,
|
||||
);
|
||||
} else if (groupBy === 'route') {
|
||||
outPath = join(
|
||||
outputDir,
|
||||
filename,
|
||||
result.route.summary
|
||||
? getFilename(result.route.summary)
|
||||
: getFilenameFromRoute(result.route.path),
|
||||
`${getFilename(id)}.mdx`,
|
||||
);
|
||||
|
||||
const metaFile = join(dirname(outPath), 'meta.json');
|
||||
if (result.route.summary && !metaFiles.has(metaFile)) {
|
||||
metaFiles.add(metaFile);
|
||||
|
||||
await write(
|
||||
metaFile,
|
||||
@@ -96,6 +123,8 @@ export async function generateFiles({
|
||||
);
|
||||
console.log(`Generated Meta: ${metaFile}`);
|
||||
}
|
||||
} else {
|
||||
outPath = join(outputDir, filename, `${getFilename(id)}.mdx`);
|
||||
}
|
||||
|
||||
await write(outPath, result.content);
|
||||
@@ -122,6 +151,7 @@ export async function generateFiles({
|
||||
function getFilenameFromRoute(path: string): string {
|
||||
return (
|
||||
path
|
||||
.replaceAll('.', '/')
|
||||
.split('/')
|
||||
.filter((v) => !v.startsWith('{') && !v.endsWith('}'))
|
||||
.at(-1) ?? ''
|
||||
|
||||
@@ -70,9 +70,9 @@ export interface GenerateTagOutput {
|
||||
}
|
||||
|
||||
export interface GenerateOperationOutput {
|
||||
id: string;
|
||||
content: string;
|
||||
|
||||
method: MethodInformation;
|
||||
route: RouteInformation;
|
||||
}
|
||||
|
||||
@@ -124,7 +124,7 @@ export async function generateOperations(
|
||||
]),
|
||||
options,
|
||||
{
|
||||
title: method.summary ?? method.method,
|
||||
title: method.summary ?? idToTitle(method.operationId),
|
||||
description: method.description,
|
||||
context: {
|
||||
type: 'operation',
|
||||
@@ -135,9 +135,9 @@ export async function generateOperations(
|
||||
);
|
||||
|
||||
return {
|
||||
id: method.operationId,
|
||||
content,
|
||||
route,
|
||||
method,
|
||||
} satisfies GenerateOperationOutput;
|
||||
});
|
||||
}),
|
||||
|
||||
@@ -12,11 +12,35 @@ export function getSampleRequest(endpoint: Endpoint): string {
|
||||
|
||||
s.push(`-H "${header}"`);
|
||||
}
|
||||
|
||||
if (param.in === 'cookie') {
|
||||
const cookie = JSON.stringify(
|
||||
`${param.name}=${toSampleInput(param.sample)}`,
|
||||
);
|
||||
|
||||
s.push(`--cookie ${cookie}`);
|
||||
}
|
||||
}
|
||||
|
||||
if (endpoint.body?.mediaType === 'multipart/form-data')
|
||||
console.warn("Curl sample with form data body isn't supported.");
|
||||
if (endpoint.body) s.push(`-d '${toSampleInput(endpoint.body.sample)}'`);
|
||||
if (endpoint.body?.mediaType === 'multipart/form-data') {
|
||||
const sample = endpoint.body.sample;
|
||||
|
||||
return s.join(' \\\n ');
|
||||
if (sample && typeof sample === 'object') {
|
||||
for (const [key, value] of Object.entries(sample)) {
|
||||
s.push(`-F ${key}=${JSON.stringify(value, null, 2)}`);
|
||||
}
|
||||
}
|
||||
} else if (endpoint.body) {
|
||||
s.push(`-H "Content-Type: application/json"`);
|
||||
s.push(`-d '${toSampleInput(endpoint.body.sample)}'`);
|
||||
}
|
||||
|
||||
return s
|
||||
.flatMap((v, i) =>
|
||||
v
|
||||
.split('\n')
|
||||
.map((line) => (i > 0 ? ` ${line}` : line))
|
||||
.join('\n'),
|
||||
)
|
||||
.join(' \\\n');
|
||||
}
|
||||
|
||||
@@ -38,7 +38,7 @@ function ApiIndicator({ method }: { method: string }): ReactNode {
|
||||
return React.createElement(
|
||||
'span',
|
||||
{
|
||||
className: badgeVariants({ className: 'ms-auto', color }),
|
||||
className: badgeVariants({ className: 'ms-auto text-nowrap', color }),
|
||||
},
|
||||
method,
|
||||
);
|
||||
|
||||
@@ -1,7 +1,10 @@
|
||||
export function idToTitle(id: string): string {
|
||||
const result = [];
|
||||
let result: string[] = [];
|
||||
|
||||
for (const c of id) {
|
||||
if (result.length === 0) result.push(c.toLocaleUpperCase());
|
||||
// ignore the other parts surrounded with '.', like 'migrations.dev' -> 'dev'
|
||||
else if (c === '.') result = [];
|
||||
else if (/^[A-Z]$/.test(c) && result.at(-1) !== ' ') result.push(' ', c);
|
||||
else if (c === '-') result.push(' ');
|
||||
else result.push(c);
|
||||
|
||||
@@ -19,7 +19,7 @@ export const buttonVariants = cva(
|
||||
);
|
||||
|
||||
export const itemVariants = cva(
|
||||
'flex w-full flex-row items-center gap-2 rounded-md px-2 py-1.5 text-fd-muted-foreground transition-colors duration-100 [&_svg]:size-4',
|
||||
'flex w-full flex-row items-center gap-2 rounded-md px-2 py-1.5 text-fd-muted-foreground transition-colors duration-100 [overflow-wrap:anywhere] [&_svg]:size-4',
|
||||
{
|
||||
variants: {
|
||||
active: {
|
||||
|
||||
Reference in New Issue
Block a user