fumadocs-openapi 9.2.3 → 9.3.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/generate-file.d.ts +41 -0
- package/dist/generate-file.d.ts.map +1 -1
- package/dist/generate-file.js +175 -83
- package/dist/generate.d.ts +13 -4
- package/dist/generate.d.ts.map +1 -1
- package/dist/generate.js +105 -8
- package/dist/render/api-page.js +2 -2
- package/dist/render/schema.d.ts.map +1 -1
- package/dist/render/schema.js +41 -22
- package/dist/server/create.d.ts +0 -3
- package/dist/server/create.d.ts.map +1 -1
- package/dist/server/create.js +7 -5
- package/dist/utils/process-document.d.ts +2 -1
- package/dist/utils/process-document.d.ts.map +1 -1
- package/dist/utils/process-document.js +12 -9
- package/dist/utils/url.d.ts.map +1 -1
- package/dist/utils/url.js +12 -3
- package/package.json +4 -3
- package/dist/utils/generate-document.d.ts +0 -17
- package/dist/utils/generate-document.d.ts.map +0 -1
- package/dist/utils/generate-document.js +0 -94
package/dist/generate-file.d.ts
CHANGED
|
@@ -8,6 +8,10 @@ interface GenerateFileOutput {
|
|
|
8
8
|
pathOrUrl: string;
|
|
9
9
|
content: string;
|
|
10
10
|
}
|
|
11
|
+
export interface OutputFile {
|
|
12
|
+
path: string;
|
|
13
|
+
content: string;
|
|
14
|
+
}
|
|
11
15
|
interface OperationConfig extends BaseConfig {
|
|
12
16
|
/**
|
|
13
17
|
* Generate a page for each API endpoint/operation (default).
|
|
@@ -59,6 +63,22 @@ interface BaseName {
|
|
|
59
63
|
*/
|
|
60
64
|
algorithm?: 'v2' | 'v1';
|
|
61
65
|
}
|
|
66
|
+
interface IndexItem {
|
|
67
|
+
path: string;
|
|
68
|
+
title?: string;
|
|
69
|
+
description?: string;
|
|
70
|
+
/**
|
|
71
|
+
* Specify linked pages:
|
|
72
|
+
* - items in `inputs` to include all generated pages of a specific schema.
|
|
73
|
+
* - specific Markdown/MDX files.
|
|
74
|
+
*/
|
|
75
|
+
only?: (string | OutputFile)[];
|
|
76
|
+
}
|
|
77
|
+
interface HookContext {
|
|
78
|
+
files: OutputFile[];
|
|
79
|
+
readonly generated: Record<string, OutputFile[]>;
|
|
80
|
+
readonly documents: Record<string, ProcessedDocument>;
|
|
81
|
+
}
|
|
62
82
|
interface BaseConfig extends GenerateOptions {
|
|
63
83
|
/**
|
|
64
84
|
* Schema files, or the OpenAPI server object
|
|
@@ -74,7 +94,28 @@ interface BaseConfig extends GenerateOptions {
|
|
|
74
94
|
* By default, it only escapes whitespaces and upper case (English) characters
|
|
75
95
|
*/
|
|
76
96
|
slugify?: (name: string) => string;
|
|
97
|
+
/**
|
|
98
|
+
* Generate index files with cards linking to generated pages.
|
|
99
|
+
*/
|
|
100
|
+
index?: {
|
|
101
|
+
items: IndexItem[] | ((ctx: HookContext) => IndexItem[]);
|
|
102
|
+
/**
|
|
103
|
+
* Generate URLs for cards
|
|
104
|
+
*/
|
|
105
|
+
url: ((filePath: string) => string) | {
|
|
106
|
+
baseUrl: string;
|
|
107
|
+
/**
|
|
108
|
+
* Base content directory
|
|
109
|
+
*/
|
|
110
|
+
contentDir: string;
|
|
111
|
+
};
|
|
112
|
+
};
|
|
113
|
+
/**
|
|
114
|
+
* Can add/change/remove output files before writing to file system
|
|
115
|
+
**/
|
|
116
|
+
beforeWrite?: (this: HookContext, files: OutputFile[]) => void | Promise<void>;
|
|
77
117
|
}
|
|
78
118
|
export declare function generateFiles(options: Config): Promise<void>;
|
|
119
|
+
export declare function generateFilesOnly(options: Config): Promise<OutputFile[]>;
|
|
79
120
|
export {};
|
|
80
121
|
//# sourceMappingURL=generate-file.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"generate-file.d.ts","sourceRoot":"","sources":["../src/generate-file.ts"],"names":[],"mappings":"AAGA,OAAO,
|
|
1
|
+
{"version":3,"file":"generate-file.d.ts","sourceRoot":"","sources":["../src/generate-file.ts"],"names":[],"mappings":"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"}
|
package/dist/generate-file.js
CHANGED
|
@@ -1,87 +1,93 @@
|
|
|
1
1
|
import { mkdir, writeFile } from 'node:fs/promises';
|
|
2
2
|
import * as path from 'node:path';
|
|
3
3
|
import { glob } from 'tinyglobby';
|
|
4
|
-
import { generateAll, generatePages, generateTags, } from './generate.js';
|
|
5
|
-
import {
|
|
4
|
+
import { generateAll, generateDocument, generatePages, generateTags, } from './generate.js';
|
|
5
|
+
import { processDocumentCached, } from './utils/process-document.js';
|
|
6
|
+
import { createGetUrl, getSlugs } from 'fumadocs-core/source';
|
|
7
|
+
import matter from 'gray-matter';
|
|
6
8
|
export async function generateFiles(options) {
|
|
7
|
-
const
|
|
9
|
+
const files = await generateFilesOnly(options);
|
|
10
|
+
await Promise.all(files.map(async (file) => {
|
|
11
|
+
await mkdir(path.dirname(file.path), { recursive: true });
|
|
12
|
+
await writeFile(file.path, file.content);
|
|
13
|
+
console.log(`Generated: ${file.path}`);
|
|
14
|
+
}));
|
|
15
|
+
}
|
|
16
|
+
export async function generateFilesOnly(options) {
|
|
17
|
+
const { cwd = process.cwd(), beforeWrite } = options;
|
|
8
18
|
const input = typeof options.input === 'string' ? [options.input] : options.input;
|
|
9
19
|
let schemas = {};
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
20
|
+
async function resolveInput(item) {
|
|
21
|
+
if (isUrl(item)) {
|
|
22
|
+
schemas[item] = await processDocumentCached(item);
|
|
23
|
+
return;
|
|
24
|
+
}
|
|
25
|
+
const resolved = await glob(item, { cwd, absolute: true });
|
|
26
|
+
if (resolved.length > 1) {
|
|
27
|
+
console.warn('glob patterns in `input` are deprecated, please specify your schemas explicitly.');
|
|
28
|
+
for (let i = 0; i < resolved.length; i++) {
|
|
29
|
+
schemas[`${item}[${i}]`] = await processDocumentCached(item);
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
else if (resolved.length === 1) {
|
|
33
|
+
schemas[item] = await processDocumentCached(resolved[0]);
|
|
18
34
|
}
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
35
|
+
else {
|
|
36
|
+
throw new Error(`input not found: ${item}`);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
if (Array.isArray(input)) {
|
|
40
|
+
await Promise.all(input.map(resolveInput));
|
|
24
41
|
}
|
|
25
42
|
else {
|
|
26
43
|
schemas = await input.getSchemas();
|
|
27
44
|
}
|
|
28
|
-
const
|
|
29
|
-
|
|
45
|
+
const generated = {};
|
|
46
|
+
const files = [];
|
|
47
|
+
const entries = Object.entries(schemas);
|
|
48
|
+
if (entries.length === 0) {
|
|
30
49
|
throw new Error('No input files found.');
|
|
31
50
|
}
|
|
32
|
-
|
|
51
|
+
for (const [id, schema] of entries) {
|
|
52
|
+
const result = generateFromDocument(id, schema, options);
|
|
53
|
+
files.push(...result);
|
|
54
|
+
generated[id] = result;
|
|
55
|
+
}
|
|
56
|
+
const context = {
|
|
57
|
+
files,
|
|
58
|
+
generated,
|
|
59
|
+
documents: schemas,
|
|
60
|
+
};
|
|
61
|
+
if (options.index) {
|
|
62
|
+
writeIndexFiles(context, options);
|
|
63
|
+
}
|
|
64
|
+
await beforeWrite?.call(context, context.files);
|
|
65
|
+
return context.files;
|
|
33
66
|
}
|
|
34
|
-
|
|
67
|
+
function generateFromDocument(schemaId, processed, options) {
|
|
68
|
+
const files = [];
|
|
69
|
+
const { document } = processed;
|
|
35
70
|
const { output, cwd = process.cwd(), slugify = defaultSlugify } = options;
|
|
36
71
|
const outputDir = path.join(cwd, output);
|
|
37
72
|
let nameFn;
|
|
38
73
|
if (!options.name || typeof options.name !== 'function') {
|
|
39
|
-
const
|
|
40
|
-
nameFn = (
|
|
41
|
-
if (options.per === 'tag') {
|
|
42
|
-
const result = output;
|
|
43
|
-
return slugify(result.tag);
|
|
44
|
-
}
|
|
45
|
-
if (options.per === 'file') {
|
|
46
|
-
return isUrl(schemaId)
|
|
47
|
-
? 'index'
|
|
48
|
-
: path.basename(schemaId, path.extname(schemaId));
|
|
49
|
-
}
|
|
50
|
-
const result = output;
|
|
51
|
-
if (result.type === 'operation') {
|
|
52
|
-
const operation = document.paths[result.item.path][result.item.method];
|
|
53
|
-
if (algorithm === 'v2' && operation.operationId) {
|
|
54
|
-
return operation.operationId;
|
|
55
|
-
}
|
|
56
|
-
return path.join(getOutputPathFromRoute(result.item.path), result.item.method.toLowerCase());
|
|
57
|
-
}
|
|
58
|
-
const hook = document.webhooks[result.item.name][result.item.method];
|
|
59
|
-
if (algorithm === 'v2' && hook.operationId) {
|
|
60
|
-
return hook.operationId;
|
|
61
|
-
}
|
|
62
|
-
return slugify(result.item.name);
|
|
63
|
-
};
|
|
74
|
+
const algorithm = options.name?.algorithm;
|
|
75
|
+
nameFn = (out, doc) => defaultNameFn(schemaId, out, doc, options, algorithm);
|
|
64
76
|
}
|
|
65
77
|
else {
|
|
66
78
|
nameFn = options.name;
|
|
67
79
|
}
|
|
68
|
-
async function write(file, content) {
|
|
69
|
-
await mkdir(path.dirname(file), { recursive: true });
|
|
70
|
-
await writeFile(file, content);
|
|
71
|
-
}
|
|
72
80
|
function getOutputPaths(groupBy = 'none', result) {
|
|
73
81
|
if (groupBy === 'route') {
|
|
74
82
|
return [
|
|
75
83
|
path.join(getOutputPathFromRoute(result.type === 'operation' ? result.item.path : result.item.name), `${result.item.method.toLowerCase()}.mdx`),
|
|
76
84
|
];
|
|
77
85
|
}
|
|
78
|
-
const file = nameFn(result, document
|
|
86
|
+
const file = nameFn(result, document);
|
|
79
87
|
if (groupBy === 'tag') {
|
|
80
88
|
let tags = result.type === 'operation'
|
|
81
|
-
? document.
|
|
82
|
-
|
|
83
|
-
: document.document.webhooks[result.item.name][result.item.method]
|
|
84
|
-
.tags;
|
|
89
|
+
? document.paths[result.item.path][result.item.method].tags
|
|
90
|
+
: document.webhooks[result.item.name][result.item.method].tags;
|
|
85
91
|
if (!tags || tags.length === 0) {
|
|
86
92
|
console.warn('When `groupBy` is set to `tag`, make sure a `tags` is defined for every operation schema.');
|
|
87
93
|
tags = ['unknown'];
|
|
@@ -91,46 +97,76 @@ async function generateFromDocument(schemaId, document, options) {
|
|
|
91
97
|
return [`${file}.mdx`];
|
|
92
98
|
}
|
|
93
99
|
if (options.per === 'file') {
|
|
94
|
-
const result =
|
|
100
|
+
const result = generateAll(schemaId, processed, options);
|
|
95
101
|
const filename = nameFn({
|
|
96
102
|
pathOrUrl: schemaId,
|
|
97
103
|
content: result,
|
|
98
|
-
}, document
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
104
|
+
}, document);
|
|
105
|
+
files.push({
|
|
106
|
+
path: path.join(outputDir, `${filename}.mdx`),
|
|
107
|
+
content: result,
|
|
108
|
+
});
|
|
109
|
+
return files;
|
|
102
110
|
}
|
|
103
|
-
|
|
104
|
-
const results =
|
|
111
|
+
if (options.per === 'tag') {
|
|
112
|
+
const results = generateTags(schemaId, processed, options);
|
|
105
113
|
for (const result of results) {
|
|
106
|
-
const filename = nameFn(result, document
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
114
|
+
const filename = nameFn(result, document);
|
|
115
|
+
files.push({
|
|
116
|
+
path: path.join(outputDir, `${filename}.mdx`),
|
|
117
|
+
content: result.content,
|
|
118
|
+
});
|
|
110
119
|
}
|
|
120
|
+
return files;
|
|
111
121
|
}
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
for (const
|
|
116
|
-
|
|
117
|
-
mapping.set(outputPath, result);
|
|
118
|
-
}
|
|
122
|
+
const results = generatePages(schemaId, processed, options);
|
|
123
|
+
const mapping = new Map();
|
|
124
|
+
for (const result of results) {
|
|
125
|
+
for (const outputPath of getOutputPaths(options.groupBy, result)) {
|
|
126
|
+
mapping.set(outputPath, result);
|
|
119
127
|
}
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
128
|
+
}
|
|
129
|
+
for (const [key, output] of mapping.entries()) {
|
|
130
|
+
let outputPath = key;
|
|
131
|
+
// v1 will remove nested directories
|
|
132
|
+
if (typeof options.name === 'object' && options.name.algorithm === 'v1') {
|
|
133
|
+
const isSharedDir = Array.from(mapping.keys()).some((item) => item !== outputPath &&
|
|
134
|
+
path.dirname(item) === path.dirname(outputPath));
|
|
135
|
+
if (!isSharedDir && path.dirname(outputPath) !== '.') {
|
|
136
|
+
outputPath = path.join(path.dirname(outputPath) + '.mdx');
|
|
129
137
|
}
|
|
130
|
-
await write(path.join(outputDir, outputPath), output.content);
|
|
131
|
-
console.log(`Generated: ${outputPath}`);
|
|
132
138
|
}
|
|
139
|
+
files.push({
|
|
140
|
+
path: path.join(outputDir, outputPath),
|
|
141
|
+
content: output.content,
|
|
142
|
+
});
|
|
143
|
+
}
|
|
144
|
+
return files;
|
|
145
|
+
}
|
|
146
|
+
function defaultNameFn(schemaId, output, document, options, algorithm = 'v2') {
|
|
147
|
+
const { slugify = defaultSlugify } = options;
|
|
148
|
+
if (options.per === 'tag') {
|
|
149
|
+
const result = output;
|
|
150
|
+
return slugify(result.tag);
|
|
151
|
+
}
|
|
152
|
+
if (options.per === 'file') {
|
|
153
|
+
return isUrl(schemaId)
|
|
154
|
+
? 'index'
|
|
155
|
+
: path.basename(schemaId, path.extname(schemaId));
|
|
156
|
+
}
|
|
157
|
+
const result = output;
|
|
158
|
+
if (result.type === 'operation') {
|
|
159
|
+
const operation = document.paths[result.item.path][result.item.method];
|
|
160
|
+
if (algorithm === 'v2' && operation.operationId) {
|
|
161
|
+
return operation.operationId;
|
|
162
|
+
}
|
|
163
|
+
return path.join(getOutputPathFromRoute(result.item.path), result.item.method.toLowerCase());
|
|
164
|
+
}
|
|
165
|
+
const hook = document.webhooks[result.item.name][result.item.method];
|
|
166
|
+
if (algorithm === 'v2' && hook.operationId) {
|
|
167
|
+
return hook.operationId;
|
|
133
168
|
}
|
|
169
|
+
return slugify(result.item.name);
|
|
134
170
|
}
|
|
135
171
|
function isUrl(input) {
|
|
136
172
|
return input.startsWith('https://') || input.startsWith('http://');
|
|
@@ -147,6 +183,62 @@ function getOutputPathFromRoute(path) {
|
|
|
147
183
|
})
|
|
148
184
|
.join('/') ?? '');
|
|
149
185
|
}
|
|
186
|
+
function writeIndexFiles(context, options) {
|
|
187
|
+
const { index, output, cwd = process.cwd() } = options;
|
|
188
|
+
if (!index)
|
|
189
|
+
return;
|
|
190
|
+
const { items, url } = index;
|
|
191
|
+
let urlFn;
|
|
192
|
+
if (typeof url === 'object') {
|
|
193
|
+
const getUrl = createGetUrl(url.baseUrl);
|
|
194
|
+
const contentDir = path.resolve(cwd, url.contentDir);
|
|
195
|
+
urlFn = (file) => getUrl(getSlugs(path.relative(contentDir, file)));
|
|
196
|
+
}
|
|
197
|
+
else {
|
|
198
|
+
urlFn = url;
|
|
199
|
+
}
|
|
200
|
+
function fileContent(index) {
|
|
201
|
+
const generatedPages = context.generated;
|
|
202
|
+
const content = [];
|
|
203
|
+
content.push('<Cards>');
|
|
204
|
+
const files = new Map();
|
|
205
|
+
const only = index.only ?? Object.keys(context.generated);
|
|
206
|
+
for (const item of only) {
|
|
207
|
+
if (typeof item === 'object') {
|
|
208
|
+
files.set(item.path, item);
|
|
209
|
+
continue;
|
|
210
|
+
}
|
|
211
|
+
const result = generatedPages[item];
|
|
212
|
+
if (!result)
|
|
213
|
+
throw new Error(`${item} does not exist on "input", available: ${Object.keys(generatedPages).join(', ')}.`);
|
|
214
|
+
for (const file of result) {
|
|
215
|
+
files.set(file.path, file);
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
for (const file of files.values()) {
|
|
219
|
+
const isContent = file.path.endsWith('.mdx') || file.path.endsWith('.md');
|
|
220
|
+
if (!isContent)
|
|
221
|
+
continue;
|
|
222
|
+
const { data } = matter(file.content);
|
|
223
|
+
if (typeof data.title !== 'string')
|
|
224
|
+
continue;
|
|
225
|
+
content.push(`<Card href="${urlFn(file.path)}" title=${JSON.stringify(data.title)} description=${JSON.stringify(data.description)} />`);
|
|
226
|
+
}
|
|
227
|
+
content.push('</Cards>');
|
|
228
|
+
return generateDocument({
|
|
229
|
+
title: index.title ?? 'Overview',
|
|
230
|
+
description: index.description,
|
|
231
|
+
}, content.join('\n'), options);
|
|
232
|
+
}
|
|
233
|
+
const outputDir = path.join(cwd, output);
|
|
234
|
+
for (const item of typeof items === 'function' ? items(context) : items) {
|
|
235
|
+
const outPath = path.join(outputDir, path.extname(item.path).length === 0 ? `${item.path}.mdx` : item.path);
|
|
236
|
+
context.files.push({
|
|
237
|
+
path: outPath,
|
|
238
|
+
content: fileContent(item),
|
|
239
|
+
});
|
|
240
|
+
}
|
|
241
|
+
}
|
|
150
242
|
function defaultSlugify(s) {
|
|
151
243
|
return s.replace(/\s+/g, '-').toLowerCase();
|
|
152
244
|
}
|
package/dist/generate.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { type DocumentContext } from './utils/generate-document.js';
|
|
2
1
|
import type { OperationItem, WebhookItem } from './render/api-page.js';
|
|
3
2
|
import type { ProcessedDocument } from './utils/process-document.js';
|
|
3
|
+
import type { TagObject } from './types.js';
|
|
4
4
|
export interface GenerateOptions {
|
|
5
5
|
/**
|
|
6
6
|
* Additional imports of your MDX components.
|
|
@@ -54,7 +54,16 @@ export type GeneratePageOutput = {
|
|
|
54
54
|
item: WebhookItem;
|
|
55
55
|
content: string;
|
|
56
56
|
};
|
|
57
|
-
export declare function generateAll(schemaId: string, processed: ProcessedDocument, options?: GenerateOptions):
|
|
58
|
-
export declare function generatePages(schemaId: string, processed: ProcessedDocument, options?: GenerateOptions):
|
|
59
|
-
export declare function generateTags(schemaId: string, processed: ProcessedDocument, options?: GenerateOptions):
|
|
57
|
+
export declare function generateAll(schemaId: string, processed: ProcessedDocument, options?: GenerateOptions): string;
|
|
58
|
+
export declare function generatePages(schemaId: string, processed: ProcessedDocument, options?: GenerateOptions): GeneratePageOutput[];
|
|
59
|
+
export declare function generateTags(schemaId: string, processed: ProcessedDocument, options?: GenerateOptions): GenerateTagOutput[];
|
|
60
|
+
export declare function generateDocument(frontmatter: unknown, content: string, options: Pick<GenerateOptions, 'addGeneratedComment' | 'imports'>): string;
|
|
61
|
+
export type DocumentContext = {
|
|
62
|
+
type: 'tag';
|
|
63
|
+
tag: TagObject | undefined;
|
|
64
|
+
} | {
|
|
65
|
+
type: 'operation';
|
|
66
|
+
} | {
|
|
67
|
+
type: 'file';
|
|
68
|
+
};
|
|
60
69
|
//# sourceMappingURL=generate.d.ts.map
|
package/dist/generate.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"generate.d.ts","sourceRoot":"","sources":["../src/generate.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"generate.d.ts","sourceRoot":"","sources":["../src/generate.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAEV,aAAa,EACb,WAAW,EACZ,MAAM,mBAAmB,CAAC;AAC3B,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,0BAA0B,CAAC;AAGlE,OAAO,KAAK,EAAY,SAAS,EAAE,MAAM,SAAS,CAAC;AAKnD,MAAM,WAAW,eAAe;IAC9B;;OAEG;IACH,OAAO,CAAC,EAAE;QACR,KAAK,EAAE,MAAM,EAAE,CAAC;QAChB,IAAI,EAAE,MAAM,CAAC;KACd,EAAE,CAAC;IAEJ;;;;OAIG;IACH,WAAW,CAAC,EAAE,CACZ,KAAK,EAAE,MAAM,EACb,WAAW,EAAE,MAAM,GAAG,SAAS,EAC/B,OAAO,EAAE,eAAe,KACrB,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAE7B;;;;;;OAMG;IACH,kBAAkB,CAAC,EAAE,OAAO,CAAC;IAE7B;;;;;;;OAOG;IACH,mBAAmB,CAAC,EAAE,OAAO,GAAG,MAAM,CAAC;IAEvC,GAAG,CAAC,EAAE,MAAM,CAAC;IAEb;;;;;OAKG;IACH,cAAc,CAAC,EAAE,OAAO,CAAC;CAC1B;AAED,MAAM,WAAW,iBAAiB;IAChC,GAAG,EAAE,MAAM,CAAC;IACZ,OAAO,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,MAAM,kBAAkB,GAC1B;IACE,IAAI,EAAE,WAAW,CAAC;IAClB,IAAI,EAAE,aAAa,CAAC;IACpB,OAAO,EAAE,MAAM,CAAC;CACjB,GACD;IACE,IAAI,EAAE,SAAS,CAAC;IAChB,IAAI,EAAE,WAAW,CAAC;IAClB,OAAO,EAAE,MAAM,CAAC;CACjB,CAAC;AAEN,wBAAgB,WAAW,CACzB,QAAQ,EAAE,MAAM,EAChB,SAAS,EAAE,iBAAiB,EAC5B,OAAO,GAAE,eAAoB,GAC5B,MAAM,CAqBR;AAED,wBAAgB,aAAa,CAC3B,QAAQ,EAAE,MAAM,EAChB,SAAS,EAAE,iBAAiB,EAC5B,OAAO,GAAE,eAAoB,GAC5B,kBAAkB,EAAE,CAiEtB;AAED,wBAAgB,YAAY,CAC1B,QAAQ,EAAE,MAAM,EAChB,SAAS,EAAE,iBAAiB,EAC5B,OAAO,GAAE,eAAoB,GAC5B,iBAAiB,EAAE,CAwCrB;AAED,wBAAgB,gBAAgB,CAC9B,WAAW,EAAE,OAAO,EACpB,OAAO,EAAE,MAAM,EACf,OAAO,EAAE,IAAI,CAAC,eAAe,EAAE,qBAAqB,GAAG,SAAS,CAAC,GAChE,MAAM,CA+BR;AAOD,MAAM,MAAM,eAAe,GACvB;IACE,IAAI,EAAE,KAAK,CAAC;IACZ,GAAG,EAAE,SAAS,GAAG,SAAS,CAAC;CAC5B,GACD;IACE,IAAI,EAAE,WAAW,CAAC;CACnB,GACD;IACE,IAAI,EAAE,MAAM,CAAC;CACd,CAAC"}
|
package/dist/generate.js
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
import { getAPIPageItems } from './build-routes.js';
|
|
2
|
-
import { generateDocument, } from './utils/generate-document.js';
|
|
3
2
|
import { idToTitle } from './utils/id-to-title.js';
|
|
4
|
-
|
|
3
|
+
import { dump } from 'js-yaml';
|
|
4
|
+
import Slugger from 'github-slugger';
|
|
5
|
+
export function generateAll(schemaId, processed, options = {}) {
|
|
5
6
|
const { document } = processed;
|
|
6
7
|
const items = getAPIPageItems(document);
|
|
7
|
-
return
|
|
8
|
+
return generatePage(schemaId, processed, {
|
|
8
9
|
operations: items.operations,
|
|
9
10
|
webhooks: items.webhooks,
|
|
10
11
|
hasHead: true,
|
|
@@ -16,7 +17,7 @@ export async function generateAll(schemaId, processed, options = {}) {
|
|
|
16
17
|
type: 'file',
|
|
17
18
|
});
|
|
18
19
|
}
|
|
19
|
-
export
|
|
20
|
+
export function generatePages(schemaId, processed, options = {}) {
|
|
20
21
|
const { document } = processed;
|
|
21
22
|
const items = getAPIPageItems(document);
|
|
22
23
|
const result = [];
|
|
@@ -30,7 +31,7 @@ export async function generatePages(schemaId, processed, options = {}) {
|
|
|
30
31
|
result.push({
|
|
31
32
|
type: 'operation',
|
|
32
33
|
item,
|
|
33
|
-
content:
|
|
34
|
+
content: generatePage(schemaId, processed, {
|
|
34
35
|
operations: [item],
|
|
35
36
|
hasHead: false,
|
|
36
37
|
}, {
|
|
@@ -54,7 +55,7 @@ export async function generatePages(schemaId, processed, options = {}) {
|
|
|
54
55
|
result.push({
|
|
55
56
|
type: 'webhook',
|
|
56
57
|
item,
|
|
57
|
-
content:
|
|
58
|
+
content: generatePage(schemaId, processed, {
|
|
58
59
|
webhooks: [item],
|
|
59
60
|
hasHead: false,
|
|
60
61
|
}, {
|
|
@@ -68,7 +69,7 @@ export async function generatePages(schemaId, processed, options = {}) {
|
|
|
68
69
|
}
|
|
69
70
|
return result;
|
|
70
71
|
}
|
|
71
|
-
export
|
|
72
|
+
export function generateTags(schemaId, processed, options = {}) {
|
|
72
73
|
const { document } = processed;
|
|
73
74
|
if (!document.tags)
|
|
74
75
|
return [];
|
|
@@ -81,7 +82,7 @@ export async function generateTags(schemaId, processed, options = {}) {
|
|
|
81
82
|
: idToTitle(tag.name);
|
|
82
83
|
return {
|
|
83
84
|
tag: tag.name,
|
|
84
|
-
content:
|
|
85
|
+
content: generatePage(schemaId, processed, {
|
|
85
86
|
operations,
|
|
86
87
|
webhooks,
|
|
87
88
|
hasHead: true,
|
|
@@ -96,3 +97,99 @@ export async function generateTags(schemaId, processed, options = {}) {
|
|
|
96
97
|
};
|
|
97
98
|
});
|
|
98
99
|
}
|
|
100
|
+
export function generateDocument(frontmatter, content, options) {
|
|
101
|
+
const { addGeneratedComment = true, imports } = options;
|
|
102
|
+
const out = [];
|
|
103
|
+
const banner = dump(frontmatter).trim();
|
|
104
|
+
if (banner.length > 0)
|
|
105
|
+
out.push(`---\n${banner}\n---`);
|
|
106
|
+
if (addGeneratedComment) {
|
|
107
|
+
let commentContent = 'This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again.';
|
|
108
|
+
if (typeof addGeneratedComment === 'string') {
|
|
109
|
+
commentContent = addGeneratedComment;
|
|
110
|
+
}
|
|
111
|
+
commentContent = commentContent.replaceAll('/', '\\/');
|
|
112
|
+
out.push(`{/* ${commentContent} */}`);
|
|
113
|
+
}
|
|
114
|
+
if (imports) {
|
|
115
|
+
out.push(...imports
|
|
116
|
+
.map((item) => `import { ${item.names.join(', ')} } from ${JSON.stringify(item.from)};`)
|
|
117
|
+
.join('\n'));
|
|
118
|
+
}
|
|
119
|
+
out.push(content);
|
|
120
|
+
return out.join('\n\n');
|
|
121
|
+
}
|
|
122
|
+
function generatePage(schemaId, processed, pageProps, options, context) {
|
|
123
|
+
const { frontmatter, includeDescription = false } = options;
|
|
124
|
+
const extend = frontmatter?.(options.title, options.description, context);
|
|
125
|
+
const page = {
|
|
126
|
+
...pageProps,
|
|
127
|
+
document: schemaId,
|
|
128
|
+
};
|
|
129
|
+
let meta;
|
|
130
|
+
if (page.operations?.length === 1) {
|
|
131
|
+
const operation = page.operations[0];
|
|
132
|
+
meta = {
|
|
133
|
+
method: operation.method.toUpperCase(),
|
|
134
|
+
route: operation.path,
|
|
135
|
+
};
|
|
136
|
+
}
|
|
137
|
+
const data = generateStaticData(processed.document, page);
|
|
138
|
+
const content = [];
|
|
139
|
+
if (options.description && includeDescription)
|
|
140
|
+
content.push(options.description);
|
|
141
|
+
content.push(pageContent(page));
|
|
142
|
+
return generateDocument({
|
|
143
|
+
title: options.title,
|
|
144
|
+
description: !includeDescription ? options.description : undefined,
|
|
145
|
+
full: true,
|
|
146
|
+
...extend,
|
|
147
|
+
_openapi: {
|
|
148
|
+
...meta,
|
|
149
|
+
...data,
|
|
150
|
+
...extend?._openapi,
|
|
151
|
+
},
|
|
152
|
+
}, content.join('\n\n'), options);
|
|
153
|
+
}
|
|
154
|
+
function generateStaticData(dereferenced, props) {
|
|
155
|
+
const slugger = new Slugger();
|
|
156
|
+
const toc = [];
|
|
157
|
+
const structuredData = { headings: [], contents: [] };
|
|
158
|
+
for (const item of props.operations ?? []) {
|
|
159
|
+
const operation = dereferenced.paths?.[item.path]?.[item.method];
|
|
160
|
+
if (!operation)
|
|
161
|
+
continue;
|
|
162
|
+
if (props.hasHead && operation.operationId) {
|
|
163
|
+
const title = operation.summary ??
|
|
164
|
+
(operation.operationId ? idToTitle(operation.operationId) : item.path);
|
|
165
|
+
const id = slugger.slug(title);
|
|
166
|
+
toc.push({
|
|
167
|
+
depth: 2,
|
|
168
|
+
title,
|
|
169
|
+
url: `#${id}`,
|
|
170
|
+
});
|
|
171
|
+
structuredData.headings.push({
|
|
172
|
+
content: title,
|
|
173
|
+
id,
|
|
174
|
+
});
|
|
175
|
+
}
|
|
176
|
+
if (operation.description)
|
|
177
|
+
structuredData.contents.push({
|
|
178
|
+
content: operation.description,
|
|
179
|
+
heading: structuredData.headings.at(-1)?.id,
|
|
180
|
+
});
|
|
181
|
+
}
|
|
182
|
+
return { toc, structuredData };
|
|
183
|
+
}
|
|
184
|
+
function pageContent(props) {
|
|
185
|
+
// filter extra properties in props
|
|
186
|
+
const operations = (props.operations ?? []).map((item) => ({
|
|
187
|
+
path: item.path,
|
|
188
|
+
method: item.method,
|
|
189
|
+
}));
|
|
190
|
+
const webhooks = (props.webhooks ?? []).map((item) => ({
|
|
191
|
+
name: item.name,
|
|
192
|
+
method: item.method,
|
|
193
|
+
}));
|
|
194
|
+
return `<APIPage document={${JSON.stringify(props.document)}} operations={${JSON.stringify(operations)}} webhooks={${JSON.stringify(webhooks)}} hasHead={${JSON.stringify(props.hasHead)}} />`;
|
|
195
|
+
}
|
package/dist/render/api-page.js
CHANGED
|
@@ -3,12 +3,12 @@ import Slugger from 'github-slugger';
|
|
|
3
3
|
import { Operation } from '../render/operation/index.js';
|
|
4
4
|
import { createMethod } from '../server/create-method.js';
|
|
5
5
|
import { createRenders } from '../render/renderer.js';
|
|
6
|
-
import {
|
|
6
|
+
import { processDocumentCached, } from '../utils/process-document.js';
|
|
7
7
|
import { defaultAdapters } from '../media/adapter.js';
|
|
8
8
|
export async function APIPage(props) {
|
|
9
9
|
const { operations, hasHead = true, webhooks } = props;
|
|
10
10
|
const processed = typeof props.document === 'string'
|
|
11
|
-
? await
|
|
11
|
+
? await processDocumentCached(props.document)
|
|
12
12
|
: await props.document;
|
|
13
13
|
const ctx = await getContext(processed, props);
|
|
14
14
|
const { document } = processed;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"schema.d.ts","sourceRoot":"","sources":["../../src/render/schema.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAY,KAAK,SAAS,EAAE,MAAM,OAAO,CAAC;AACjD,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,gBAAgB,CAAC;AACrD,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,SAAS,CAAC;AAe7C,wBAAgB,MAAM,CAAC,EACrB,IAAI,EACJ,MAAM,EACN,QAAgB,EAChB,QAAgB,EAChB,SAAiB,EACjB,EAAe,EACf,GAAG,EAAE,aAAa,GACnB,EAAE;IACD,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,MAAM,EAAE,cAAc,CAAC;IACvB,EAAE,CAAC,EAAE,UAAU,GAAG,MAAM,CAAC;IAEzB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,GAAG,EAAE,aAAa,CAAC;CACpB,GAAG,SAAS,
|
|
1
|
+
{"version":3,"file":"schema.d.ts","sourceRoot":"","sources":["../../src/render/schema.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAY,KAAK,SAAS,EAAE,MAAM,OAAO,CAAC;AACjD,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,gBAAgB,CAAC;AACrD,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,SAAS,CAAC;AAe7C,wBAAgB,MAAM,CAAC,EACrB,IAAI,EACJ,MAAM,EACN,QAAgB,EAChB,QAAgB,EAChB,SAAiB,EACjB,EAAe,EACf,GAAG,EAAE,aAAa,GACnB,EAAE;IACD,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,MAAM,EAAE,cAAc,CAAC;IACvB,EAAE,CAAC,EAAE,UAAU,GAAG,MAAM,CAAC;IAEzB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,GAAG,EAAE,aAAa,CAAC;CACpB,GAAG,SAAS,CA+TZ"}
|
package/dist/render/schema.js
CHANGED
|
@@ -7,6 +7,12 @@ import { Tabs, TabsContent, TabsList, TabsTrigger, } from 'fumadocs-ui/component
|
|
|
7
7
|
export function Schema({ name, schema, required = false, readOnly = false, writeOnly = false, as = 'property', ctx: renderContext, }) {
|
|
8
8
|
const { renderer } = renderContext;
|
|
9
9
|
function propertyBody(schema, renderPrimitive, ctx) {
|
|
10
|
+
if (ctx.stack.has(schema))
|
|
11
|
+
return;
|
|
12
|
+
const next = {
|
|
13
|
+
...ctx,
|
|
14
|
+
stack: ctx.stack.next(schema),
|
|
15
|
+
};
|
|
10
16
|
if (Array.isArray(schema.type)) {
|
|
11
17
|
const items = schema.type.flatMap((type) => {
|
|
12
18
|
const composed = {
|
|
@@ -20,24 +26,25 @@ export function Schema({ name, schema, required = false, readOnly = false, write
|
|
|
20
26
|
if (items.length === 0)
|
|
21
27
|
return;
|
|
22
28
|
if (items.length === 1)
|
|
23
|
-
return propertyBody(items[0], renderPrimitive,
|
|
29
|
+
return propertyBody(items[0], renderPrimitive, next);
|
|
24
30
|
return (_jsxs(Tabs, { defaultValue: items[0].type, children: [_jsx(TabsList, { children: items.map((item) => (_jsx(TabsTrigger, { value: item.type, children: schemaToString(item, renderContext.schema) }, item.type))) }), items.map((item) => (_jsxs(TabsContent, { value: item.type, children: [item.description && _jsx(Markdown, { text: item.description }), propertyInfo(item), renderPrimitive(item, ctx)] }, item.type)))] }));
|
|
25
31
|
}
|
|
26
32
|
if (schema.oneOf) {
|
|
27
33
|
const oneOf = schema.oneOf.filter((item) => isComplexType(item));
|
|
28
|
-
if (oneOf.length === 0)
|
|
34
|
+
if (oneOf.length === 0 || oneOf.some((item) => ctx.stack.has(item)))
|
|
29
35
|
return;
|
|
30
36
|
if (oneOf.length === 1) {
|
|
31
|
-
return propertyBody(oneOf[0], renderPrimitive,
|
|
37
|
+
return propertyBody(oneOf[0], renderPrimitive, next);
|
|
32
38
|
}
|
|
33
|
-
return (_jsxs(Tabs, { defaultValue: "0", children: [_jsx(TabsList, { children: oneOf.map((item, i) => (_jsx(TabsTrigger, { value: i.toString(), children: schemaToString(item, renderContext.schema) }, i))) }), oneOf.map((item, i) => (_jsxs(TabsContent, { value: i.toString(), children: [item.description && _jsx(Markdown, { text: item.description }), propertyInfo(item), propertyBody(item, (child, ctx) => primitiveBody(child, ctx, false, true),
|
|
39
|
+
return (_jsxs(Tabs, { defaultValue: "0", children: [_jsx(TabsList, { children: oneOf.map((item, i) => (_jsx(TabsTrigger, { value: i.toString(), children: schemaToString(item, renderContext.schema) }, i))) }), oneOf.map((item, i) => (_jsxs(TabsContent, { value: i.toString(), children: [item.description && _jsx(Markdown, { text: item.description }), propertyInfo(item), propertyBody(item, (child, ctx) => primitiveBody(child, ctx, false, true), next)] }, i)))] }));
|
|
34
40
|
}
|
|
35
41
|
const of = schema.allOf ?? schema.anyOf;
|
|
36
42
|
if (of) {
|
|
37
|
-
|
|
38
|
-
|
|
43
|
+
if (of.length === 0)
|
|
44
|
+
return;
|
|
45
|
+
if (of.some((item) => typeof item === 'object' && ctx.stack.has(item)))
|
|
39
46
|
return;
|
|
40
|
-
const combined = combineSchema(
|
|
47
|
+
const combined = combineSchema(of);
|
|
41
48
|
if (typeof combined === 'boolean')
|
|
42
49
|
return;
|
|
43
50
|
return renderPrimitive(combined, ctx);
|
|
@@ -99,9 +106,9 @@ export function Schema({ name, schema, required = false, readOnly = false, write
|
|
|
99
106
|
return (_jsx("div", { className: "flex flex-wrap gap-2 not-prose", children: fields.map((field) => (_jsxs("div", { className: "bg-fd-secondary border rounded-lg text-xs p-1.5 shadow-md", children: [_jsx("span", { className: "font-medium me-2", children: field.key }), _jsx("code", { className: "text-fd-muted-foreground", children: field.value })] }, field.key))) }));
|
|
100
107
|
}
|
|
101
108
|
function primitiveBody(schema, ctx, collapsible, nested) {
|
|
109
|
+
if (ctx.stack.has(schema))
|
|
110
|
+
return _jsx("p", { children: "Recursive" });
|
|
102
111
|
if (schema.type === 'object') {
|
|
103
|
-
if (ctx.stack.has(schema))
|
|
104
|
-
return _jsx("p", { children: "Recursive" });
|
|
105
112
|
const props = Object.entries(schema.properties ?? {});
|
|
106
113
|
const patternProps = Object.entries(schema.patternProperties ?? {});
|
|
107
114
|
const next = {
|
|
@@ -138,12 +145,14 @@ export function Schema({ name, schema, required = false, readOnly = false, write
|
|
|
138
145
|
else if (schema === false) {
|
|
139
146
|
return _jsx(renderer.Property, { name: key, type: "never", ...props });
|
|
140
147
|
}
|
|
148
|
+
if (ctx.stack.has(schema))
|
|
149
|
+
return;
|
|
141
150
|
if ((schema.readOnly && !readOnly) || (schema.writeOnly && !writeOnly))
|
|
142
151
|
return;
|
|
143
152
|
return (_jsxs(renderer.Property, { name: key, type: schemaToString(schema, renderContext.schema), deprecated: schema.deprecated, ...props, children: [schema.description && _jsx(Markdown, { text: schema.description }), propertyInfo(schema), propertyBody(schema, (child, ctx) => primitiveBody(child, ctx, true, true), ctx)] }));
|
|
144
153
|
}
|
|
145
154
|
const context = {
|
|
146
|
-
stack: schemaStack(),
|
|
155
|
+
stack: schemaStack(renderContext),
|
|
147
156
|
};
|
|
148
157
|
if (typeof schema === 'boolean' ||
|
|
149
158
|
as === 'property' ||
|
|
@@ -151,12 +160,17 @@ export function Schema({ name, schema, required = false, readOnly = false, write
|
|
|
151
160
|
return property(name, schema, context, { required });
|
|
152
161
|
return propertyBody(schema, (child, ctx) => primitiveBody(child, ctx, false, false), context);
|
|
153
162
|
}
|
|
154
|
-
function schemaStack(parent) {
|
|
155
|
-
const
|
|
156
|
-
|
|
163
|
+
function schemaStack(renderContext, parent) {
|
|
164
|
+
const ids = new Set();
|
|
165
|
+
function getId(schema) {
|
|
166
|
+
if (typeof schema !== 'object')
|
|
167
|
+
return;
|
|
168
|
+
return schema.title ?? renderContext.schema.dereferenceMap.get(schema);
|
|
169
|
+
}
|
|
157
170
|
return {
|
|
171
|
+
history: parent ? [...parent.history] : [],
|
|
158
172
|
next(...schemas) {
|
|
159
|
-
const child = schemaStack(this);
|
|
173
|
+
const child = schemaStack(renderContext, this);
|
|
160
174
|
for (const item of schemas) {
|
|
161
175
|
child.add(item);
|
|
162
176
|
}
|
|
@@ -165,18 +179,23 @@ function schemaStack(parent) {
|
|
|
165
179
|
add(schema) {
|
|
166
180
|
if (typeof schema !== 'object')
|
|
167
181
|
return;
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
182
|
+
const id = getId(schema);
|
|
183
|
+
if (id)
|
|
184
|
+
ids.add(id);
|
|
185
|
+
this.history.push(schema);
|
|
171
186
|
},
|
|
172
187
|
has(schema) {
|
|
173
|
-
if (
|
|
174
|
-
|
|
175
|
-
|
|
188
|
+
if (this.history.length > 30) {
|
|
189
|
+
console.warn(`[Fumadocs OpenAPI] schema depth exceeded 30, this might be unexpected.`);
|
|
190
|
+
// stopping at here
|
|
176
191
|
return true;
|
|
177
|
-
|
|
192
|
+
}
|
|
193
|
+
if (parent && parent.has(schema))
|
|
178
194
|
return true;
|
|
179
|
-
|
|
195
|
+
const id = getId(schema);
|
|
196
|
+
if (id)
|
|
197
|
+
return ids.has(id);
|
|
198
|
+
return this.history.includes(schema);
|
|
180
199
|
},
|
|
181
200
|
};
|
|
182
201
|
}
|
package/dist/server/create.d.ts
CHANGED
|
@@ -56,9 +56,6 @@ export interface OpenAPIOptions extends SharedOpenAPIOptions {
|
|
|
56
56
|
* - a function returning records of downloaded schemas.
|
|
57
57
|
*/
|
|
58
58
|
input?: string[] | (() => Promise<SchemaMap>);
|
|
59
|
-
/**
|
|
60
|
-
* By default, it is disabled on dev mode
|
|
61
|
-
*/
|
|
62
59
|
disableCache?: boolean;
|
|
63
60
|
}
|
|
64
61
|
export interface OpenAPIServer {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"create.d.ts","sourceRoot":"","sources":["../../src/server/create.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC;AACtD,OAAO,EAAE,WAAW,EAAE,MAAM,gBAAgB,CAAC;AAC7C,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,oBAAoB,CAAC;AACrD,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,mBAAmB,CAAC;AAClD,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,gBAAgB,CAAC;AAClD,OAAO,KAAK,EACV,YAAY,EACZ,iBAAiB,EACjB,uBAAuB,EACxB,MAAM,OAAO,CAAC;AACf,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,iBAAiB,CAAC;AACpD,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,SAAS,CAAC;AACjD,OAAO,KAAK,EAAE,SAAS,EAAE,WAAW,EAAE,MAAM,eAAe,CAAC;AAC5D,OAAO,
|
|
1
|
+
{"version":3,"file":"create.d.ts","sourceRoot":"","sources":["../../src/server/create.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC;AACtD,OAAO,EAAE,WAAW,EAAE,MAAM,gBAAgB,CAAC;AAC7C,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,oBAAoB,CAAC;AACrD,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,mBAAmB,CAAC;AAClD,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,gBAAgB,CAAC;AAClD,OAAO,KAAK,EACV,YAAY,EACZ,iBAAiB,EACjB,uBAAuB,EACxB,MAAM,OAAO,CAAC;AACf,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,iBAAiB,CAAC;AACpD,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,SAAS,CAAC;AACjD,OAAO,KAAK,EAAE,SAAS,EAAE,WAAW,EAAE,MAAM,eAAe,CAAC;AAC5D,OAAO,EAGL,KAAK,iBAAiB,EACvB,MAAM,0BAA0B,CAAC;AAElC,KAAK,SAAS,CAAC,CAAC,IAAI,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC,CAAC;AACnC;;GAEG;AACH,KAAK,SAAS,GAAG,MAAM,CAAC,MAAM,EAAE,WAAW,CAAC,QAAQ,GAAG,SAAS,CAAC,QAAQ,CAAC,CAAC;AAC3E,KAAK,kBAAkB,GAAG,MAAM,CAAC,MAAM,EAAE,iBAAiB,CAAC,CAAC;AAE5D,MAAM,WAAW,oBAAoB;IACnC;;OAEG;IACH,QAAQ,CAAC,EAAE,MAAM,CAAC;IAElB,QAAQ,CAAC,EAAE,OAAO,CAAC,QAAQ,CAAC,CAAC;IAE7B;;;;OAIG;IACH,iBAAiB,CAAC,EAAE,OAAO,CAAC;IAE5B;;;;;;;OAOG;IACH,wBAAwB,CAAC,EACrB,CAAC,CACC,MAAM,EAAE,WAAW,CAAC,iBAAiB,CAAC,EACtC,UAAU,EAAE,MAAM,KACf,SAAS,CAAC,MAAM,CAAC,CAAC,GACvB,KAAK,CAAC;IAEV;;OAEG;IACH,mBAAmB,CAAC,EAAE,CAAC,MAAM,EAAE,iBAAiB,KAAK,SAAS,CAAC,UAAU,EAAE,CAAC,CAAC;IAE7E,YAAY,CAAC,EAAE,IAAI,CAAC,uBAAuB,EAAE,MAAM,CAAC,GAClD,iBAAiB,CAAC,YAAY,CAAC,CAAC;IAElC;;;;OAIG;IACH,kBAAkB,CAAC,EAAE,OAAO,CAAC;IAE7B,aAAa,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,YAAY,CAAC,CAAC;CAC9C;AAED,MAAM,WAAW,cAAe,SAAQ,oBAAoB;IAC1D;;;;;OAKG;IACH,KAAK,CAAC,EAAE,MAAM,EAAE,GAAG,CAAC,MAAM,OAAO,CAAC,SAAS,CAAC,CAAC,CAAC;IAE9C,YAAY,CAAC,EAAE,OAAO,CAAC;CACxB;AAED,MAAM,WAAW,aAAa;IAC5B,eAAe,EAAE,CAAC,IAAI,EAAE,YAAY,KAAK,YAAY,CAAC;IACtD,WAAW,EAAE,OAAO,WAAW,CAAC;IAChC,UAAU,EAAE,MAAM,OAAO,CAAC,kBAAkB,CAAC,CAAC;CAC/C;AAED,wBAAgB,aAAa,CAAC,OAAO,GAAE,cAAmB,GAAG,aAAa,CA4CzE;AAED,wBAAgB,gBAAgB,CAAC,CAAC,EAAE,OAAO,EAAE,UAAU,CAAC,CAAC,CAAC,GAAG,UAAU,CAEtE"}
|
package/dist/server/create.js
CHANGED
|
@@ -1,18 +1,18 @@
|
|
|
1
1
|
import { createProxy } from '../server/proxy.js';
|
|
2
|
-
import { processDocument, } from '../utils/process-document.js';
|
|
2
|
+
import { processDocument, processDocumentCached, } from '../utils/process-document.js';
|
|
3
3
|
export function createOpenAPI(options = {}) {
|
|
4
|
-
const { input = [], disableCache =
|
|
4
|
+
const { input = [], disableCache = false, ...shared } = options;
|
|
5
5
|
let schemas;
|
|
6
6
|
async function getSchemas() {
|
|
7
7
|
const out = {};
|
|
8
8
|
if (Array.isArray(input)) {
|
|
9
9
|
await Promise.all(input.map(async (item) => {
|
|
10
|
-
out[item] = await processDocument(item
|
|
10
|
+
out[item] = await processDocument(item);
|
|
11
11
|
}));
|
|
12
12
|
}
|
|
13
13
|
else {
|
|
14
14
|
await Promise.all(Object.entries(await input()).map(async ([k, v]) => {
|
|
15
|
-
out[k] = await processDocument(v
|
|
15
|
+
out[k] = await processDocument(v);
|
|
16
16
|
}));
|
|
17
17
|
}
|
|
18
18
|
return out;
|
|
@@ -20,6 +20,8 @@ export function createOpenAPI(options = {}) {
|
|
|
20
20
|
return {
|
|
21
21
|
createProxy,
|
|
22
22
|
async getSchemas() {
|
|
23
|
+
if (disableCache)
|
|
24
|
+
return getSchemas();
|
|
23
25
|
return (schemas ?? (schemas = getSchemas()));
|
|
24
26
|
},
|
|
25
27
|
getAPIPageProps({ document, ...props }) {
|
|
@@ -28,7 +30,7 @@ export function createOpenAPI(options = {}) {
|
|
|
28
30
|
...props,
|
|
29
31
|
document: typeof document === 'string'
|
|
30
32
|
? this.getSchemas().then((map) => {
|
|
31
|
-
return map[document] ??
|
|
33
|
+
return map[document] ?? processDocumentCached(document);
|
|
32
34
|
})
|
|
33
35
|
: document,
|
|
34
36
|
};
|
|
@@ -6,8 +6,9 @@ export type ProcessedDocument = {
|
|
|
6
6
|
dereferenceMap: DereferenceMap;
|
|
7
7
|
downloaded: Document;
|
|
8
8
|
};
|
|
9
|
+
export declare function processDocumentCached(input: string | OpenAPIV3_1.Document | OpenAPIV3.Document): Promise<ProcessedDocument>;
|
|
9
10
|
/**
|
|
10
11
|
* process & reference input document to a Fumadocs OpenAPI compatible format
|
|
11
12
|
*/
|
|
12
|
-
export declare function processDocument(input: string | OpenAPIV3_1.Document | OpenAPIV3.Document
|
|
13
|
+
export declare function processDocument(input: string | OpenAPIV3_1.Document | OpenAPIV3.Document): Promise<ProcessedDocument>;
|
|
13
14
|
//# sourceMappingURL=process-document.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"process-document.d.ts","sourceRoot":"","sources":["../../src/utils/process-document.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAC;AACxD,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,gBAAgB,CAAC;AAClD,OAAO,KAAK,EAAE,SAAS,EAAE,WAAW,EAAE,MAAM,eAAe,CAAC;AAI5D,MAAM,MAAM,iBAAiB,GAAG;IAC9B,QAAQ,EAAE,WAAW,CAAC,QAAQ,CAAC,CAAC;IAChC,cAAc,EAAE,cAAc,CAAC;IAC/B,UAAU,EAAE,QAAQ,CAAC;CACtB,CAAC;AAIF;;GAEG;AACH,wBAAsB,eAAe,CACnC,KAAK,EAAE,MAAM,GAAG,WAAW,CAAC,QAAQ,GAAG,SAAS,CAAC,QAAQ,
|
|
1
|
+
{"version":3,"file":"process-document.d.ts","sourceRoot":"","sources":["../../src/utils/process-document.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAC;AACxD,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,gBAAgB,CAAC;AAClD,OAAO,KAAK,EAAE,SAAS,EAAE,WAAW,EAAE,MAAM,eAAe,CAAC;AAI5D,MAAM,MAAM,iBAAiB,GAAG;IAC9B,QAAQ,EAAE,WAAW,CAAC,QAAQ,CAAC,CAAC;IAChC,cAAc,EAAE,cAAc,CAAC;IAC/B,UAAU,EAAE,QAAQ,CAAC;CACtB,CAAC;AAIF,wBAAsB,qBAAqB,CACzC,KAAK,EAAE,MAAM,GAAG,WAAW,CAAC,QAAQ,GAAG,SAAS,CAAC,QAAQ,GACxD,OAAO,CAAC,iBAAiB,CAAC,CAS5B;AAED;;GAEG;AACH,wBAAsB,eAAe,CACnC,KAAK,EAAE,MAAM,GAAG,WAAW,CAAC,QAAQ,GAAG,SAAS,CAAC,QAAQ,GACxD,OAAO,CAAC,iBAAiB,CAAC,CAoB5B"}
|
|
@@ -1,13 +1,20 @@
|
|
|
1
1
|
import { bundle, dereference, upgrade } from '@scalar/openapi-parser';
|
|
2
2
|
import { fetchUrls, readFiles } from '@scalar/openapi-parser/plugins';
|
|
3
3
|
const cache = new Map();
|
|
4
|
+
export async function processDocumentCached(input) {
|
|
5
|
+
if (typeof input !== 'string')
|
|
6
|
+
return processDocument(input);
|
|
7
|
+
const cached = cache.get(input);
|
|
8
|
+
if (cached)
|
|
9
|
+
return cached;
|
|
10
|
+
const processed = await processDocument(input);
|
|
11
|
+
cache.set(input, processed);
|
|
12
|
+
return processed;
|
|
13
|
+
}
|
|
4
14
|
/**
|
|
5
15
|
* process & reference input document to a Fumadocs OpenAPI compatible format
|
|
6
16
|
*/
|
|
7
|
-
export async function processDocument(input
|
|
8
|
-
const cached = !disableCache && typeof input === 'string' ? cache.get(input) : null;
|
|
9
|
-
if (cached)
|
|
10
|
-
return cached;
|
|
17
|
+
export async function processDocument(input) {
|
|
11
18
|
const dereferenceMap = new Map();
|
|
12
19
|
let document = await bundle(input, {
|
|
13
20
|
plugins: [fetchUrls(), readFiles()],
|
|
@@ -20,13 +27,9 @@ export async function processDocument(input, disableCache = false) {
|
|
|
20
27
|
dereferenceMap.set(schema, ref);
|
|
21
28
|
},
|
|
22
29
|
});
|
|
23
|
-
|
|
30
|
+
return {
|
|
24
31
|
document: dereferenced,
|
|
25
32
|
dereferenceMap,
|
|
26
33
|
downloaded: document,
|
|
27
34
|
};
|
|
28
|
-
if (!disableCache && typeof input === 'string') {
|
|
29
|
-
cache.set(input, processed);
|
|
30
|
-
}
|
|
31
|
-
return processed;
|
|
32
35
|
}
|
package/dist/utils/url.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"url.d.ts","sourceRoot":"","sources":["../../src/utils/url.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAC;AAEtD,wBAAgB,OAAO,CAAC,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,MAAM,CAM9D;AAED;;;GAGG;AACH,wBAAgB,QAAQ,CAAC,GAAG,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,MAAM,CAM1D;AAED,wBAAgB,gBAAgB,CAC9B,QAAQ,EAAE,MAAM,EAChB,SAAS,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAChC,MAAM,CAMR;AAED,wBAAgB,kBAAkB,CAChC,QAAQ,EAAE,MAAM,EAChB,EAAE,IAAI,EAAE,KAAK,EAAE,EAAE,WAAW,GAC3B,MAAM,
|
|
1
|
+
{"version":3,"file":"url.d.ts","sourceRoot":"","sources":["../../src/utils/url.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAC;AAEtD,wBAAgB,OAAO,CAAC,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,MAAM,CAM9D;AAED;;;GAGG;AACH,wBAAgB,QAAQ,CAAC,GAAG,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,MAAM,CAM1D;AAED,wBAAgB,gBAAgB,CAC9B,QAAQ,EAAE,MAAM,EAChB,SAAS,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAChC,MAAM,CAMR;AAED,wBAAgB,kBAAkB,CAChC,QAAQ,EAAE,MAAM,EAChB,EAAE,IAAI,EAAE,KAAK,EAAE,EAAE,WAAW,GAC3B,MAAM,CAoCR"}
|
package/dist/utils/url.js
CHANGED
|
@@ -25,6 +25,7 @@ export function resolveServerUrl(template, variables) {
|
|
|
25
25
|
return template;
|
|
26
26
|
}
|
|
27
27
|
export function resolveRequestData(pathname, { path, query }) {
|
|
28
|
+
// First, resolve path parameters in the pathname
|
|
28
29
|
for (const key in path) {
|
|
29
30
|
const param = path[key];
|
|
30
31
|
if (Array.isArray(param.value)) {
|
|
@@ -34,17 +35,25 @@ export function resolveRequestData(pathname, { path, query }) {
|
|
|
34
35
|
pathname = pathname.replace(`{${key}}`, param.value);
|
|
35
36
|
}
|
|
36
37
|
}
|
|
37
|
-
|
|
38
|
+
// Check if pathname already contains query parameters (legacy API support)
|
|
39
|
+
const [pathPart, existingQueryString] = pathname.split('?', 2);
|
|
40
|
+
// Parse existing query parameters from the pathname if they exist
|
|
41
|
+
const searchParams = new URLSearchParams(existingQueryString || '');
|
|
42
|
+
// Add new query parameters from the RequestData
|
|
38
43
|
for (const key in query) {
|
|
39
44
|
const param = query[key];
|
|
40
45
|
if (Array.isArray(param.value)) {
|
|
46
|
+
// Remove existing parameter first to avoid duplicates
|
|
47
|
+
searchParams.delete(key);
|
|
41
48
|
for (const item of param.value) {
|
|
42
49
|
searchParams.append(key, item);
|
|
43
50
|
}
|
|
44
51
|
}
|
|
45
52
|
else {
|
|
46
|
-
|
|
53
|
+
// Set (replace if exists) the parameter value
|
|
54
|
+
searchParams.set(key, param.value);
|
|
47
55
|
}
|
|
48
56
|
}
|
|
49
|
-
|
|
57
|
+
// Return the reconstructed URL
|
|
58
|
+
return searchParams.size > 0 ? `${pathPart}?${searchParams}` : pathPart;
|
|
50
59
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "fumadocs-openapi",
|
|
3
|
-
"version": "9.
|
|
3
|
+
"version": "9.3.1",
|
|
4
4
|
"description": "Generate MDX docs for your OpenAPI spec",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"NextJs",
|
|
@@ -54,6 +54,7 @@
|
|
|
54
54
|
"ajv": "^8.17.1",
|
|
55
55
|
"class-variance-authority": "^0.7.1",
|
|
56
56
|
"github-slugger": "^2.0.0",
|
|
57
|
+
"gray-matter": "^4.0.3",
|
|
57
58
|
"hast-util-to-jsx-runtime": "^2.3.6",
|
|
58
59
|
"js-yaml": "^4.1.0",
|
|
59
60
|
"next-themes": "^0.4.6",
|
|
@@ -64,8 +65,8 @@
|
|
|
64
65
|
"shiki": "^3.11.0",
|
|
65
66
|
"tinyglobby": "^0.2.14",
|
|
66
67
|
"xml-js": "^1.6.11",
|
|
67
|
-
"fumadocs-core": "15.7.
|
|
68
|
-
"fumadocs-ui": "15.7.
|
|
68
|
+
"fumadocs-core": "15.7.4",
|
|
69
|
+
"fumadocs-ui": "15.7.4"
|
|
69
70
|
},
|
|
70
71
|
"devDependencies": {
|
|
71
72
|
"@scalar/api-client-react": "^1.3.29",
|
|
@@ -1,17 +0,0 @@
|
|
|
1
|
-
import type { ApiPageProps } from '../render/api-page.js';
|
|
2
|
-
import type { GenerateOptions } from '../generate.js';
|
|
3
|
-
import type { TagObject } from '../types.js';
|
|
4
|
-
import type { ProcessedDocument } from '../utils/process-document.js';
|
|
5
|
-
export type DocumentContext = {
|
|
6
|
-
type: 'tag';
|
|
7
|
-
tag: TagObject | undefined;
|
|
8
|
-
} | {
|
|
9
|
-
type: 'operation';
|
|
10
|
-
} | {
|
|
11
|
-
type: 'file';
|
|
12
|
-
};
|
|
13
|
-
export declare function generateDocument(schemaId: string, processed: ProcessedDocument, pageProps: Omit<ApiPageProps, 'document'>, options: GenerateOptions & {
|
|
14
|
-
title: string;
|
|
15
|
-
description?: string;
|
|
16
|
-
}, context: DocumentContext): string;
|
|
17
|
-
//# sourceMappingURL=generate-document.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"generate-document.d.ts","sourceRoot":"","sources":["../../src/utils/generate-document.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EACV,YAAY,EAGb,MAAM,mBAAmB,CAAC;AAC3B,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,YAAY,CAAC;AAElD,OAAO,KAAK,EAAY,SAAS,EAAE,MAAM,SAAS,CAAC;AAEnD,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,0BAA0B,CAAC;AAOlE,MAAM,MAAM,eAAe,GACvB;IACE,IAAI,EAAE,KAAK,CAAC;IACZ,GAAG,EAAE,SAAS,GAAG,SAAS,CAAC;CAC5B,GACD;IACE,IAAI,EAAE,WAAW,CAAC;CACnB,GACD;IACE,IAAI,EAAE,MAAM,CAAC;CACd,CAAC;AAEN,wBAAgB,gBAAgB,CAC9B,QAAQ,EAAE,MAAM,EAChB,SAAS,EAAE,iBAAiB,EAC5B,SAAS,EAAE,IAAI,CAAC,YAAY,EAAE,UAAU,CAAC,EACzC,OAAO,EAAE,eAAe,GAAG;IACzB,KAAK,EAAE,MAAM,CAAC;IACd,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB,EACD,OAAO,EAAE,eAAe,GACvB,MAAM,CAkER"}
|
|
@@ -1,94 +0,0 @@
|
|
|
1
|
-
import { dump } from 'js-yaml';
|
|
2
|
-
import Slugger from 'github-slugger';
|
|
3
|
-
import { idToTitle } from '../utils/id-to-title.js';
|
|
4
|
-
export function generateDocument(schemaId, processed, pageProps, options, context) {
|
|
5
|
-
const { frontmatter, includeDescription = false, addGeneratedComment = true, } = options;
|
|
6
|
-
const out = [];
|
|
7
|
-
const extend = frontmatter?.(options.title, options.description, context);
|
|
8
|
-
const page = {
|
|
9
|
-
...pageProps,
|
|
10
|
-
document: schemaId,
|
|
11
|
-
};
|
|
12
|
-
let meta;
|
|
13
|
-
if (page.operations?.length === 1) {
|
|
14
|
-
const operation = page.operations[0];
|
|
15
|
-
meta = {
|
|
16
|
-
method: operation.method.toUpperCase(),
|
|
17
|
-
route: operation.path,
|
|
18
|
-
};
|
|
19
|
-
}
|
|
20
|
-
const data = generateStaticData(processed.document, page);
|
|
21
|
-
const banner = dump({
|
|
22
|
-
title: options.title,
|
|
23
|
-
description: !includeDescription ? options.description : undefined,
|
|
24
|
-
full: true,
|
|
25
|
-
...extend,
|
|
26
|
-
_openapi: {
|
|
27
|
-
...meta,
|
|
28
|
-
...data,
|
|
29
|
-
...extend?._openapi,
|
|
30
|
-
},
|
|
31
|
-
}).trim();
|
|
32
|
-
if (banner.length > 0)
|
|
33
|
-
out.push(`---\n${banner}\n---`);
|
|
34
|
-
if (addGeneratedComment !== false) {
|
|
35
|
-
let commentContent = 'This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again.';
|
|
36
|
-
if (typeof addGeneratedComment === 'string') {
|
|
37
|
-
commentContent = addGeneratedComment;
|
|
38
|
-
}
|
|
39
|
-
commentContent = commentContent.replaceAll('/', '\\/');
|
|
40
|
-
out.push(`{/* ${commentContent} */}`);
|
|
41
|
-
}
|
|
42
|
-
const imports = options.imports
|
|
43
|
-
?.map((item) => `import { ${item.names.join(', ')} } from ${JSON.stringify(item.from)};`)
|
|
44
|
-
.join('\n');
|
|
45
|
-
if (imports) {
|
|
46
|
-
out.push(imports);
|
|
47
|
-
}
|
|
48
|
-
if (options.description && includeDescription)
|
|
49
|
-
out.push(options.description);
|
|
50
|
-
out.push(pageContent(page));
|
|
51
|
-
return out.join('\n\n');
|
|
52
|
-
}
|
|
53
|
-
function generateStaticData(dereferenced, props) {
|
|
54
|
-
const slugger = new Slugger();
|
|
55
|
-
const toc = [];
|
|
56
|
-
const structuredData = { headings: [], contents: [] };
|
|
57
|
-
for (const item of props.operations ?? []) {
|
|
58
|
-
const operation = dereferenced.paths?.[item.path]?.[item.method];
|
|
59
|
-
if (!operation)
|
|
60
|
-
continue;
|
|
61
|
-
if (props.hasHead && operation.operationId) {
|
|
62
|
-
const title = operation.summary ??
|
|
63
|
-
(operation.operationId ? idToTitle(operation.operationId) : item.path);
|
|
64
|
-
const id = slugger.slug(title);
|
|
65
|
-
toc.push({
|
|
66
|
-
depth: 2,
|
|
67
|
-
title,
|
|
68
|
-
url: `#${id}`,
|
|
69
|
-
});
|
|
70
|
-
structuredData.headings.push({
|
|
71
|
-
content: title,
|
|
72
|
-
id,
|
|
73
|
-
});
|
|
74
|
-
}
|
|
75
|
-
if (operation.description)
|
|
76
|
-
structuredData.contents.push({
|
|
77
|
-
content: operation.description,
|
|
78
|
-
heading: structuredData.headings.at(-1)?.id,
|
|
79
|
-
});
|
|
80
|
-
}
|
|
81
|
-
return { toc, structuredData };
|
|
82
|
-
}
|
|
83
|
-
function pageContent(props) {
|
|
84
|
-
// filter extra properties in props
|
|
85
|
-
const operations = (props.operations ?? []).map((item) => ({
|
|
86
|
-
path: item.path,
|
|
87
|
-
method: item.method,
|
|
88
|
-
}));
|
|
89
|
-
const webhooks = (props.webhooks ?? []).map((item) => ({
|
|
90
|
-
name: item.name,
|
|
91
|
-
method: item.method,
|
|
92
|
-
}));
|
|
93
|
-
return `<APIPage document={${JSON.stringify(props.document)}} operations={${JSON.stringify(operations)}} webhooks={${JSON.stringify(webhooks)}} hasHead={${JSON.stringify(props.hasHead)}} />`;
|
|
94
|
-
}
|