fumadocs-openapi 3.2.0 → 4.0.0
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/chunk-RSFOKBAM.js +115 -0
- package/dist/index.d.ts +9 -2
- package/dist/index.js +291 -58
- package/dist/playground-D8pYn13F.d.ts +52 -0
- package/dist/playground-XE3Y6DRJ.js +963 -0
- package/dist/ui/index.d.ts +46 -0
- package/dist/ui/index.js +197 -0
- package/package.json +39 -4
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
// src/ui/shared.ts
|
|
2
|
+
import { cva } from "class-variance-authority";
|
|
3
|
+
var badgeVariants = cva(
|
|
4
|
+
"rounded border px-1.5 py-1 text-xs font-medium leading-[12px]",
|
|
5
|
+
{
|
|
6
|
+
variants: {
|
|
7
|
+
color: {
|
|
8
|
+
green: "border-green-400/50 bg-green-400/20 text-green-600 dark:text-green-400",
|
|
9
|
+
yellow: "border-yellow-400/50 bg-yellow-400/20 text-yellow-600 dark:text-yellow-400",
|
|
10
|
+
red: "border-red-400/50 bg-red-400/20 text-red-600 dark:text-red-400",
|
|
11
|
+
blue: "border-blue-400/50 bg-blue-400/20 text-blue-600 dark:text-blue-400",
|
|
12
|
+
orange: "border-orange-400/50 bg-orange-400/20 text-orange-600 dark:text-orange-400"
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
);
|
|
17
|
+
function getBadgeColor(method) {
|
|
18
|
+
switch (method) {
|
|
19
|
+
case "PUT":
|
|
20
|
+
return "yellow";
|
|
21
|
+
case "PATCH":
|
|
22
|
+
return "orange";
|
|
23
|
+
case "POST":
|
|
24
|
+
return "blue";
|
|
25
|
+
case "DELETE":
|
|
26
|
+
return "red";
|
|
27
|
+
default:
|
|
28
|
+
return "green";
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
function getDefaultValue(item, references) {
|
|
32
|
+
if (item.type === "object")
|
|
33
|
+
return Object.fromEntries(
|
|
34
|
+
Object.entries(item.properties).map(([key, prop]) => [
|
|
35
|
+
key,
|
|
36
|
+
getDefaultValue(references[prop.schema], references)
|
|
37
|
+
])
|
|
38
|
+
);
|
|
39
|
+
if (item.type === "array") return [];
|
|
40
|
+
if (item.type === "null") return null;
|
|
41
|
+
if (item.type === "switcher")
|
|
42
|
+
return getDefaultValue(
|
|
43
|
+
resolve(Object.values(item.items)[0], references),
|
|
44
|
+
references
|
|
45
|
+
);
|
|
46
|
+
return String(item.defaultValue);
|
|
47
|
+
}
|
|
48
|
+
function getDefaultValues(field, context) {
|
|
49
|
+
return Object.fromEntries(
|
|
50
|
+
field.map((p) => [p.name, getDefaultValue(p, context)])
|
|
51
|
+
);
|
|
52
|
+
}
|
|
53
|
+
function resolve(schema, references) {
|
|
54
|
+
if (typeof schema === "string") return references[schema];
|
|
55
|
+
if (schema.type !== "ref") return schema;
|
|
56
|
+
return {
|
|
57
|
+
...references[schema.schema],
|
|
58
|
+
description: schema.description,
|
|
59
|
+
isRequired: schema.isRequired
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// src/ui/contexts/api.tsx
|
|
64
|
+
import { createContext, useContext, useEffect, useState } from "react";
|
|
65
|
+
import { jsx } from "react/jsx-runtime";
|
|
66
|
+
var ApiContext = createContext({
|
|
67
|
+
baseUrl: void 0,
|
|
68
|
+
setBaseUrl: () => void 0,
|
|
69
|
+
highlighter: null
|
|
70
|
+
});
|
|
71
|
+
function useApiContext() {
|
|
72
|
+
return useContext(ApiContext);
|
|
73
|
+
}
|
|
74
|
+
async function initHighlighter() {
|
|
75
|
+
const { createHighlighterCore } = await import("shiki/core");
|
|
76
|
+
const getWasm = await import("shiki/wasm");
|
|
77
|
+
return createHighlighterCore({
|
|
78
|
+
themes: [
|
|
79
|
+
import("shiki/themes/github-light.mjs"),
|
|
80
|
+
import("shiki/themes/github-dark.mjs")
|
|
81
|
+
],
|
|
82
|
+
langs: [import("shiki/langs/json.mjs")],
|
|
83
|
+
loadWasm: getWasm
|
|
84
|
+
});
|
|
85
|
+
}
|
|
86
|
+
var highlighterInstance;
|
|
87
|
+
function ApiProvider({
|
|
88
|
+
defaultBaseUrl,
|
|
89
|
+
children
|
|
90
|
+
}) {
|
|
91
|
+
const [highlighter, setHighlighter] = useState(null);
|
|
92
|
+
const [baseUrl, setBaseUrl] = useState(defaultBaseUrl);
|
|
93
|
+
useEffect(() => {
|
|
94
|
+
setBaseUrl((prev) => localStorage.getItem("apiBaseUrl") ?? prev);
|
|
95
|
+
if (highlighterInstance) setHighlighter(highlighterInstance);
|
|
96
|
+
else
|
|
97
|
+
void initHighlighter().then((res) => {
|
|
98
|
+
setHighlighter(res);
|
|
99
|
+
});
|
|
100
|
+
}, []);
|
|
101
|
+
useEffect(() => {
|
|
102
|
+
if (baseUrl) localStorage.setItem("apiBaseUrl", baseUrl);
|
|
103
|
+
}, [baseUrl]);
|
|
104
|
+
return /* @__PURE__ */ jsx(ApiContext.Provider, { value: { baseUrl, setBaseUrl, highlighter }, children });
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
export {
|
|
108
|
+
badgeVariants,
|
|
109
|
+
getBadgeColor,
|
|
110
|
+
getDefaultValue,
|
|
111
|
+
getDefaultValues,
|
|
112
|
+
resolve,
|
|
113
|
+
useApiContext,
|
|
114
|
+
ApiProvider
|
|
115
|
+
};
|
package/dist/index.d.ts
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import { OpenAPIV3 } from 'openapi-types';
|
|
2
|
+
import { A as APIPlaygroundProps } from './playground-D8pYn13F.js';
|
|
3
|
+
export { P as PrimitiveRequestField, a as ReferenceSchema, R as RequestSchema } from './playground-D8pYn13F.js';
|
|
2
4
|
|
|
3
5
|
interface ResponsesProps {
|
|
4
6
|
items: string[];
|
|
@@ -24,8 +26,11 @@ interface RequestProps {
|
|
|
24
26
|
name: string;
|
|
25
27
|
code: string;
|
|
26
28
|
}
|
|
29
|
+
interface RootProps {
|
|
30
|
+
baseUrl?: string;
|
|
31
|
+
}
|
|
27
32
|
interface Renderer {
|
|
28
|
-
Root: (child: string[]) => string;
|
|
33
|
+
Root: (props: RootProps, child: string[]) => string;
|
|
29
34
|
API: (child: string[]) => string;
|
|
30
35
|
APIInfo: (props: APIInfoProps, child: string[]) => string;
|
|
31
36
|
APIExample: (child: string[]) => string;
|
|
@@ -41,7 +46,9 @@ interface Renderer {
|
|
|
41
46
|
*/
|
|
42
47
|
ObjectCollapsible: (props: ObjectCollapsibleProps, child: string[]) => string;
|
|
43
48
|
Property: (props: PropertyProps, child: string[]) => string;
|
|
49
|
+
APIPlayground: (props: APIPlaygroundProps) => string;
|
|
44
50
|
}
|
|
51
|
+
|
|
45
52
|
declare const defaultRenderer: Renderer;
|
|
46
53
|
|
|
47
54
|
interface CodeSample {
|
|
@@ -147,4 +154,4 @@ declare function generateFiles({ input, output, name: nameFn, per, cwd, ...optio
|
|
|
147
154
|
|
|
148
155
|
declare function createElement(name: string, props: object, ...child: string[]): string;
|
|
149
156
|
|
|
150
|
-
export { type APIInfoProps, type Config, type GenerateOperationOutput, type GenerateOptions, type GenerateTagOutput, type MethodInformation, type ObjectCollapsibleProps, type PropertyProps, type RenderContext, type Renderer, type RequestProps, type ResponseProps, type ResponsesProps, type RouteInformation, createElement, defaultRenderer, generate, generateFiles, generateOperations, generateTags };
|
|
157
|
+
export { type APIInfoProps, APIPlaygroundProps, type Config, type GenerateOperationOutput, type GenerateOptions, type GenerateTagOutput, type MethodInformation, type ObjectCollapsibleProps, type PropertyProps, type RenderContext, type Renderer, type RequestProps, type ResponseProps, type ResponsesProps, type RootProps, type RouteInformation, createElement, defaultRenderer, generate, generateFiles, generateOperations, generateTags };
|
package/dist/index.js
CHANGED
|
@@ -46,7 +46,7 @@ function buildOperation(method, operation) {
|
|
|
46
46
|
};
|
|
47
47
|
}
|
|
48
48
|
|
|
49
|
-
// src/
|
|
49
|
+
// src/utils/generate-document.ts
|
|
50
50
|
import { dump } from "js-yaml";
|
|
51
51
|
|
|
52
52
|
// src/render/element.ts
|
|
@@ -78,7 +78,7 @@ function heading(depth, child) {
|
|
|
78
78
|
|
|
79
79
|
// src/render/renderer.ts
|
|
80
80
|
var defaultRenderer = {
|
|
81
|
-
Root: (child) => createElement("Root",
|
|
81
|
+
Root: (props, child) => createElement("Root", props, ...child),
|
|
82
82
|
API: (child) => createElement("API", {}, ...child),
|
|
83
83
|
APIInfo: (props, child) => createElement("APIInfo", props, ...child),
|
|
84
84
|
APIExample: (child) => createElement("APIExample", {}, ...child),
|
|
@@ -94,11 +94,12 @@ var defaultRenderer = {
|
|
|
94
94
|
Property: (props, child) => createElement("Property", props, ...child),
|
|
95
95
|
ObjectCollapsible: (props, child) => createElement("ObjectCollapsible", props, ...child),
|
|
96
96
|
Requests: (items, child) => createElement("Requests", { items }, ...child),
|
|
97
|
-
Request: ({ language, code, name }) => createElement("Request", { value: name }, codeblock({ language }, code))
|
|
97
|
+
Request: ({ language, code, name }) => createElement("Request", { value: name }, codeblock({ language }, code)),
|
|
98
|
+
APIPlayground: (props) => createElement("APIPlayground", props)
|
|
98
99
|
};
|
|
99
100
|
|
|
100
|
-
// src/
|
|
101
|
-
function
|
|
101
|
+
// src/utils/generate-document.ts
|
|
102
|
+
function generateDocument(title, description, content, options) {
|
|
102
103
|
const banner = dump({
|
|
103
104
|
title,
|
|
104
105
|
description,
|
|
@@ -108,19 +109,30 @@ function renderPage(title, description, content, options) {
|
|
|
108
109
|
const finalImports = (options.imports ?? [
|
|
109
110
|
{
|
|
110
111
|
names: Object.keys(defaultRenderer),
|
|
111
|
-
from: "fumadocs-ui
|
|
112
|
+
from: "fumadocs-openapi/ui"
|
|
112
113
|
}
|
|
113
114
|
]).map(
|
|
114
115
|
(item) => `import { ${item.names.join(", ")} } from ${JSON.stringify(item.from)};`
|
|
115
116
|
).join("\n");
|
|
116
|
-
const Root = options.renderer?.Root ?? defaultRenderer.Root;
|
|
117
117
|
return `---
|
|
118
118
|
${banner}
|
|
119
119
|
---
|
|
120
120
|
|
|
121
121
|
${finalImports}
|
|
122
122
|
|
|
123
|
-
${
|
|
123
|
+
${content}`;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
// src/utils/id-to-title.ts
|
|
127
|
+
function idToTitle(id) {
|
|
128
|
+
const result = [];
|
|
129
|
+
for (const c of id) {
|
|
130
|
+
if (result.length === 0) result.push(c.toLocaleUpperCase());
|
|
131
|
+
else if (/^[A-Z]$/.test(c) && result.at(-1) !== " ") result.push(" ", c);
|
|
132
|
+
else if (c === "-") result.push(" ");
|
|
133
|
+
else result.push(c);
|
|
134
|
+
}
|
|
135
|
+
return result.join("");
|
|
124
136
|
}
|
|
125
137
|
|
|
126
138
|
// src/utils/schema.ts
|
|
@@ -266,6 +278,160 @@ async function getTypescriptSchema(endpoint, code) {
|
|
|
266
278
|
}
|
|
267
279
|
}
|
|
268
280
|
|
|
281
|
+
// src/utils/get-security.ts
|
|
282
|
+
function getScheme(requirement, document) {
|
|
283
|
+
const results = [];
|
|
284
|
+
const schemas = document.components?.securitySchemes ?? {};
|
|
285
|
+
for (const [key, scopes] of Object.entries(requirement)) {
|
|
286
|
+
if (!(key in schemas)) return [];
|
|
287
|
+
const schema = noRef(schemas[key]);
|
|
288
|
+
results.push({
|
|
289
|
+
...schema,
|
|
290
|
+
scopes
|
|
291
|
+
});
|
|
292
|
+
}
|
|
293
|
+
return results;
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
// src/render/playground.ts
|
|
297
|
+
function renderPlayground(path, method, ctx) {
|
|
298
|
+
let currentId = 0;
|
|
299
|
+
const context = {
|
|
300
|
+
schema: {},
|
|
301
|
+
nextId() {
|
|
302
|
+
return String(currentId++);
|
|
303
|
+
},
|
|
304
|
+
registered: /* @__PURE__ */ new WeakMap()
|
|
305
|
+
};
|
|
306
|
+
const body = method.requestBody ? getPreferredMedia(noRef(method.requestBody).content) : void 0;
|
|
307
|
+
return ctx.renderer.APIPlayground({
|
|
308
|
+
authorization: getAuthorizationField(method, ctx),
|
|
309
|
+
method: method.method,
|
|
310
|
+
route: path,
|
|
311
|
+
path: method.parameters.filter((v) => v.in === "path").map((v) => parameterToField(v, context)),
|
|
312
|
+
query: method.parameters.filter((v) => v.in === "query").map((v) => parameterToField(v, context)),
|
|
313
|
+
header: method.parameters.filter((v) => v.in === "header").map((v) => parameterToField(v, context)),
|
|
314
|
+
body: body?.schema ? toSchema(noRef(body.schema), true, context) : void 0,
|
|
315
|
+
schemas: context.schema
|
|
316
|
+
});
|
|
317
|
+
}
|
|
318
|
+
function getAuthorizationField(method, ctx) {
|
|
319
|
+
const security = method.security ?? ctx.document.security ?? [];
|
|
320
|
+
if (security.length === 0) return;
|
|
321
|
+
const singular = security.find(
|
|
322
|
+
(requirements) => Object.keys(requirements).length === 1
|
|
323
|
+
);
|
|
324
|
+
if (!singular) return;
|
|
325
|
+
const scheme = getScheme(singular, ctx.document)[0];
|
|
326
|
+
return {
|
|
327
|
+
type: "string",
|
|
328
|
+
name: "Authorization",
|
|
329
|
+
defaultValue: scheme.type === "oauth2" || scheme.type === "http" && scheme.scheme === "bearer" ? "Bearer" : "Basic",
|
|
330
|
+
isRequired: security.every(
|
|
331
|
+
(requirements) => Object.keys(requirements).length > 0
|
|
332
|
+
),
|
|
333
|
+
description: "The Authorization access token"
|
|
334
|
+
};
|
|
335
|
+
}
|
|
336
|
+
function getIdFromSchema(schema, required, ctx) {
|
|
337
|
+
const registered = ctx.registered.get(schema);
|
|
338
|
+
if (registered === void 0) {
|
|
339
|
+
const id = ctx.nextId();
|
|
340
|
+
ctx.registered.set(schema, id);
|
|
341
|
+
ctx.schema[id] = toSchema(schema, required, ctx);
|
|
342
|
+
return id;
|
|
343
|
+
}
|
|
344
|
+
return registered;
|
|
345
|
+
}
|
|
346
|
+
function parameterToField(v, ctx) {
|
|
347
|
+
return {
|
|
348
|
+
name: v.name,
|
|
349
|
+
...toSchema(
|
|
350
|
+
noRef(v.schema) ?? { type: "string" },
|
|
351
|
+
v.required ?? false,
|
|
352
|
+
ctx
|
|
353
|
+
)
|
|
354
|
+
};
|
|
355
|
+
}
|
|
356
|
+
function toReference(schema, required, ctx) {
|
|
357
|
+
return {
|
|
358
|
+
type: "ref",
|
|
359
|
+
isRequired: required,
|
|
360
|
+
schema: getIdFromSchema(schema, false, ctx)
|
|
361
|
+
};
|
|
362
|
+
}
|
|
363
|
+
function toSchema(schema, required, ctx) {
|
|
364
|
+
if (schema.type === "array") {
|
|
365
|
+
return {
|
|
366
|
+
type: "array",
|
|
367
|
+
description: schema.description ?? schema.title,
|
|
368
|
+
isRequired: required,
|
|
369
|
+
items: getIdFromSchema(noRef(schema.items), false, ctx)
|
|
370
|
+
};
|
|
371
|
+
}
|
|
372
|
+
if (schema.type === "object" || schema.properties !== void 0 || schema.allOf !== void 0) {
|
|
373
|
+
const properties = {};
|
|
374
|
+
Object.entries(schema.properties ?? {}).forEach(([key, prop]) => {
|
|
375
|
+
properties[key] = toReference(
|
|
376
|
+
noRef(prop),
|
|
377
|
+
schema.required?.includes(key) ?? false,
|
|
378
|
+
ctx
|
|
379
|
+
);
|
|
380
|
+
});
|
|
381
|
+
schema.allOf?.forEach((c) => {
|
|
382
|
+
const field = toSchema(noRef(c), true, ctx);
|
|
383
|
+
if (field.type === "object") Object.assign(properties, field.properties);
|
|
384
|
+
});
|
|
385
|
+
const additional = noRef(schema.additionalProperties);
|
|
386
|
+
let additionalProperties;
|
|
387
|
+
if (additional && typeof additional === "object") {
|
|
388
|
+
if (!additional.type && !additional.anyOf && !additional.allOf && !additional.oneOf) {
|
|
389
|
+
additionalProperties = true;
|
|
390
|
+
} else {
|
|
391
|
+
additionalProperties = getIdFromSchema(additional, false, ctx);
|
|
392
|
+
}
|
|
393
|
+
} else {
|
|
394
|
+
additionalProperties = additional;
|
|
395
|
+
}
|
|
396
|
+
return {
|
|
397
|
+
type: "object",
|
|
398
|
+
isRequired: required,
|
|
399
|
+
description: schema.description ?? schema.title,
|
|
400
|
+
properties,
|
|
401
|
+
additionalProperties
|
|
402
|
+
};
|
|
403
|
+
}
|
|
404
|
+
if (schema.type === void 0) {
|
|
405
|
+
const combine = schema.anyOf ?? schema.oneOf;
|
|
406
|
+
if (combine) {
|
|
407
|
+
return {
|
|
408
|
+
type: "switcher",
|
|
409
|
+
description: schema.description ?? schema.title,
|
|
410
|
+
items: Object.fromEntries(
|
|
411
|
+
combine.map((c, idx) => {
|
|
412
|
+
const item = noRef(c);
|
|
413
|
+
return [
|
|
414
|
+
item.title ?? item.type ?? `Item ${idx.toString()}`,
|
|
415
|
+
toReference(item, true, ctx)
|
|
416
|
+
];
|
|
417
|
+
})
|
|
418
|
+
),
|
|
419
|
+
isRequired: required
|
|
420
|
+
};
|
|
421
|
+
}
|
|
422
|
+
return {
|
|
423
|
+
type: "null",
|
|
424
|
+
isRequired: false
|
|
425
|
+
};
|
|
426
|
+
}
|
|
427
|
+
return {
|
|
428
|
+
type: schema.type === "integer" ? "number" : schema.type,
|
|
429
|
+
defaultValue: schema.example ?? "",
|
|
430
|
+
isRequired: required,
|
|
431
|
+
description: schema.description ?? schema.title
|
|
432
|
+
};
|
|
433
|
+
}
|
|
434
|
+
|
|
269
435
|
// src/render/schema.ts
|
|
270
436
|
var keys = {
|
|
271
437
|
example: "Example",
|
|
@@ -278,42 +444,46 @@ var keys = {
|
|
|
278
444
|
format: "Format"
|
|
279
445
|
};
|
|
280
446
|
function isObject(schema) {
|
|
281
|
-
return schema.type === "object" || schema.properties !== void 0;
|
|
447
|
+
return schema.type === "object" || schema.properties !== void 0 || schema.additionalProperties !== void 0;
|
|
282
448
|
}
|
|
283
449
|
function schemaElement(name, schema, ctx) {
|
|
450
|
+
return render(name, schema, {
|
|
451
|
+
...ctx,
|
|
452
|
+
stack: []
|
|
453
|
+
});
|
|
454
|
+
}
|
|
455
|
+
function render(name, schema, ctx) {
|
|
284
456
|
if (schema.readOnly && !ctx.readOnly) return "";
|
|
285
457
|
if (schema.writeOnly && !ctx.writeOnly) return "";
|
|
286
458
|
const { renderer } = ctx.render;
|
|
287
459
|
const child = [];
|
|
288
460
|
function field(key, value) {
|
|
289
|
-
child.push(span(`${key}:
|
|
461
|
+
child.push(span(`${key}: \`${value}\``));
|
|
290
462
|
}
|
|
291
463
|
if (isObject(schema) && ctx.parseObject) {
|
|
292
464
|
const { additionalProperties, properties } = schema;
|
|
293
|
-
if (additionalProperties) {
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
);
|
|
312
|
-
}
|
|
465
|
+
if (additionalProperties === true) {
|
|
466
|
+
child.push(
|
|
467
|
+
renderer.Property(
|
|
468
|
+
{
|
|
469
|
+
name: "[key: string]",
|
|
470
|
+
type: "any"
|
|
471
|
+
},
|
|
472
|
+
[]
|
|
473
|
+
)
|
|
474
|
+
);
|
|
475
|
+
} else if (additionalProperties) {
|
|
476
|
+
child.push(
|
|
477
|
+
render("[key: string]", noRef(additionalProperties), {
|
|
478
|
+
...ctx,
|
|
479
|
+
required: false,
|
|
480
|
+
parseObject: false
|
|
481
|
+
})
|
|
482
|
+
);
|
|
313
483
|
}
|
|
314
484
|
Object.entries(properties ?? {}).forEach(([key, value]) => {
|
|
315
485
|
child.push(
|
|
316
|
-
|
|
486
|
+
render(key, noRef(value), {
|
|
317
487
|
...ctx,
|
|
318
488
|
required: schema.required?.includes(key) ?? false,
|
|
319
489
|
parseObject: false
|
|
@@ -334,17 +504,48 @@ function schemaElement(name, schema, ctx) {
|
|
|
334
504
|
schema.enum.map((value) => JSON.stringify(value)).join(" | ")
|
|
335
505
|
);
|
|
336
506
|
}
|
|
337
|
-
|
|
338
|
-
|
|
507
|
+
if (isObject(schema) && !ctx.parseObject) {
|
|
508
|
+
child.push(
|
|
509
|
+
renderer.ObjectCollapsible({ name }, [
|
|
510
|
+
render(name, schema, {
|
|
511
|
+
...ctx,
|
|
512
|
+
parseObject: true,
|
|
513
|
+
required: false
|
|
514
|
+
})
|
|
515
|
+
])
|
|
516
|
+
);
|
|
517
|
+
} else if (schema.allOf) {
|
|
339
518
|
child.push(
|
|
340
519
|
renderer.ObjectCollapsible({ name }, [
|
|
341
|
-
|
|
520
|
+
render(name, combineSchema(schema.allOf.map(noRef)), {
|
|
342
521
|
...ctx,
|
|
343
522
|
parseObject: true,
|
|
344
523
|
required: false
|
|
345
524
|
})
|
|
346
525
|
])
|
|
347
526
|
);
|
|
527
|
+
} else {
|
|
528
|
+
const mentionedObjectTypes = [
|
|
529
|
+
...schema.anyOf ?? schema.oneOf ?? [],
|
|
530
|
+
...schema.not ? [schema.not] : [],
|
|
531
|
+
...schema.type === "array" ? [schema.items] : []
|
|
532
|
+
].map(noRef).filter((s) => isComplexType(s) && !ctx.stack.includes(s));
|
|
533
|
+
ctx.stack.push(schema);
|
|
534
|
+
child.push(
|
|
535
|
+
...mentionedObjectTypes.map(
|
|
536
|
+
(s, idx) => renderer.ObjectCollapsible(
|
|
537
|
+
{ name: s.title ?? `Object ${(idx + 1).toString()}` },
|
|
538
|
+
[
|
|
539
|
+
render("element", noRef(s), {
|
|
540
|
+
...ctx,
|
|
541
|
+
parseObject: true,
|
|
542
|
+
required: false
|
|
543
|
+
})
|
|
544
|
+
]
|
|
545
|
+
)
|
|
546
|
+
)
|
|
547
|
+
);
|
|
548
|
+
ctx.stack.pop();
|
|
348
549
|
}
|
|
349
550
|
return renderer.Property(
|
|
350
551
|
{
|
|
@@ -356,28 +557,51 @@ function schemaElement(name, schema, ctx) {
|
|
|
356
557
|
child
|
|
357
558
|
);
|
|
358
559
|
}
|
|
359
|
-
function
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
560
|
+
function combineSchema(schema) {
|
|
561
|
+
const result = {
|
|
562
|
+
type: "object"
|
|
563
|
+
};
|
|
564
|
+
function add(s) {
|
|
565
|
+
result.properties ??= {};
|
|
566
|
+
if (s.properties) {
|
|
567
|
+
Object.assign(result.properties, s.properties);
|
|
568
|
+
}
|
|
569
|
+
result.additionalProperties ??= {};
|
|
570
|
+
if (s.additionalProperties === true) {
|
|
571
|
+
result.additionalProperties = true;
|
|
572
|
+
} else if (s.additionalProperties && typeof result.additionalProperties !== "boolean") {
|
|
573
|
+
Object.assign(result.additionalProperties, s.additionalProperties);
|
|
574
|
+
}
|
|
575
|
+
if (s.allOf) {
|
|
576
|
+
add(combineSchema(s.allOf.map(noRef)));
|
|
577
|
+
}
|
|
363
578
|
}
|
|
579
|
+
schema.forEach(add);
|
|
580
|
+
return result;
|
|
581
|
+
}
|
|
582
|
+
function isComplexType(schema) {
|
|
583
|
+
if (schema.anyOf ?? schema.oneOf ?? schema.allOf) return true;
|
|
584
|
+
return isObject(schema) || schema.type === "array";
|
|
364
585
|
}
|
|
365
586
|
function getSchemaType(schema) {
|
|
366
587
|
if (schema.nullable) {
|
|
367
|
-
|
|
368
|
-
return
|
|
588
|
+
const type = getSchemaType({ ...schema, nullable: false });
|
|
589
|
+
return type === "unknown" ? "null" : `${type} | null`;
|
|
369
590
|
}
|
|
591
|
+
if (schema.title) return schema.title;
|
|
370
592
|
if (schema.type === "array")
|
|
371
|
-
return `array
|
|
593
|
+
return `array<${getSchemaType(noRef(schema.items))}>`;
|
|
372
594
|
if (schema.oneOf)
|
|
373
595
|
return schema.oneOf.map((one) => getSchemaType(noRef(one))).join(" | ");
|
|
374
596
|
if (schema.allOf)
|
|
375
597
|
return schema.allOf.map((one) => getSchemaType(noRef(one))).join(" & ");
|
|
376
|
-
if (schema.
|
|
598
|
+
if (schema.not) return `not ${getSchemaType(noRef(schema.not))}`;
|
|
599
|
+
if (schema.anyOf) {
|
|
377
600
|
return `Any properties in ${schema.anyOf.map((one) => getSchemaType(noRef(one))).join(", ")}`;
|
|
601
|
+
}
|
|
378
602
|
if (schema.type) return schema.type;
|
|
379
603
|
if (isObject(schema)) return "object";
|
|
380
|
-
|
|
604
|
+
return "unknown";
|
|
381
605
|
}
|
|
382
606
|
|
|
383
607
|
// src/render/operation.ts
|
|
@@ -387,9 +611,13 @@ async function renderOperation(path, method, ctx, noTitle = false) {
|
|
|
387
611
|
const security = method.security ?? ctx.document.security;
|
|
388
612
|
const info = [];
|
|
389
613
|
const example = [];
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
614
|
+
if (!noTitle) {
|
|
615
|
+
info.push(
|
|
616
|
+
heading(
|
|
617
|
+
level,
|
|
618
|
+
method.summary ?? (method.operationId ? idToTitle(method.operationId) : path)
|
|
619
|
+
)
|
|
620
|
+
);
|
|
393
621
|
level++;
|
|
394
622
|
}
|
|
395
623
|
if (method.description) info.push(p(method.description));
|
|
@@ -448,6 +676,7 @@ async function renderOperation(path, method, ctx, noTitle = false) {
|
|
|
448
676
|
info.push(heading(level, group), ...parameters);
|
|
449
677
|
}
|
|
450
678
|
info.push(getResponseTable(method));
|
|
679
|
+
info.push(renderPlayground(path, method, ctx));
|
|
451
680
|
const samples = dedupe([
|
|
452
681
|
{
|
|
453
682
|
label: "cURL",
|
|
@@ -492,12 +721,9 @@ function dedupe(samples) {
|
|
|
492
721
|
}
|
|
493
722
|
function getAuthSection(requirements, { document, renderer }) {
|
|
494
723
|
const info = [];
|
|
495
|
-
const schemas = document.components?.securitySchemes ?? {};
|
|
496
724
|
for (const requirement of requirements) {
|
|
497
725
|
if (info.length > 0) info.push(`---`);
|
|
498
|
-
for (const
|
|
499
|
-
if (!(name in schemas)) continue;
|
|
500
|
-
const schema = noRef(schemas[name]);
|
|
726
|
+
for (const schema of getScheme(requirement, document)) {
|
|
501
727
|
if (schema.type === "http") {
|
|
502
728
|
info.push(
|
|
503
729
|
renderer.Property(
|
|
@@ -524,7 +750,7 @@ function getAuthSection(requirements, { document, renderer }) {
|
|
|
524
750
|
[
|
|
525
751
|
p(schema.description),
|
|
526
752
|
`In: \`header\``,
|
|
527
|
-
`Scope: \`${scopes.length > 0 ? scopes.join(", ") : "none"}\``
|
|
753
|
+
`Scope: \`${schema.scopes.length > 0 ? schema.scopes.join(", ") : "none"}\``
|
|
528
754
|
]
|
|
529
755
|
)
|
|
530
756
|
);
|
|
@@ -606,10 +832,10 @@ async function generate(pathOrDocument, options = {}) {
|
|
|
606
832
|
child.push(await renderOperation(route.path, method, ctx));
|
|
607
833
|
}
|
|
608
834
|
}
|
|
609
|
-
return
|
|
835
|
+
return generateDocument(
|
|
610
836
|
document.info.title,
|
|
611
837
|
document.info.description,
|
|
612
|
-
child,
|
|
838
|
+
ctx.renderer.Root({ baseUrl: ctx.baseUrl }, child),
|
|
613
839
|
options
|
|
614
840
|
);
|
|
615
841
|
}
|
|
@@ -620,10 +846,12 @@ async function generateOperations(pathOrDocument, options = {}) {
|
|
|
620
846
|
return await Promise.all(
|
|
621
847
|
routes.flatMap((route) => {
|
|
622
848
|
return route.methods.map(async (method) => {
|
|
623
|
-
const content =
|
|
849
|
+
const content = generateDocument(
|
|
624
850
|
method.summary ?? method.method,
|
|
625
851
|
method.description,
|
|
626
|
-
|
|
852
|
+
ctx.renderer.Root({ baseUrl: ctx.baseUrl }, [
|
|
853
|
+
await renderOperation(route.path, method, ctx, true)
|
|
854
|
+
]),
|
|
627
855
|
options
|
|
628
856
|
);
|
|
629
857
|
if (!method.operationId)
|
|
@@ -651,7 +879,12 @@ async function generateTags(pathOrDocument, options = {}) {
|
|
|
651
879
|
}
|
|
652
880
|
return {
|
|
653
881
|
tag,
|
|
654
|
-
content:
|
|
882
|
+
content: generateDocument(
|
|
883
|
+
idToTitle(tag),
|
|
884
|
+
info?.description,
|
|
885
|
+
ctx.renderer.Root({ baseUrl: ctx.baseUrl }, child),
|
|
886
|
+
options
|
|
887
|
+
)
|
|
655
888
|
};
|
|
656
889
|
})
|
|
657
890
|
);
|
|
@@ -668,8 +901,8 @@ function getContext(document, options) {
|
|
|
668
901
|
}
|
|
669
902
|
|
|
670
903
|
// src/generate-file.ts
|
|
671
|
-
import { mkdir, writeFile } from "
|
|
672
|
-
import { dirname, join, parse } from "
|
|
904
|
+
import { mkdir, writeFile } from "fs/promises";
|
|
905
|
+
import { dirname, join, parse } from "path";
|
|
673
906
|
import fg from "fast-glob";
|
|
674
907
|
async function generateFiles({
|
|
675
908
|
input,
|