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.
@@ -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/render/page.ts
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", {}, ...child),
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/render/page.ts
101
- function renderPage(title, description, content, options) {
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/components/api"
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
- ${Root(content)}`;
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}: \`${value}\``));
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
- if (additionalProperties === true) {
295
- child.push(
296
- renderer.Property(
297
- {
298
- name: "[key: string]",
299
- type: "any"
300
- },
301
- []
302
- )
303
- );
304
- } else {
305
- child.push(
306
- schemaElement("[key: string]", noRef(additionalProperties), {
307
- ...ctx,
308
- required: false,
309
- parseObject: false
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
- schemaElement(key, noRef(value), {
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
- const resolved = resolveObjectType(schema);
338
- if (resolved && !ctx.parseObject) {
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
- schemaElement(name, resolved, {
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 resolveObjectType(schema) {
360
- if (isObject(schema)) return schema;
361
- if (schema.type === "array") {
362
- return resolveObjectType(noRef(schema.items));
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
- if (!schema.type) return "null";
368
- return `${getSchemaType({ ...schema, nullable: false })} | null`;
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 of ${getSchemaType(noRef(schema.items))}`;
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.anyOf)
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
- throw new Error(`Cannot detect object type: ${JSON.stringify(schema)}`);
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
- const title = method.summary ?? method.operationId;
391
- if (title && !noTitle) {
392
- info.push(heading(level, title));
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 [name, scopes] of Object.entries(requirement)) {
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 renderPage(
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 = renderPage(
849
+ const content = generateDocument(
624
850
  method.summary ?? method.method,
625
851
  method.description,
626
- [await renderOperation(route.path, method, ctx, true)],
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: renderPage(tag, info?.description, child, options)
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 "node:fs/promises";
672
- import { dirname, join, parse } from "node:path";
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,