fumadocs-openapi 1.1.0 → 2.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/index.js CHANGED
@@ -1,9 +1,537 @@
1
- import {
2
- dereference,
3
- generate,
4
- generateFiles,
5
- generateTags
6
- } from "./chunk-COCHYFC7.js";
1
+ // src/generate.ts
2
+ import Parser from "@apidevtools/swagger-parser";
3
+
4
+ // src/render/element.ts
5
+ function createElement(name, props, ...child) {
6
+ const s = [];
7
+ const params = Object.entries(props).map(([key, value]) => `${key}={${JSON.stringify(value)}}`).join(" ");
8
+ s.push(params.length > 0 ? `<${name} ${params}>` : `<${name}>`);
9
+ s.push(...child);
10
+ s.push(`</${name}>`);
11
+ return s.join("\n\n");
12
+ }
13
+ function p(child) {
14
+ if (!child)
15
+ return "";
16
+ return child.replace("<", "\\<").replace(">", "\\>");
17
+ }
18
+ function span(child) {
19
+ return `<span>${p(child)}</span>`;
20
+ }
21
+ function codeblock({ language, title }, child) {
22
+ return [
23
+ title ? `\`\`\`${language} title=${JSON.stringify(title)}` : `\`\`\`${language}`,
24
+ child,
25
+ "```"
26
+ ].join("\n");
27
+ }
28
+
29
+ // src/render/custom.ts
30
+ function api(...child) {
31
+ return createElement("API", {}, ...child);
32
+ }
33
+ function apiExample(...child) {
34
+ return createElement("APIExample", {}, ...child);
35
+ }
36
+ function root(...child) {
37
+ return createElement("Root", {}, ...child);
38
+ }
39
+ function apiInfo(props, ...child) {
40
+ return createElement("APIInfo", props, ...child);
41
+ }
42
+ function accordions(...child) {
43
+ return createElement("Accordions", {}, ...child);
44
+ }
45
+ function accordion(props, ...child) {
46
+ return createElement("Accordion", props, ...child);
47
+ }
48
+ function tabs(props, ...child) {
49
+ return createElement("Tabs", props, ...child);
50
+ }
51
+ function tab(props, ...child) {
52
+ return createElement("Tab", props, ...child);
53
+ }
54
+ function property({ required = false, deprecated = false, ...props }, ...child) {
55
+ return createElement(
56
+ "Property",
57
+ { required, deprecated, ...props },
58
+ ...child
59
+ );
60
+ }
61
+
62
+ // src/samples/index.ts
63
+ import { sample } from "openapi-sampler";
64
+
65
+ // src/utils.ts
66
+ function noRef(v) {
67
+ return v;
68
+ }
69
+ function getPreferredMedia(body) {
70
+ if (Object.keys(body).length === 0)
71
+ return void 0;
72
+ if ("application/json" in body)
73
+ return body["application/json"];
74
+ return Object.values(body)[0];
75
+ }
76
+ function getValue(value) {
77
+ return typeof value === "string" ? value : JSON.stringify(value, null, 2);
78
+ }
79
+
80
+ // src/samples/index.ts
81
+ function createEndpoint(path, method, baseUrl = "https://example.com") {
82
+ const params = [];
83
+ const responses = {};
84
+ for (const param of method.parameters) {
85
+ const schema = noRef(
86
+ param.schema ?? getPreferredMedia(param.content ?? {})?.schema
87
+ );
88
+ if (!schema)
89
+ continue;
90
+ params.push({
91
+ name: param.name,
92
+ in: param.in,
93
+ schema
94
+ });
95
+ }
96
+ const body = noRef(method.requestBody)?.content ?? {};
97
+ const bodySchema = noRef(getPreferredMedia(body)?.schema);
98
+ for (const [code, value] of Object.entries(method.responses)) {
99
+ const mediaTypes = noRef(value).content ?? {};
100
+ const responseSchema = noRef(getPreferredMedia(mediaTypes)?.schema);
101
+ if (!responseSchema)
102
+ continue;
103
+ responses[code] = {
104
+ schema: responseSchema
105
+ };
106
+ }
107
+ let pathWithParameters = path;
108
+ const queryParams = new URLSearchParams();
109
+ for (const param of params) {
110
+ const value = generateSample(method.method, param.schema);
111
+ if (param.in === "query")
112
+ queryParams.append(param.name, getValue(value));
113
+ if (param.in === "path")
114
+ pathWithParameters = pathWithParameters.replace(
115
+ `{${param.name}}`,
116
+ getValue(value)
117
+ );
118
+ }
119
+ return {
120
+ url: new URL(pathWithParameters, baseUrl).toString(),
121
+ body: bodySchema ? generateSample(method.method, bodySchema) : void 0,
122
+ responses,
123
+ method: method.method,
124
+ parameters: params
125
+ };
126
+ }
127
+ function generateSample(method, schema) {
128
+ return sample(schema, {
129
+ skipReadOnly: method !== "GET",
130
+ skipWriteOnly: method === "GET"
131
+ });
132
+ }
133
+
134
+ // src/samples/response.ts
135
+ function getExampleResponse(endpoint, code) {
136
+ if (code in endpoint.responses) {
137
+ const value = generateSample(
138
+ endpoint.method,
139
+ endpoint.responses[code].schema
140
+ );
141
+ return JSON.stringify(value, null, 2);
142
+ }
143
+ }
144
+
145
+ // src/samples/typescript.ts
146
+ import { compile } from "json-schema-to-typescript";
147
+ async function getTypescript(endpoint, code) {
148
+ if (code in endpoint.responses) {
149
+ return compile(endpoint.responses[code].schema, "Response", {
150
+ bannerComment: "",
151
+ additionalProperties: false,
152
+ format: true,
153
+ enableConstEnums: false
154
+ });
155
+ }
156
+ }
157
+
158
+ // src/samples/curl.ts
159
+ function getSampleRequest(endpoint) {
160
+ const s = [];
161
+ s.push(`curl -X ${endpoint.method} "${endpoint.url}"`);
162
+ for (const param of endpoint.parameters) {
163
+ if (param.in === "header") {
164
+ const value = generateSample(endpoint.method, param.schema);
165
+ const header = `${param.name}: ${getValue2(value)}`;
166
+ s.push(`-H "${header}"`);
167
+ }
168
+ if (param.in === "formData") {
169
+ console.log("Request example for form data is not supported");
170
+ }
171
+ }
172
+ if (endpoint.body)
173
+ s.push(`-d '${getValue2(endpoint.body)}'`);
174
+ return s.join(" \\\n ");
175
+ }
176
+ function getValue2(value) {
177
+ return typeof value === "string" ? value : JSON.stringify(value, null, 2);
178
+ }
179
+
180
+ // src/render/schema.ts
181
+ var keys = {
182
+ example: "Example",
183
+ default: "Default",
184
+ minimum: "Minimum",
185
+ maximum: "Maximum",
186
+ minLength: "Minimum length",
187
+ maxLength: "Maximum length",
188
+ pattern: "Pattern",
189
+ format: "Format"
190
+ };
191
+ function isObject(schema) {
192
+ return schema.type === "object" || schema.properties !== void 0;
193
+ }
194
+ function schemaElement(name, schema, { parseObject, ...ctx }) {
195
+ if (schema.readOnly && !ctx.readOnly)
196
+ return "";
197
+ if (schema.writeOnly && !ctx.writeOnly)
198
+ return "";
199
+ const child = [];
200
+ function field(key, value) {
201
+ child.push(span(`${key}: \`${value}\``));
202
+ }
203
+ if (isObject(schema) && parseObject) {
204
+ const { additionalProperties, properties } = schema;
205
+ if (additionalProperties) {
206
+ if (additionalProperties === true) {
207
+ child.push(
208
+ property({
209
+ name: "[key: string]",
210
+ type: "any"
211
+ })
212
+ );
213
+ } else {
214
+ child.push(
215
+ schemaElement("[key: string]", noRef(additionalProperties), {
216
+ ...ctx,
217
+ required: false,
218
+ parseObject: false
219
+ })
220
+ );
221
+ }
222
+ }
223
+ Object.entries(properties ?? {}).forEach(([key, value]) => {
224
+ child.push(
225
+ schemaElement(key, noRef(value), {
226
+ ...ctx,
227
+ required: schema.required?.includes(key) ?? false,
228
+ parseObject: false
229
+ })
230
+ );
231
+ });
232
+ return child.join("\n\n");
233
+ }
234
+ child.push(p(schema.description));
235
+ for (const [key, value] of Object.entries(keys)) {
236
+ if (key in schema) {
237
+ field(value, JSON.stringify(schema[key]));
238
+ }
239
+ }
240
+ if (schema.enum) {
241
+ field(
242
+ "Value in",
243
+ schema.enum.map((value) => JSON.stringify(value)).join(" | ")
244
+ );
245
+ }
246
+ const resolved = resolveObjectType(schema);
247
+ if (resolved && !parseObject) {
248
+ child.push(
249
+ accordions(
250
+ accordion(
251
+ { title: "Object Type" },
252
+ schemaElement(name, resolved, {
253
+ ...ctx,
254
+ parseObject: true,
255
+ required: false
256
+ })
257
+ )
258
+ )
259
+ );
260
+ }
261
+ return property(
262
+ {
263
+ name,
264
+ type: getSchemaType(schema),
265
+ required: ctx.required,
266
+ deprecated: schema.deprecated
267
+ },
268
+ ...child
269
+ );
270
+ }
271
+ function resolveObjectType(schema) {
272
+ if (isObject(schema))
273
+ return schema;
274
+ if (schema.type === "array") {
275
+ return resolveObjectType(noRef(schema.items));
276
+ }
277
+ }
278
+ function getSchemaType(schema) {
279
+ if (schema.nullable) {
280
+ return `${getSchemaType({ ...schema, nullable: false })} | null`;
281
+ }
282
+ if (schema.type === "array")
283
+ return `array of ${getSchemaType(noRef(schema.items))}`;
284
+ if (schema.oneOf)
285
+ return schema.oneOf.map((one) => getSchemaType(noRef(one))).join(" | ");
286
+ if (schema.allOf)
287
+ return schema.allOf.map((one) => getSchemaType(noRef(one))).join(" & ");
288
+ if (schema.anyOf)
289
+ return `Any properties in ${schema.anyOf.map((one) => getSchemaType(noRef(one))).join(", ")}`;
290
+ if (schema.type)
291
+ return schema.type;
292
+ if (isObject(schema))
293
+ return "object";
294
+ throw new Error(`Cannot detect object type: ${JSON.stringify(schema)}`);
295
+ }
296
+
297
+ // src/render/operation.ts
298
+ async function renderOperation(path, method, baseUrl) {
299
+ const info = [];
300
+ const example = [];
301
+ info.push(`## ${method.summary ?? method.operationId}`);
302
+ if (method.description)
303
+ info.push(p(method.description));
304
+ const body = noRef(method.requestBody);
305
+ if (body) {
306
+ const bodySchema = getPreferredMedia(body.content)?.schema;
307
+ if (!bodySchema)
308
+ throw new Error();
309
+ info.push(
310
+ `### Request Body${!body.required ? " (Optional)" : ""}`,
311
+ p(body.description),
312
+ schemaElement("body", noRef(bodySchema), {
313
+ parseObject: true,
314
+ readOnly: method.method === "GET",
315
+ writeOnly: method.method !== "GET",
316
+ required: body.required ?? false
317
+ })
318
+ );
319
+ }
320
+ const parameterGroups = /* @__PURE__ */ new Map();
321
+ const endpoint = createEndpoint(path, method, baseUrl);
322
+ for (const param of method.parameters) {
323
+ const schema = noRef(
324
+ param.schema ?? getPreferredMedia(param.content ?? {})?.schema
325
+ );
326
+ if (!schema)
327
+ continue;
328
+ const content = schemaElement(
329
+ param.name,
330
+ {
331
+ ...schema,
332
+ description: param.description ?? schema.description,
333
+ deprecated: param.deprecated || schema.deprecated
334
+ },
335
+ {
336
+ parseObject: false,
337
+ readOnly: method.method === "GET",
338
+ writeOnly: method.method !== "GET",
339
+ required: param.required ?? false
340
+ }
341
+ );
342
+ const groupName = {
343
+ path: "Path Parameters",
344
+ query: "Query Parameters",
345
+ header: "Header Parameters",
346
+ cookie: "Cookie Parameters"
347
+ }[param.in] ?? "Other Parameters";
348
+ const group = parameterGroups.get(groupName) ?? [];
349
+ group.push(content);
350
+ parameterGroups.set(groupName, group);
351
+ }
352
+ for (const [group, parameters] of Array.from(parameterGroups.entries())) {
353
+ info.push(`### ${group}`, ...parameters);
354
+ }
355
+ info.push(getResponseTable(method));
356
+ example.push(
357
+ codeblock({ language: "bash", title: "curl" }, getSampleRequest(endpoint))
358
+ );
359
+ example.push(await getResponseTabs(endpoint, method));
360
+ return api(
361
+ apiInfo({ method: method.method, route: path }, ...info),
362
+ apiExample(...example)
363
+ );
364
+ }
365
+ function getResponseTable(operation) {
366
+ const table = [];
367
+ table.push(`| Status code | Description |`);
368
+ table.push(`| ----------- | ----------- |`);
369
+ Object.entries(operation.responses).forEach(([code, value]) => {
370
+ table.push(`| \`${code}\` | ${noRef(value).description} |`);
371
+ });
372
+ return table.join("\n");
373
+ }
374
+ async function getResponseTabs(endpoint, operation) {
375
+ const items = [];
376
+ const child = [];
377
+ for (const [code, _] of Object.entries(operation.responses)) {
378
+ const example = getExampleResponse(endpoint, code);
379
+ const ts = await getTypescript(endpoint, code);
380
+ const description = code in endpoint.responses ? endpoint.responses[code].schema.description : void 0;
381
+ if (example && ts) {
382
+ items.push(code);
383
+ child.push(
384
+ tab(
385
+ { value: code },
386
+ p(description),
387
+ codeblock({ language: "json", title: "Example Response" }, example),
388
+ accordions(
389
+ accordion(
390
+ { title: "Typescript Definition" },
391
+ codeblock({ language: "ts" }, ts)
392
+ )
393
+ )
394
+ )
395
+ );
396
+ }
397
+ }
398
+ if (items.length === 0)
399
+ return "";
400
+ return tabs(
401
+ {
402
+ items
403
+ },
404
+ ...child
405
+ );
406
+ }
407
+
408
+ // src/generate.ts
409
+ async function dereference(pathOrDocument) {
410
+ return await Parser.dereference(pathOrDocument);
411
+ }
412
+ async function generate(pathOrDocument, options = {}) {
413
+ const document = await dereference(pathOrDocument);
414
+ const tag = options.tag ? document.tags?.find((item) => item.name === options.tag) : void 0;
415
+ const routes = Object.entries(document.paths).map(
416
+ ([key, value]) => {
417
+ if (!value)
418
+ throw new Error("Invalid schema");
419
+ const methodKeys = ["get", "post", "patch", "delete", "head"];
420
+ const methods = [];
421
+ for (const methodKey of methodKeys) {
422
+ const operation = value[methodKey];
423
+ if (!operation)
424
+ continue;
425
+ if (tag && !operation.tags?.includes(tag.name))
426
+ continue;
427
+ methods.push(buildOperation(methodKey, operation));
428
+ }
429
+ return {
430
+ ...value,
431
+ path: key,
432
+ methods
433
+ };
434
+ }
435
+ );
436
+ const serverUrl = document.servers?.[0].url;
437
+ const s = [];
438
+ for (const route of routes) {
439
+ for (const method of route.methods) {
440
+ s.push(await renderOperation(route.path, method, serverUrl));
441
+ }
442
+ }
443
+ return render(
444
+ tag?.name ?? document.info.title,
445
+ tag?.description ?? document.info.description,
446
+ root(...s),
447
+ options
448
+ );
449
+ }
450
+ async function generateTags(pathOrDocument, options = {}) {
451
+ const document = await dereference(pathOrDocument);
452
+ const results = document.tags?.map(async (tag) => {
453
+ return {
454
+ tag: tag.name,
455
+ content: await generate(document, {
456
+ tag: tag.name,
457
+ ...options
458
+ })
459
+ };
460
+ });
461
+ return Promise.all(results ?? []);
462
+ }
463
+ function render(title, description, content, {
464
+ render: fn,
465
+ componentsImportPath = "fumadocs-ui/components/api"
466
+ }) {
467
+ const result = fn?.(title, description, content) ?? {};
468
+ const rendered = {
469
+ frontmatter: result.frontmatter ?? [
470
+ "---",
471
+ title && `title: ${title}`,
472
+ description && `description: ${description}`,
473
+ "---"
474
+ ].filter(Boolean).join("\n"),
475
+ imports: result.imports ?? [
476
+ `import { Root, API, APIInfo, APIExample, Property } from '${componentsImportPath}'`,
477
+ `import { Tabs, Tab } from 'fumadocs-ui/components/tabs'`,
478
+ `import { Accordion, Accordions } from 'fumadocs-ui/components/accordion';`
479
+ ],
480
+ content: result.content ?? content
481
+ };
482
+ return [rendered.frontmatter, rendered.imports.join("\n"), rendered.content].filter(Boolean).join("\n\n");
483
+ }
484
+ function buildOperation(method, operation) {
485
+ return {
486
+ ...operation,
487
+ parameters: operation.parameters ?? [],
488
+ method: method.toUpperCase()
489
+ };
490
+ }
491
+
492
+ // src/generate-file.ts
493
+ import { mkdirSync, writeFileSync } from "node:fs";
494
+ import { dirname, join, parse } from "node:path";
495
+ import fg from "fast-glob";
496
+ async function generateFiles({
497
+ input,
498
+ output,
499
+ name: nameFn,
500
+ per = "file",
501
+ render: render2,
502
+ cwd = process.cwd()
503
+ }) {
504
+ const outputDir = join(cwd, output);
505
+ const options = {
506
+ render: render2
507
+ };
508
+ const resolvedInputs = await fg.glob(input, { absolute: true, cwd });
509
+ await Promise.all(
510
+ resolvedInputs.map(async (path) => {
511
+ let filename = parse(path).name;
512
+ filename = nameFn?.("file", filename) ?? filename;
513
+ if (per === "file") {
514
+ const outPath = join(outputDir, `${filename}.mdx`);
515
+ const result = await generate(path, options);
516
+ write(outPath, result);
517
+ console.log(`Generated: ${outPath}`);
518
+ } else {
519
+ const results = await generateTags(path, options);
520
+ results.forEach((result) => {
521
+ let tagName = result.tag;
522
+ tagName = nameFn?.("tag", tagName) ?? tagName.toLowerCase().replace(/\s+/g, "-");
523
+ const outPath = join(outputDir, filename, `${tagName}.mdx`);
524
+ write(outPath, result.content);
525
+ console.log(`Generated: ${outPath}`);
526
+ });
527
+ }
528
+ })
529
+ );
530
+ }
531
+ function write(path, content) {
532
+ mkdirSync(dirname(path), { recursive: true });
533
+ writeFileSync(path, content);
534
+ }
7
535
  export {
8
536
  dereference,
9
537
  generate,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "fumadocs-openapi",
3
- "version": "1.1.0",
3
+ "version": "2.0.0",
4
4
  "description": "Generate MDX docs for your OpenAPI spec",
5
5
  "keywords": [
6
6
  "NextJs",
@@ -13,9 +13,6 @@
13
13
  "type": "module",
14
14
  "main": "./dist/index.js",
15
15
  "types": "./dist/index.d.ts",
16
- "bin": {
17
- "fumadocs-openapi": "./dist/bin.js"
18
- },
19
16
  "files": [
20
17
  "dist"
21
18
  ],
package/dist/bin.d.ts DELETED
@@ -1 +0,0 @@
1
- #!/usr/bin/env node
package/dist/bin.js DELETED
@@ -1,21 +0,0 @@
1
- #!/usr/bin/env node
2
- import {
3
- generateFiles
4
- } from "./chunk-COCHYFC7.js";
5
-
6
- // src/bin.ts
7
- import { resolve } from "node:path";
8
- import { pathToFileURL } from "node:url";
9
- async function main() {
10
- const configName = process.argv[2];
11
- const config = await readConfig(configName);
12
- await generateFiles(config);
13
- }
14
- async function readConfig(name = "openapi.config.js") {
15
- const path = resolve(process.cwd(), name);
16
- const result = await import(pathToFileURL(path).href);
17
- if (typeof result.default !== "object")
18
- throw new Error("Invalid configuration");
19
- return result.default;
20
- }
21
- void main();
@@ -1,541 +0,0 @@
1
- // src/generate.ts
2
- import Parser from "@apidevtools/swagger-parser";
3
-
4
- // src/render/element.ts
5
- function createElement(name, props, ...child) {
6
- const s = [];
7
- const params = Object.entries(props).map(([key, value]) => `${key}={${JSON.stringify(value)}}`).join(" ");
8
- s.push(params.length > 0 ? `<${name} ${params}>` : `<${name}>`);
9
- s.push(...child);
10
- s.push(`</${name}>`);
11
- return s.join("\n\n");
12
- }
13
- function p(child) {
14
- if (!child)
15
- return "";
16
- return child.replace("<", "\\<").replace(">", "\\>");
17
- }
18
- function span(child) {
19
- return `<span>${p(child)}</span>`;
20
- }
21
- function codeblock({ language, title }, child) {
22
- return [
23
- title ? `\`\`\`${language} title=${JSON.stringify(title)}` : `\`\`\`${language}`,
24
- child,
25
- "```"
26
- ].join("\n");
27
- }
28
-
29
- // src/render/custom.ts
30
- function api(...child) {
31
- return createElement("API", {}, ...child);
32
- }
33
- function apiExample(...child) {
34
- return createElement("APIExample", {}, ...child);
35
- }
36
- function root(...child) {
37
- return createElement("Root", {}, ...child);
38
- }
39
- function apiInfo(props, ...child) {
40
- return createElement("APIInfo", props, ...child);
41
- }
42
- function accordions(...child) {
43
- return createElement("Accordions", {}, ...child);
44
- }
45
- function accordion(props, ...child) {
46
- return createElement("Accordion", props, ...child);
47
- }
48
- function tabs(props, ...child) {
49
- return createElement("Tabs", props, ...child);
50
- }
51
- function tab(props, ...child) {
52
- return createElement("Tab", props, ...child);
53
- }
54
- function property({ required = false, deprecated = false, ...props }, ...child) {
55
- return createElement(
56
- "Property",
57
- { required, deprecated, ...props },
58
- ...child
59
- );
60
- }
61
-
62
- // src/samples/index.ts
63
- import { sample } from "openapi-sampler";
64
-
65
- // src/utils.ts
66
- function noRef(v) {
67
- return v;
68
- }
69
- function getPreferredMedia(body) {
70
- if (Object.keys(body).length === 0)
71
- return void 0;
72
- if ("application/json" in body)
73
- return body["application/json"];
74
- return Object.values(body)[0];
75
- }
76
- function getValue(value) {
77
- return typeof value === "string" ? value : JSON.stringify(value, null, 2);
78
- }
79
-
80
- // src/samples/index.ts
81
- function createEndpoint(path, method, baseUrl = "https://example.com") {
82
- const params = [];
83
- const responses = {};
84
- for (const param of method.parameters) {
85
- const schema = noRef(
86
- param.schema ?? getPreferredMedia(param.content ?? {})?.schema
87
- );
88
- if (!schema)
89
- continue;
90
- params.push({
91
- name: param.name,
92
- in: param.in,
93
- schema
94
- });
95
- }
96
- const body = noRef(method.requestBody)?.content ?? {};
97
- const bodySchema = noRef(getPreferredMedia(body)?.schema);
98
- for (const [code, value] of Object.entries(method.responses)) {
99
- const mediaTypes = noRef(value).content ?? {};
100
- const responseSchema = noRef(getPreferredMedia(mediaTypes)?.schema);
101
- if (!responseSchema)
102
- continue;
103
- responses[code] = {
104
- schema: responseSchema
105
- };
106
- }
107
- let pathWithParameters = path;
108
- const queryParams = new URLSearchParams();
109
- for (const param of params) {
110
- const value = generateSample(method.method, param.schema);
111
- if (param.in === "query")
112
- queryParams.append(param.name, getValue(value));
113
- if (param.in === "path")
114
- pathWithParameters = pathWithParameters.replace(
115
- `{${param.name}}`,
116
- getValue(value)
117
- );
118
- }
119
- return {
120
- url: new URL(pathWithParameters, baseUrl).toString(),
121
- body: bodySchema ? generateSample(method.method, bodySchema) : void 0,
122
- responses,
123
- method: method.method,
124
- parameters: params
125
- };
126
- }
127
- function generateSample(method, schema) {
128
- return sample(schema, {
129
- skipReadOnly: method !== "GET",
130
- skipWriteOnly: method === "GET"
131
- });
132
- }
133
-
134
- // src/samples/response.ts
135
- function getExampleResponse(endpoint, code) {
136
- if (code in endpoint.responses) {
137
- const value = generateSample(
138
- endpoint.method,
139
- endpoint.responses[code].schema
140
- );
141
- return JSON.stringify(value, null, 2);
142
- }
143
- }
144
-
145
- // src/samples/typescript.ts
146
- import { compile } from "json-schema-to-typescript";
147
- async function getTypescript(endpoint, code) {
148
- if (code in endpoint.responses) {
149
- return compile(endpoint.responses[code].schema, "Response", {
150
- bannerComment: "",
151
- additionalProperties: false,
152
- format: true,
153
- enableConstEnums: false
154
- });
155
- }
156
- }
157
-
158
- // src/samples/curl.ts
159
- function getSampleRequest(endpoint) {
160
- const s = [];
161
- s.push(`curl -X ${endpoint.method} "${endpoint.url}"`);
162
- for (const param of endpoint.parameters) {
163
- if (param.in === "header") {
164
- const value = generateSample(endpoint.method, param.schema);
165
- const header = `${param.name}: ${getValue2(value)}`;
166
- s.push(`-H "${header}"`);
167
- }
168
- if (param.in === "formData") {
169
- console.log("Request example for form data is not supported");
170
- }
171
- }
172
- if (endpoint.body)
173
- s.push(`-d '${getValue2(endpoint.body)}'`);
174
- return s.join(" \\\n ");
175
- }
176
- function getValue2(value) {
177
- return typeof value === "string" ? value : JSON.stringify(value, null, 2);
178
- }
179
-
180
- // src/render/schema.ts
181
- var keys = {
182
- example: "Example",
183
- default: "Default",
184
- minimum: "Minimum",
185
- maximum: "Maximum",
186
- minLength: "Minimum length",
187
- maxLength: "Maximum length",
188
- pattern: "Pattern",
189
- format: "Format"
190
- };
191
- function isObject(schema) {
192
- return schema.type === "object" || schema.properties !== void 0;
193
- }
194
- function schemaElement(name, schema, { parseObject, ...ctx }) {
195
- if (schema.readOnly && !ctx.readOnly)
196
- return "";
197
- if (schema.writeOnly && !ctx.writeOnly)
198
- return "";
199
- const child = [];
200
- function field(key, value) {
201
- child.push(span(`${key}: \`${value}\``));
202
- }
203
- if (isObject(schema) && parseObject) {
204
- const { additionalProperties, properties } = schema;
205
- if (additionalProperties) {
206
- if (additionalProperties === true) {
207
- child.push(
208
- property({
209
- name: "[key: string]",
210
- type: "any"
211
- })
212
- );
213
- } else {
214
- child.push(
215
- schemaElement("[key: string]", noRef(additionalProperties), {
216
- ...ctx,
217
- required: false,
218
- parseObject: false
219
- })
220
- );
221
- }
222
- }
223
- Object.entries(properties ?? {}).forEach(([key, value]) => {
224
- child.push(
225
- schemaElement(key, noRef(value), {
226
- ...ctx,
227
- required: schema.required?.includes(key) ?? false,
228
- parseObject: false
229
- })
230
- );
231
- });
232
- return child.join("\n\n");
233
- }
234
- child.push(p(schema.description));
235
- for (const [key, value] of Object.entries(keys)) {
236
- if (key in schema) {
237
- field(value, JSON.stringify(schema[key]));
238
- }
239
- }
240
- if (schema.enum) {
241
- field(
242
- "Value in",
243
- schema.enum.map((value) => JSON.stringify(value)).join(" | ")
244
- );
245
- }
246
- const resolved = resolveObjectType(schema);
247
- if (resolved && !parseObject) {
248
- child.push(
249
- accordions(
250
- accordion(
251
- { title: "Object Type" },
252
- schemaElement(name, resolved, {
253
- ...ctx,
254
- parseObject: true,
255
- required: false
256
- })
257
- )
258
- )
259
- );
260
- }
261
- return property(
262
- {
263
- name,
264
- type: getSchemaType(schema),
265
- required: ctx.required,
266
- deprecated: schema.deprecated
267
- },
268
- ...child
269
- );
270
- }
271
- function resolveObjectType(schema) {
272
- if (isObject(schema))
273
- return schema;
274
- if (schema.type === "array") {
275
- return resolveObjectType(noRef(schema.items));
276
- }
277
- }
278
- function getSchemaType(schema) {
279
- if (schema.nullable) {
280
- return `${getSchemaType({ ...schema, nullable: false })} | null`;
281
- }
282
- if (schema.type === "array")
283
- return `array of ${getSchemaType(noRef(schema.items))}`;
284
- if (schema.oneOf)
285
- return schema.oneOf.map((one) => getSchemaType(noRef(one))).join(" | ");
286
- if (schema.allOf)
287
- return schema.allOf.map((one) => getSchemaType(noRef(one))).join(" & ");
288
- if (schema.anyOf)
289
- return `Any properties in ${schema.anyOf.map((one) => getSchemaType(noRef(one))).join(", ")}`;
290
- if (schema.type)
291
- return schema.type;
292
- if (isObject(schema))
293
- return "object";
294
- throw new Error(`Cannot detect object type: ${JSON.stringify(schema)}`);
295
- }
296
-
297
- // src/render/operation.ts
298
- async function renderOperation(path, method, baseUrl) {
299
- const info = [];
300
- const example = [];
301
- info.push(`## ${method.summary ?? method.operationId}`);
302
- if (method.description)
303
- info.push(p(method.description));
304
- const body = noRef(method.requestBody);
305
- if (body) {
306
- const bodySchema = getPreferredMedia(body.content)?.schema;
307
- if (!bodySchema)
308
- throw new Error();
309
- info.push(
310
- `### Request Body${!body.required ? " (Optional)" : ""}`,
311
- p(body.description),
312
- schemaElement("body", noRef(bodySchema), {
313
- parseObject: true,
314
- readOnly: method.method === "GET",
315
- writeOnly: method.method !== "GET",
316
- required: body.required ?? false
317
- })
318
- );
319
- }
320
- const parameterGroups = /* @__PURE__ */ new Map();
321
- const endpoint = createEndpoint(path, method, baseUrl);
322
- for (const param of method.parameters) {
323
- const schema = noRef(
324
- param.schema ?? getPreferredMedia(param.content ?? {})?.schema
325
- );
326
- if (!schema)
327
- continue;
328
- const content = schemaElement(
329
- param.name,
330
- {
331
- ...schema,
332
- description: param.description ?? schema.description,
333
- deprecated: param.deprecated || schema.deprecated
334
- },
335
- {
336
- parseObject: false,
337
- readOnly: method.method === "GET",
338
- writeOnly: method.method !== "GET",
339
- required: param.required ?? false
340
- }
341
- );
342
- const groupName = {
343
- path: "Path Parameters",
344
- query: "Query Parameters",
345
- header: "Header Parameters",
346
- cookie: "Cookie Parameters"
347
- }[param.in] ?? "Other Parameters";
348
- const group = parameterGroups.get(groupName) ?? [];
349
- group.push(content);
350
- parameterGroups.set(groupName, group);
351
- }
352
- for (const [group, parameters] of Array.from(parameterGroups.entries())) {
353
- info.push(`### ${group}`, ...parameters);
354
- }
355
- info.push(getResponseTable(method));
356
- example.push(
357
- codeblock({ language: "bash", title: "curl" }, getSampleRequest(endpoint))
358
- );
359
- example.push(await getResponseTabs(endpoint, method));
360
- return api(
361
- apiInfo({ method: method.method, route: path }, ...info),
362
- apiExample(...example)
363
- );
364
- }
365
- function getResponseTable(operation) {
366
- const table = [];
367
- table.push(`| Status code | Description |`);
368
- table.push(`| ----------- | ----------- |`);
369
- Object.entries(operation.responses).forEach(([code, value]) => {
370
- table.push(`| \`${code}\` | ${noRef(value).description} |`);
371
- });
372
- return table.join("\n");
373
- }
374
- async function getResponseTabs(endpoint, operation) {
375
- const items = [];
376
- const child = [];
377
- for (const [code, _] of Object.entries(operation.responses)) {
378
- const example = getExampleResponse(endpoint, code);
379
- const ts = await getTypescript(endpoint, code);
380
- const description = code in endpoint.responses ? endpoint.responses[code].schema.description : void 0;
381
- if (example && ts) {
382
- items.push(code);
383
- child.push(
384
- tab(
385
- { value: code },
386
- p(description),
387
- codeblock({ language: "json", title: "Example Response" }, example),
388
- accordions(
389
- accordion(
390
- { title: "Typescript Definition" },
391
- codeblock({ language: "ts" }, ts)
392
- )
393
- )
394
- )
395
- );
396
- }
397
- }
398
- if (items.length === 0)
399
- return "";
400
- return tabs(
401
- {
402
- items
403
- },
404
- ...child
405
- );
406
- }
407
-
408
- // src/generate.ts
409
- async function dereference(pathOrDocument) {
410
- return await Parser.dereference(pathOrDocument);
411
- }
412
- async function generate(pathOrDocument, options = {}) {
413
- const document = await dereference(pathOrDocument);
414
- const tag = options.tag ? document.tags?.find((item) => item.name === options.tag) : void 0;
415
- const routes = Object.entries(document.paths).map(
416
- ([key, value]) => {
417
- if (!value)
418
- throw new Error("Invalid schema");
419
- const methodKeys = ["get", "post", "patch", "delete", "head"];
420
- const methods = [];
421
- for (const methodKey of methodKeys) {
422
- const operation = value[methodKey];
423
- if (!operation)
424
- continue;
425
- if (tag && !operation.tags?.includes(tag.name))
426
- continue;
427
- methods.push(buildOperation(methodKey, operation));
428
- }
429
- return {
430
- ...value,
431
- path: key,
432
- methods
433
- };
434
- }
435
- );
436
- const serverUrl = document.servers?.[0].url;
437
- const s = [];
438
- for (const route of routes) {
439
- for (const method of route.methods) {
440
- s.push(await renderOperation(route.path, method, serverUrl));
441
- }
442
- }
443
- return render(
444
- tag?.name ?? document.info.title,
445
- tag?.description ?? document.info.description,
446
- root(...s),
447
- options
448
- );
449
- }
450
- async function generateTags(pathOrDocument, options = {}) {
451
- const document = await dereference(pathOrDocument);
452
- const results = document.tags?.map(async (tag) => {
453
- return {
454
- tag: tag.name,
455
- content: await generate(document, {
456
- tag: tag.name,
457
- ...options
458
- })
459
- };
460
- });
461
- return Promise.all(results ?? []);
462
- }
463
- function render(title, description, content, {
464
- render: fn,
465
- componentsImportPath = "fumadocs-ui/components/api"
466
- }) {
467
- const result = fn?.(title, description, content) ?? {};
468
- const rendered = {
469
- frontmatter: result.frontmatter ?? [
470
- "---",
471
- title && `title: ${title}`,
472
- description && `description: ${description}`,
473
- "---"
474
- ].filter(Boolean).join("\n"),
475
- imports: result.imports ?? [
476
- `import { Root, API, APIInfo, APIExample, Property } from '${componentsImportPath}'`,
477
- `import { Tabs, Tab } from 'fumadocs-ui/components/tabs'`,
478
- `import { Accordion, Accordions } from 'fumadocs-ui/components/accordion';`
479
- ],
480
- content: result.content ?? content
481
- };
482
- return [rendered.frontmatter, rendered.imports.join("\n"), rendered.content].filter(Boolean).join("\n\n");
483
- }
484
- function buildOperation(method, operation) {
485
- return {
486
- ...operation,
487
- parameters: operation.parameters ?? [],
488
- method: method.toUpperCase()
489
- };
490
- }
491
-
492
- // src/generate-file.ts
493
- import { mkdirSync, writeFileSync } from "node:fs";
494
- import { dirname, join, parse } from "node:path";
495
- import fg from "fast-glob";
496
- async function generateFiles({
497
- input,
498
- output,
499
- name: nameFn,
500
- per = "file",
501
- render: render2,
502
- cwd = process.cwd()
503
- }) {
504
- const outputDir = join(cwd, output);
505
- const options = {
506
- render: render2
507
- };
508
- const resolvedInputs = await fg.glob(input, { absolute: true, cwd });
509
- await Promise.all(
510
- resolvedInputs.map(async (path) => {
511
- let filename = parse(path).name;
512
- filename = nameFn?.("file", filename) ?? filename;
513
- if (per === "file") {
514
- const outPath = join(outputDir, `${filename}.mdx`);
515
- const result = await generate(path, options);
516
- write(outPath, result);
517
- console.log(`Generated: ${outPath}`);
518
- } else {
519
- const results = await generateTags(path, options);
520
- results.forEach((result) => {
521
- let tagName = result.tag;
522
- tagName = nameFn?.("tag", tagName) ?? tagName.toLowerCase().replace(/\s+/g, "-");
523
- const outPath = join(outputDir, filename, `${tagName}.mdx`);
524
- write(outPath, result.content);
525
- console.log(`Generated: ${outPath}`);
526
- });
527
- }
528
- })
529
- );
530
- }
531
- function write(path, content) {
532
- mkdirSync(dirname(path), { recursive: true });
533
- writeFileSync(path, content);
534
- }
535
-
536
- export {
537
- dereference,
538
- generate,
539
- generateTags,
540
- generateFiles
541
- };