mirror of
https://github.com/hanzo-docs/docs.git
synced 2026-10-25 04:20:02 +00:00
OpenAPI: Require peer dep of Fumadocs 16.2.0
This commit is contained in:
@@ -0,0 +1,5 @@
|
||||
---
|
||||
'fumadocs-openapi': minor
|
||||
---
|
||||
|
||||
Require peer dep of Fumadocs 16.2.0
|
||||
@@ -65,10 +65,7 @@
|
||||
"@scalar/openapi-parser": "0.23.2",
|
||||
"ajv": "^8.17.1",
|
||||
"class-variance-authority": "^0.7.1",
|
||||
"fumadocs-core": "workspace:*",
|
||||
"fumadocs-ui": "workspace:*",
|
||||
"github-slugger": "^2.0.0",
|
||||
"gray-matter": "^4.0.3",
|
||||
"hast-util-to-jsx-runtime": "^2.3.6",
|
||||
"js-yaml": "^4.1.1",
|
||||
"next-themes": "^0.4.6",
|
||||
@@ -76,10 +73,11 @@
|
||||
"react-hook-form": "^7.66.0",
|
||||
"remark": "^15.0.1",
|
||||
"remark-rehype": "^11.1.2",
|
||||
"tinyglobby": "^0.2.15",
|
||||
"xml-js": "^1.6.11"
|
||||
},
|
||||
"devDependencies": {
|
||||
"fumadocs-core": "workspace:*",
|
||||
"fumadocs-ui": "workspace:*",
|
||||
"@scalar/api-client-react": "^1.3.50",
|
||||
"@types/js-yaml": "^4.0.9",
|
||||
"@types/node": "24.10.1",
|
||||
@@ -93,6 +91,8 @@
|
||||
"tsconfig": "workspace:*"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"fumadocs-core": "^16.2.0",
|
||||
"fumadocs-ui": "^16.2.0",
|
||||
"@scalar/api-client-react": "*",
|
||||
"@types/react": "*",
|
||||
"react": "^19.2.0",
|
||||
|
||||
@@ -8,12 +8,11 @@ import {
|
||||
import type { ProcessedDocument } from '@/utils/process-document';
|
||||
import type { OpenAPIServer } from '@/server';
|
||||
import { createGetUrl, getSlugs } from 'fumadocs-core/source';
|
||||
import matter from 'gray-matter';
|
||||
import {
|
||||
createAutoPreset,
|
||||
type SchemaToPagesOptions,
|
||||
} from '@/utils/pages/preset-auto';
|
||||
import { fromSchema } from '@/utils/pages/builder';
|
||||
import { fromSchema, type OutputEntry } from '@/utils/pages/builder';
|
||||
|
||||
export interface OutputFile {
|
||||
path: string;
|
||||
@@ -21,7 +20,7 @@ export interface OutputFile {
|
||||
}
|
||||
|
||||
interface IndexConfig {
|
||||
items: IndexItem[] | ((ctx: HookContext) => IndexItem[]);
|
||||
items: IndexItem[] | ((ctx: BeforeWriteContext) => IndexItem[]);
|
||||
|
||||
/**
|
||||
* Generate URLs for cards
|
||||
@@ -47,7 +46,7 @@ interface IndexItem {
|
||||
* - items in `inputs` to include all generated pages of a specific schema.
|
||||
* - specific Markdown/MDX files.
|
||||
*/
|
||||
only?: (string | OutputFile)[];
|
||||
only?: string[];
|
||||
}
|
||||
|
||||
interface GenerateFilesConfig extends PagesToTextOptions {
|
||||
@@ -70,16 +69,16 @@ interface GenerateFilesConfig extends PagesToTextOptions {
|
||||
* Can add/change/remove output files before writing to file system
|
||||
**/
|
||||
beforeWrite?: (
|
||||
this: HookContext,
|
||||
this: BeforeWriteContext,
|
||||
files: OutputFile[],
|
||||
) => void | Promise<void>;
|
||||
}
|
||||
|
||||
export type Config = SchemaToPagesOptions & GenerateFilesConfig;
|
||||
|
||||
interface HookContext {
|
||||
files: OutputFile[];
|
||||
interface BeforeWriteContext {
|
||||
readonly generated: Record<string, OutputFile[]>;
|
||||
readonly generatedEntries: Record<string, OutputEntry[]>;
|
||||
readonly documents: Record<string, ProcessedDocument>;
|
||||
}
|
||||
|
||||
@@ -101,11 +100,11 @@ export async function generateFiles(options: Config): Promise<void> {
|
||||
export async function generateFilesOnly(
|
||||
options: SchemaToPagesOptions & Omit<GenerateFilesConfig, 'output'>,
|
||||
): Promise<OutputFile[]> {
|
||||
const { beforeWrite } = options;
|
||||
const schemas = await options.input.getSchemas();
|
||||
|
||||
const generated: Record<string, OutputFile[]> = {};
|
||||
const files: OutputFile[] = [];
|
||||
const generated: Record<string, OutputFile[]> = {};
|
||||
const generatedEntries: Record<string, OutputEntry[]> = {};
|
||||
|
||||
const entries = Object.entries(schemas);
|
||||
if (entries.length === 0) {
|
||||
@@ -113,34 +112,42 @@ export async function generateFilesOnly(
|
||||
}
|
||||
const preset = createAutoPreset(options);
|
||||
for (const [id, schema] of entries) {
|
||||
const result = fromSchema(id, schema, preset).map<OutputFile>((page) => ({
|
||||
path: page.path,
|
||||
content: toText(page, schema, options),
|
||||
}));
|
||||
files.push(...result);
|
||||
generated[id] = result;
|
||||
const entries = fromSchema(id, schema, preset);
|
||||
const schemaFiles = (generated[id] ??= []);
|
||||
|
||||
generatedEntries[id] = entries;
|
||||
for (const entry of entries) {
|
||||
const file: OutputFile = {
|
||||
path: entry.path,
|
||||
content: toText(entry, schema, options),
|
||||
};
|
||||
|
||||
schemaFiles.push(file);
|
||||
files.push(file);
|
||||
}
|
||||
}
|
||||
|
||||
const context: HookContext = {
|
||||
files,
|
||||
const context: BeforeWriteContext = {
|
||||
generated,
|
||||
generatedEntries,
|
||||
documents: schemas,
|
||||
};
|
||||
|
||||
if (options.index) {
|
||||
writeIndexFiles(context, options.index, options);
|
||||
writeIndexFiles(files, context, options);
|
||||
}
|
||||
|
||||
await beforeWrite?.call(context, context.files);
|
||||
return context.files;
|
||||
await options.beforeWrite?.call(context, files);
|
||||
return files;
|
||||
}
|
||||
|
||||
function writeIndexFiles(
|
||||
context: HookContext,
|
||||
options: IndexConfig,
|
||||
generateOptions: PagesToTextOptions,
|
||||
files: OutputFile[],
|
||||
context: BeforeWriteContext,
|
||||
options: SchemaToPagesOptions & Omit<GenerateFilesConfig, 'output'>,
|
||||
) {
|
||||
const { items, url } = options;
|
||||
const { generatedEntries } = context;
|
||||
const { items, url } = options.index!;
|
||||
|
||||
let urlFn: (path: string) => string;
|
||||
if (typeof url === 'object') {
|
||||
@@ -151,41 +158,43 @@ function writeIndexFiles(
|
||||
urlFn = url;
|
||||
}
|
||||
|
||||
function findEntryByPath(path: string) {
|
||||
for (const entries of Object.values(generatedEntries)) {
|
||||
const match = entries.find((entry) => entry.path === path);
|
||||
|
||||
if (match) return match;
|
||||
}
|
||||
}
|
||||
|
||||
function fileContent(index: IndexItem): string {
|
||||
const generatedPages = context.generated;
|
||||
const content: string[] = [];
|
||||
content.push('<Cards>');
|
||||
const files = new Map<string, OutputFile>();
|
||||
const pathToEntry = new Map<string, OutputEntry>();
|
||||
const only = index.only ?? Object.keys(context.generated);
|
||||
|
||||
for (const item of only) {
|
||||
if (typeof item === 'object') {
|
||||
files.set(item.path, item);
|
||||
continue;
|
||||
}
|
||||
if (generatedEntries[item]) {
|
||||
for (const entry of generatedEntries[item]) {
|
||||
pathToEntry.set(entry.path, entry);
|
||||
}
|
||||
} else {
|
||||
const match = findEntryByPath(item);
|
||||
if (!match) {
|
||||
throw new Error(
|
||||
`${item} does not exist on "input", available: ${Object.keys(generatedEntries).join(', ')}.`,
|
||||
);
|
||||
}
|
||||
|
||||
const result = generatedPages[item];
|
||||
if (!result)
|
||||
throw new Error(
|
||||
`${item} does not exist on "input", available: ${Object.keys(generatedPages).join(', ')}.`,
|
||||
);
|
||||
|
||||
for (const file of result) {
|
||||
files.set(file.path, file);
|
||||
pathToEntry.set(match.path, match);
|
||||
}
|
||||
}
|
||||
|
||||
for (const file of files.values()) {
|
||||
const isContent = file.path.endsWith('.mdx') || file.path.endsWith('.md');
|
||||
if (!isContent) continue;
|
||||
const { data } = matter(file.content);
|
||||
if (typeof data.title !== 'string') continue;
|
||||
|
||||
const descriptionAttr = data.description
|
||||
? `description=${JSON.stringify(data.description)} `
|
||||
for (const file of pathToEntry.values()) {
|
||||
const descriptionAttr = file.info.description
|
||||
? `description=${JSON.stringify(file.info.description)} `
|
||||
: '';
|
||||
content.push(
|
||||
`<Card href="${urlFn(file.path)}" title=${JSON.stringify(data.title)} ${descriptionAttr}/>`,
|
||||
`<Card href="${urlFn(file.path)}" title=${JSON.stringify(file.info.title)} ${descriptionAttr}/>`,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -196,12 +205,12 @@ function writeIndexFiles(
|
||||
description: index.description,
|
||||
},
|
||||
content.join('\n'),
|
||||
generateOptions,
|
||||
options,
|
||||
);
|
||||
}
|
||||
|
||||
for (const item of typeof items === 'function' ? items(context) : items) {
|
||||
context.files.push({
|
||||
files.push({
|
||||
path:
|
||||
path.extname(item.path).length === 0 ? `${item.path}.mdx` : item.path,
|
||||
content: fileContent(item),
|
||||
|
||||
@@ -279,7 +279,9 @@ export async function Operation({
|
||||
{slots.responses}
|
||||
{slots.callbacks}
|
||||
</div>
|
||||
{slots.apiExample}
|
||||
<div className="@4xl:sticky @4xl:top-[calc(var(--fd-docs-row-1,2rem)+1rem)] @4xl:w-[400px]">
|
||||
{slots.apiExample}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -333,7 +335,9 @@ export async function Operation({
|
||||
{slots.responses}
|
||||
{slots.callbacks}
|
||||
</div>
|
||||
{slots.requests}
|
||||
<div className="@4xl:sticky @4xl:top-[calc(var(--fd-docs-row-1,2rem)+1rem)] @4xl:w-[400px]">
|
||||
{slots.requests}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
return renderWebhookLayout({
|
||||
|
||||
@@ -168,15 +168,11 @@ export async function RequestTabs({
|
||||
if (!operation.requestBody) return null;
|
||||
const { renderRequestTabs = renderRequestTabsDefault } = ctx.content ?? {};
|
||||
|
||||
return (
|
||||
<div className="@4xl:sticky @4xl:top-[calc(var(--fd-docs-row-1,2rem)+1rem)] @4xl:w-[400px]">
|
||||
{await renderRequestTabs(getExampleRequests(path, operation, ctx), {
|
||||
...ctx,
|
||||
route: path,
|
||||
operation,
|
||||
})}
|
||||
</div>
|
||||
);
|
||||
return renderRequestTabs(getExampleRequests(path, operation, ctx), {
|
||||
...ctx,
|
||||
route: path,
|
||||
operation,
|
||||
});
|
||||
}
|
||||
|
||||
function renderRequestTabsDefault(
|
||||
@@ -232,8 +228,9 @@ function renderRequestTabsDefault(
|
||||
);
|
||||
}
|
||||
|
||||
let children: ReactNode;
|
||||
if (items.length > 1) {
|
||||
return (
|
||||
children = (
|
||||
<Tabs defaultValue={items[0].id}>
|
||||
<TabsList>
|
||||
{items.map((item) => (
|
||||
@@ -250,13 +247,15 @@ function renderRequestTabsDefault(
|
||||
</Tabs>
|
||||
);
|
||||
} else if (items.length === 1) {
|
||||
return (
|
||||
<div className="p-3 rounded-xl border prose-no-margin bg-fd-card text-fd-card-foreground shadow-md">
|
||||
<p className="font-semibold border-b pb-2">Example Requests</p>
|
||||
{renderItem(items[0])}
|
||||
</div>
|
||||
);
|
||||
children = renderItem(items[0]);
|
||||
} else {
|
||||
children = <p className="text-fd-muted-foreground text-xs">Empty</p>;
|
||||
}
|
||||
|
||||
return 'Empty';
|
||||
return (
|
||||
<div className="p-3 rounded-xl border prose-no-margin bg-fd-card text-fd-card-foreground shadow-md">
|
||||
<p className="font-semibold border-b pb-2">Example Requests</p>
|
||||
{children}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -42,7 +42,7 @@ export async function UsageTabs({
|
||||
|
||||
renderAPIExampleLayout ??= (slots) => {
|
||||
return (
|
||||
<div className="prose-no-margin @4xl:sticky @4xl:top-[calc(var(--fd-docs-row-1,2rem)+1rem)] @4xl:w-[400px]">
|
||||
<div className="prose-no-margin">
|
||||
{slots.selector}
|
||||
{slots.usageTabs}
|
||||
{slots.responseTabs}
|
||||
|
||||
Generated
+66
-67
Reference in New Issue
Block a user