fumadocs-openapi 9.4.0 → 9.5.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.
@@ -1,67 +1,23 @@
1
- import { type GenerateOptions, type GeneratePageOutput, type GenerateTagOutput } from './generate.js';
1
+ import { type PagesToTextOptions } from './utils/pages/to-text.js';
2
2
  import { type ProcessedDocument } from './utils/process-document.js';
3
3
  import type { OpenAPIServer } from './server/index.js';
4
- interface GenerateFileOutput {
5
- /**
6
- * The original schema file path/url from `input`
7
- */
8
- pathOrUrl: string;
9
- content: string;
10
- }
4
+ import { type SchemaToPagesOptions } from './utils/schema-to-pages.js';
11
5
  export interface OutputFile {
12
6
  path: string;
13
7
  content: string;
14
8
  }
15
- interface OperationConfig extends BaseConfig {
16
- /**
17
- * Generate a page for each API endpoint/operation (default).
18
- */
19
- per?: 'operation';
20
- /**
21
- * Group output using folders (Only works on `operation` mode)
22
- * - tag: `{tag}/{file}`
23
- * - route: `{endpoint}/{method}` (it will ignore the `name` option)
24
- * - none: `{file}` (default)
25
- *
26
- * @defaultValue 'none'
27
- */
28
- groupBy?: 'tag' | 'route' | 'none';
29
- /**
30
- * Specify name for output file
31
- */
32
- name?: ((output: GeneratePageOutput, document: ProcessedDocument['dereferenced']) => string) | BaseName;
33
- }
34
- interface TagConfig extends BaseConfig {
35
- /**
36
- * Generate a page for each tag.
37
- */
38
- per: 'tag';
39
- /**
40
- * Specify name for output file
41
- */
42
- name?: ((output: GenerateTagOutput, document: ProcessedDocument['dereferenced']) => string) | BaseName;
43
- }
44
- interface FileConfig extends BaseConfig {
45
- /**
46
- * Generate a page for each schema file.
47
- */
48
- per: 'file';
9
+ interface IndexConfig {
10
+ items: IndexItem[] | ((ctx: HookContext) => IndexItem[]);
49
11
  /**
50
- * Specify name for output file
12
+ * Generate URLs for cards
51
13
  */
52
- name?: ((output: GenerateFileOutput, document: ProcessedDocument['dereferenced']) => string) | BaseName;
53
- }
54
- export type Config = FileConfig | TagConfig | OperationConfig;
55
- interface BaseName {
56
- /**
57
- * The version of algorithm used to generate file paths.
58
- *
59
- * v1: Fumadocs OpenAPI v8
60
- * v2: Fumadocs OpenAPI v9
61
- *
62
- * @defaultValue v2
63
- */
64
- algorithm?: 'v2' | 'v1';
14
+ url: ((filePath: string) => string) | {
15
+ baseUrl: string;
16
+ /**
17
+ * Base content directory
18
+ */
19
+ contentDir: string;
20
+ };
65
21
  }
66
22
  interface IndexItem {
67
23
  path: string;
@@ -74,12 +30,8 @@ interface IndexItem {
74
30
  */
75
31
  only?: (string | OutputFile)[];
76
32
  }
77
- interface HookContext {
78
- files: OutputFile[];
79
- readonly generated: Record<string, OutputFile[]>;
80
- readonly documents: Record<string, ProcessedDocument>;
81
- }
82
- interface BaseConfig extends GenerateOptions {
33
+ interface GenerateFilesConfig extends PagesToTextOptions {
34
+ cwd?: string;
83
35
  /**
84
36
  * Schema files, or the OpenAPI server object
85
37
  */
@@ -88,34 +40,22 @@ interface BaseConfig extends GenerateOptions {
88
40
  * Output directory
89
41
  */
90
42
  output: string;
91
- /**
92
- * Custom function to convert names into file names.
93
- *
94
- * By default, it only escapes whitespaces and upper case (English) characters
95
- */
96
- slugify?: (name: string) => string;
97
43
  /**
98
44
  * Generate index files with cards linking to generated pages.
99
45
  */
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
- };
46
+ index?: IndexConfig;
113
47
  /**
114
48
  * Can add/change/remove output files before writing to file system
115
49
  **/
116
50
  beforeWrite?: (this: HookContext, files: OutputFile[]) => void | Promise<void>;
117
51
  }
52
+ export type Config = SchemaToPagesOptions & GenerateFilesConfig;
53
+ interface HookContext {
54
+ files: OutputFile[];
55
+ readonly generated: Record<string, OutputFile[]>;
56
+ readonly documents: Record<string, ProcessedDocument>;
57
+ }
118
58
  export declare function generateFiles(options: Config): Promise<void>;
119
- export declare function generateFilesOnly(options: Config): Promise<OutputFile[]>;
59
+ export declare function generateFilesOnly(options: SchemaToPagesOptions & Omit<GenerateFilesConfig, 'output'>): Promise<OutputFile[]>;
120
60
  export {};
121
61
  //# sourceMappingURL=generate-file.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"generate-file.d.ts","sourceRoot":"","sources":["../src/generate-file.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"generate-file.d.ts","sourceRoot":"","sources":["../src/generate-file.ts"],"names":[],"mappings":"AAGA,OAAO,EAEL,KAAK,kBAAkB,EAExB,MAAM,uBAAuB,CAAC;AAC/B,OAAO,EAEL,KAAK,iBAAiB,EACvB,MAAM,0BAA0B,CAAC;AAClC,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AAG9C,OAAO,EAGL,KAAK,oBAAoB,EAC1B,MAAM,yBAAyB,CAAC;AAEjC,MAAM,WAAW,UAAU;IACzB,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;CACjB;AAED,UAAU,WAAW;IACnB,KAAK,EAAE,SAAS,EAAE,GAAG,CAAC,CAAC,GAAG,EAAE,WAAW,KAAK,SAAS,EAAE,CAAC,CAAC;IAEzD;;OAEG;IACH,GAAG,EACC,CAAC,CAAC,QAAQ,EAAE,MAAM,KAAK,MAAM,CAAC,GAC9B;QACE,OAAO,EAAE,MAAM,CAAC;QAChB;;WAEG;QACH,UAAU,EAAE,MAAM,CAAC;KACpB,CAAC;CACP;AAED,UAAU,SAAS;IACjB,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,WAAW,CAAC,EAAE,MAAM,CAAC;IAErB;;;;OAIG;IACH,IAAI,CAAC,EAAE,CAAC,MAAM,GAAG,UAAU,CAAC,EAAE,CAAC;CAChC;AAED,UAAU,mBAAoB,SAAQ,kBAAkB;IACtD,GAAG,CAAC,EAAE,MAAM,CAAC;IAEb;;OAEG;IACH,KAAK,EAAE,MAAM,EAAE,GAAG,MAAM,GAAG,aAAa,CAAC;IAEzC;;OAEG;IACH,MAAM,EAAE,MAAM,CAAC;IAEf;;OAEG;IACH,KAAK,CAAC,EAAE,WAAW,CAAC;IAEpB;;QAEI;IACJ,WAAW,CAAC,EAAE,CACZ,IAAI,EAAE,WAAW,EACjB,KAAK,EAAE,UAAU,EAAE,KAChB,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CAC3B;AAED,MAAM,MAAM,MAAM,GAAG,oBAAoB,GAAG,mBAAmB,CAAC;AAEhE,UAAU,WAAW;IACnB,KAAK,EAAE,UAAU,EAAE,CAAC;IACpB,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC,MAAM,EAAE,UAAU,EAAE,CAAC,CAAC;IACjD,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC,MAAM,EAAE,iBAAiB,CAAC,CAAC;CACvD;AAED,wBAAsB,aAAa,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAclE;AAED,wBAAsB,iBAAiB,CACrC,OAAO,EAAE,oBAAoB,GAAG,IAAI,CAAC,mBAAmB,EAAE,QAAQ,CAAC,GAClE,OAAO,CAAC,UAAU,EAAE,CAAC,CAkEvB"}
@@ -1,16 +1,20 @@
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, generateDocument, generatePages, generateTags, } from './generate.js';
4
+ import { generateDocument, toText, } from './utils/pages/to-text.js';
5
5
  import { processDocumentCached, } from './utils/process-document.js';
6
6
  import { createGetUrl, getSlugs } from 'fumadocs-core/source';
7
7
  import matter from 'gray-matter';
8
+ import { isUrl, schemaToPages, } from './utils/schema-to-pages.js';
8
9
  export async function generateFiles(options) {
9
10
  const files = await generateFilesOnly(options);
11
+ const { output, cwd = process.cwd() } = options;
12
+ const baseDir = path.join(cwd, output);
10
13
  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
+ const filePath = path.join(baseDir, file.path);
15
+ await mkdir(path.dirname(filePath), { recursive: true });
16
+ await writeFile(filePath, file.content);
17
+ console.log(`Generated: ${filePath}`);
14
18
  }));
15
19
  }
16
20
  export async function generateFilesOnly(options) {
@@ -49,7 +53,10 @@ export async function generateFilesOnly(options) {
49
53
  throw new Error('No input files found.');
50
54
  }
51
55
  for (const [id, schema] of entries) {
52
- const result = generateFromDocument(id, schema, options);
56
+ const result = schemaToPages(id, schema, options).map((page) => ({
57
+ path: page.path,
58
+ content: toText(page, schema, options),
59
+ }));
53
60
  files.push(...result);
54
61
  generated[id] = result;
55
62
  }
@@ -59,140 +66,17 @@ export async function generateFilesOnly(options) {
59
66
  documents: schemas,
60
67
  };
61
68
  if (options.index) {
62
- writeIndexFiles(context, options);
69
+ writeIndexFiles(context, options.index, options);
63
70
  }
64
71
  await beforeWrite?.call(context, context.files);
65
72
  return context.files;
66
73
  }
67
- function generateFromDocument(schemaId, processed, options) {
68
- const files = [];
69
- const { dereferenced } = processed;
70
- const { output, cwd = process.cwd(), slugify = defaultSlugify } = options;
71
- const outputDir = path.join(cwd, output);
72
- let nameFn;
73
- if (!options.name || typeof options.name !== 'function') {
74
- const algorithm = options.name?.algorithm;
75
- nameFn = (out, doc) => defaultNameFn(schemaId, out, doc, options, algorithm);
76
- }
77
- else {
78
- nameFn = options.name;
79
- }
80
- function getOutputPaths(groupBy = 'none', result) {
81
- if (groupBy === 'route') {
82
- return [
83
- path.join(getOutputPathFromRoute(result.type === 'operation' ? result.item.path : result.item.name), `${result.item.method.toLowerCase()}.mdx`),
84
- ];
85
- }
86
- const file = nameFn(result, dereferenced);
87
- if (groupBy === 'tag') {
88
- let tags = result.type === 'operation'
89
- ? dereferenced.paths[result.item.path][result.item.method].tags
90
- : dereferenced.webhooks[result.item.name][result.item.method].tags;
91
- if (!tags || tags.length === 0) {
92
- console.warn('When `groupBy` is set to `tag`, make sure a `tags` is defined for every operation schema.');
93
- tags = ['unknown'];
94
- }
95
- return tags.map((tag) => path.join(slugify(tag), `${file}.mdx`));
96
- }
97
- return [`${file}.mdx`];
98
- }
99
- if (options.per === 'file') {
100
- const result = generateAll(schemaId, processed, options);
101
- const filename = nameFn({
102
- pathOrUrl: schemaId,
103
- content: result,
104
- }, dereferenced);
105
- files.push({
106
- path: path.join(outputDir, `${filename}.mdx`),
107
- content: result,
108
- });
109
- return files;
110
- }
111
- if (options.per === 'tag') {
112
- const results = generateTags(schemaId, processed, options);
113
- for (const result of results) {
114
- const filename = nameFn(result, dereferenced);
115
- files.push({
116
- path: path.join(outputDir, `${filename}.mdx`),
117
- content: result.content,
118
- });
119
- }
120
- return files;
121
- }
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);
127
- }
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');
137
- }
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;
168
- }
169
- return slugify(result.item.name);
170
- }
171
- function isUrl(input) {
172
- return input.startsWith('https://') || input.startsWith('http://');
173
- }
174
- function getOutputPathFromRoute(path) {
175
- return (path
176
- .toLowerCase()
177
- .replaceAll('.', '-')
178
- .split('/')
179
- .map((v) => {
180
- if (v.startsWith('{') && v.endsWith('}'))
181
- return v.slice(1, -1);
182
- return v;
183
- })
184
- .join('/') ?? '');
185
- }
186
- function writeIndexFiles(context, options) {
187
- const { index, output, cwd = process.cwd() } = options;
188
- if (!index)
189
- return;
190
- const { items, url } = index;
74
+ function writeIndexFiles(context, options, generateOptions) {
75
+ const { items, url } = options;
191
76
  let urlFn;
192
77
  if (typeof url === 'object') {
193
78
  const getUrl = createGetUrl(url.baseUrl);
194
- const contentDir = path.resolve(cwd, url.contentDir);
195
- urlFn = (file) => getUrl(getSlugs(path.relative(contentDir, file)));
79
+ urlFn = (file) => getUrl(getSlugs(path.relative(url.contentDir, file)));
196
80
  }
197
81
  else {
198
82
  urlFn = url;
@@ -231,17 +115,12 @@ function writeIndexFiles(context, options) {
231
115
  return generateDocument({
232
116
  title: index.title ?? 'Overview',
233
117
  description: index.description,
234
- }, content.join('\n'), options);
118
+ }, content.join('\n'), generateOptions);
235
119
  }
236
- const outputDir = path.join(cwd, output);
237
120
  for (const item of typeof items === 'function' ? items(context) : items) {
238
- const outPath = path.join(outputDir, path.extname(item.path).length === 0 ? `${item.path}.mdx` : item.path);
239
121
  context.files.push({
240
- path: outPath,
122
+ path: path.extname(item.path).length === 0 ? `${item.path}.mdx` : item.path,
241
123
  content: fileContent(item),
242
124
  });
243
125
  }
244
126
  }
245
- function defaultSlugify(s) {
246
- return s.replace(/\s+/g, '-').toLowerCase();
247
- }
@@ -299,14 +299,14 @@ function useAuthInputs(securities) {
299
299
  }
300
300
  return result;
301
301
  }, [securities]);
302
- const mapInputs = useEffectEvent((values) => {
302
+ const mapInputs = (values) => {
303
303
  for (const item of inputs) {
304
304
  if (!item.mapOutput)
305
305
  continue;
306
306
  values = manipulateValues(values, item.fieldName, item.mapOutput, true);
307
307
  }
308
308
  return values;
309
- });
309
+ };
310
310
  return { inputs, mapInputs };
311
311
  }
312
312
  function renderCustomField(fieldName, info, field, key) {
@@ -1,7 +1,7 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import Slugger from 'github-slugger';
3
3
  import { Operation } from '../render/operation/index.js';
4
- import { createMethod } from '../server/create-method.js';
4
+ import { createMethod } from '../utils/schema.js';
5
5
  import { createRenders } from '../render/renderer.js';
6
6
  import { processDocumentCached, } from '../utils/process-document.js';
7
7
  import { defaultAdapters } from '../media/adapter.js';
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/render/operation/index.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAY,KAAK,YAAY,EAAkB,MAAM,OAAO,CAAC;AACpE,OAAO,KAAK,EAEV,iBAAiB,EACjB,aAAa,EAEd,MAAM,SAAS,CAAC;AAcjB,OAAO,EAAE,KAAK,eAAe,EAAE,MAAM,oBAAoB,CAAC;AAY1D,MAAM,WAAW,UAAU,CAAC,CAAC,GAAG,OAAO;IACrC,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,CAAC,EAAE,MAAM,CAAC;IACf;;;;;OAKG;IACH,MAAM,CAAC,EAAE,MAAM,GAAG,eAAe,CAAC,CAAC,CAAC,GAAG,KAAK,CAAC;IAE7C;;OAEG;IACH,aAAa,CAAC,EAAE,CAAC,CAAC;CACnB;AASD,wBAAgB,SAAS,CAAC,EACxB,IAAkB,EAClB,IAAI,EACJ,MAAM,EACN,GAAG,EACH,OAAO,EACP,YAAgB,GACjB,EAAE;IACD,IAAI,CAAC,EAAE,SAAS,GAAG,WAAW,CAAC;IAC/B,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,iBAAiB,CAAC;IAC1B,GAAG,EAAE,aAAa,CAAC;IAEnB,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB,GAAG,YAAY,CAiNf"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/render/operation/index.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAY,KAAK,YAAY,EAAkB,MAAM,OAAO,CAAC;AACpE,OAAO,KAAK,EAEV,iBAAiB,EACjB,aAAa,EAEd,MAAM,SAAS,CAAC;AAajB,OAAO,EAAE,KAAK,eAAe,EAAE,MAAM,oBAAoB,CAAC;AAY1D,MAAM,WAAW,UAAU,CAAC,CAAC,GAAG,OAAO;IACrC,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,CAAC,EAAE,MAAM,CAAC;IACf;;;;;OAKG;IACH,MAAM,CAAC,EAAE,MAAM,GAAG,eAAe,CAAC,CAAC,CAAC,GAAG,KAAK,CAAC;IAE7C;;OAEG;IACH,aAAa,CAAC,EAAE,CAAC,CAAC;CACnB;AASD,wBAAgB,SAAS,CAAC,EACxB,IAAkB,EAClB,IAAI,EACJ,MAAM,EACN,GAAG,EACH,OAAO,EACP,YAAgB,GACjB,EAAE;IACD,IAAI,CAAC,EAAE,SAAS,GAAG,WAAW,CAAC;IAC/B,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,iBAAiB,CAAC;IAC1B,GAAG,EAAE,aAAa,CAAC;IAEnB,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB,GAAG,YAAY,CAiNf"}
@@ -1,10 +1,10 @@
1
1
  import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import { Fragment } from 'react';
3
+ import { createMethod } from '../../utils/schema.js';
3
4
  import { idToTitle } from '../../utils/id-to-title.js';
4
5
  import { Markdown } from '../markdown.js';
5
6
  import { heading } from '../heading.js';
6
7
  import { Schema } from '../schema.js';
7
- import { createMethod } from '../../server/create-method.js';
8
8
  import { methodKeys } from '../../build-routes.js';
9
9
  import { APIExample, APIExampleProvider, getAPIExamples, } from '../../render/operation/api-example.js';
10
10
  import { MethodLabel } from '../../ui/components/method-label.js';
@@ -1,9 +1,34 @@
1
1
  import type * as PageTree from 'fumadocs-core/page-tree';
2
- import type { LoaderPlugin, PageFile, PageTreeTransformer } from 'fumadocs-core/source';
2
+ import { LoaderPlugin, MetaData, PageData, PageFile, PageTreeTransformer, Source } from 'fumadocs-core/source';
3
+ import type { OpenAPIServer } from '../server/create.js';
4
+ import type { SchemaToPagesOptions } from '../utils/schema-to-pages.js';
5
+ import { ApiPageProps } from '../render/api-page.js';
6
+ declare module 'fumadocs-core/source' {
7
+ interface PageData {
8
+ /**
9
+ * Added by Fumadocs OpenAPI
10
+ */
11
+ _openapi?: {
12
+ method?: string;
13
+ };
14
+ }
15
+ }
3
16
  /**
4
17
  * Fumadocs Source API integration, pass this to `plugins` array in `loader()`.
5
18
  */
6
19
  export declare function openapiPlugin(): LoaderPlugin;
20
+ interface OpenAPIPageData extends PageData {
21
+ getAPIPageProps: () => ApiPageProps;
22
+ }
23
+ /**
24
+ * Generate virtual pages for Fumadocs Source API
25
+ */
26
+ export declare function openapiSource(from: OpenAPIServer, options?: SchemaToPagesOptions & {
27
+ baseDir?: string;
28
+ }): Promise<Source<{
29
+ metaData: MetaData;
30
+ pageData: OpenAPIPageData;
31
+ }>>;
7
32
  /**
8
33
  * Source API Integration, add this to page tree builder options.
9
34
  *
@@ -14,4 +39,5 @@ export declare const attachFile: (node: PageTree.Item, file: PageFile | undefine
14
39
  * @deprecated use `openapiPlugin()`
15
40
  */
16
41
  export declare function transformerOpenAPI(): PageTreeTransformer;
42
+ export {};
17
43
  //# sourceMappingURL=source-api.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"source-api.d.ts","sourceRoot":"","sources":["../../src/server/source-api.tsx"],"names":[],"mappings":"AACA,OAAO,KAAK,KAAK,QAAQ,MAAM,yBAAyB,CAAC;AACzD,OAAO,KAAK,EACV,YAAY,EACZ,QAAQ,EACR,mBAAmB,EACpB,MAAM,sBAAsB,CAAC;AAE9B;;GAEG;AACH,wBAAgB,aAAa,IAAI,YAAY,CAkC5C;AAED;;;;GAIG;AACH,eAAO,MAAM,UAAU,GACrB,MAAM,QAAQ,CAAC,IAAI,EACnB,MAAM,QAAQ,GAAG,SAAS,KACzB,QAAQ,CAAC,IA4BX,CAAC;AAEF;;GAEG;AACH,wBAAgB,kBAAkB,IAAI,mBAAmB,CAExD"}
1
+ {"version":3,"file":"source-api.d.ts","sourceRoot":"","sources":["../../src/server/source-api.tsx"],"names":[],"mappings":"AACA,OAAO,KAAK,KAAK,QAAQ,MAAM,yBAAyB,CAAC;AACzD,OAAO,EACL,YAAY,EACZ,QAAQ,EACR,QAAQ,EACR,QAAQ,EACR,mBAAmB,EACnB,MAAM,EAEP,MAAM,sBAAsB,CAAC;AAC9B,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAC;AACrD,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,yBAAyB,CAAC;AACpE,OAAO,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC;AAEjD,OAAO,QAAQ,sBAAsB,CAAC;IACpC,UAAiB,QAAQ;QACvB;;WAEG;QACH,QAAQ,CAAC,EAAE;YACT,MAAM,CAAC,EAAE,MAAM,CAAC;SACjB,CAAC;KACH;CACF;AAED;;GAEG;AACH,wBAAgB,aAAa,IAAI,YAAY,CAgC5C;AAED,UAAU,eAAgB,SAAQ,QAAQ;IACxC,eAAe,EAAE,MAAM,YAAY,CAAC;CACrC;AAED;;GAEG;AACH,wBAAsB,aAAa,CACjC,IAAI,EAAE,aAAa,EACnB,OAAO,GAAE,oBAAoB,GAAG;IAC9B,OAAO,CAAC,EAAE,MAAM,CAAC;CACb,GACL,OAAO,CACR,MAAM,CAAC;IACL,QAAQ,EAAE,QAAQ,CAAC;IACnB,QAAQ,EAAE,eAAe,CAAC;CAC3B,CAAC,CACH,CA8BA;AAED;;;;GAIG;AACH,eAAO,MAAM,UAAU,GACrB,MAAM,QAAQ,CAAC,IAAI,EACnB,MAAM,QAAQ,GAAG,SAAS,KACzB,QAAQ,CAAC,IA4BX,CAAC;AAEF;;GAEG;AACH,wBAAgB,kBAAkB,IAAI,mBAAmB,CAExD"}
@@ -5,6 +5,8 @@ import { MethodLabel } from '../ui/components/method-label.js';
5
5
  */
6
6
  export function openapiPlugin() {
7
7
  return {
8
+ name: 'fumadocs:openapi',
9
+ enforce: 'pre',
8
10
  transformPageTree: {
9
11
  file(node, filePath) {
10
12
  if (!filePath)
@@ -15,8 +17,7 @@ export function openapiPlugin() {
15
17
  const data = file.data;
16
18
  let method;
17
19
  if ('_openapi' in data && typeof data._openapi === 'object') {
18
- const meta = data._openapi;
19
- method = meta.method;
20
+ method = data._openapi.method;
20
21
  }
21
22
  if (method) {
22
23
  node.name = (_jsxs(_Fragment, { children: [node.name, ' ', _jsx(MethodLabel, { className: "ms-auto text-xs text-nowrap", children: method })] }));
@@ -26,6 +27,34 @@ export function openapiPlugin() {
26
27
  },
27
28
  };
28
29
  }
30
+ /**
31
+ * Generate virtual pages for Fumadocs Source API
32
+ */
33
+ export async function openapiSource(from, options = {}) {
34
+ const { baseDir = '' } = options;
35
+ const { serverToPages } = await import('../utils/schema-to-pages.js');
36
+ const { toBody } = await import('../utils/pages/to-body.js');
37
+ const files = [];
38
+ const entries = await serverToPages(from, options);
39
+ for (const entry of Object.values(entries).flat()) {
40
+ files.push({
41
+ type: 'page',
42
+ path: `${baseDir}/${entry.path}`,
43
+ data: {
44
+ ...entry.info,
45
+ getAPIPageProps: () => toBody(from, entry),
46
+ _openapi: {
47
+ method: entry.type === 'operation' || entry.type === 'webhook'
48
+ ? entry.item.method
49
+ : undefined,
50
+ },
51
+ },
52
+ });
53
+ }
54
+ return {
55
+ files,
56
+ };
57
+ }
29
58
  /**
30
59
  * Source API Integration, add this to page tree builder options.
31
60
  *
@@ -1 +1 @@
1
- {"version":3,"file":"code-example.d.ts","sourceRoot":"","sources":["../../../src/ui/contexts/code-example.tsx"],"names":[],"mappings":"AACA,OAAO,EAGL,KAAK,SAAS,EAMf,MAAM,OAAO,CAAC;AAGf,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,oBAAoB,CAAC;AACrD,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC;AAetD,OAAO,KAAK,EAAE,cAAc,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAC;AAqBtE,wBAAgB,mBAAmB,CAAC,EAClC,KAAK,EACL,QAAQ,EACR,UAAU,EACV,QAAQ,GACT,EAAE;IACD,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE;QACR,GAAG,EAAE,MAAM,CAAC;QACZ,IAAI,EAAE,cAAc,CAAC;QACrB,OAAO,EAAE,WAAW,CAAC;KACtB,EAAE,CAAC;IACJ,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,QAAQ,EAAE,SAAS,CAAC;CACrB,2CA6DA;AAED,wBAAgB,WAAW,CAAC,MAAM,EAAE,UAAU,kDA6C7C;AAED,wBAAgB,mBAAmB,CAAC,EAAE,KAAK,EAAE,EAAE,YAAY,2CAoB1D;AAgBD,wBAAgB,qBAAqB,mBAOpC;AAED,wBAAgB,qBAAqB;oBA/KnB,cAAc,WAAW,WAAW,KAAK,IAAI;EAkL9D"}
1
+ {"version":3,"file":"code-example.d.ts","sourceRoot":"","sources":["../../../src/ui/contexts/code-example.tsx"],"names":[],"mappings":"AACA,OAAO,EAGL,KAAK,SAAS,EAMf,MAAM,OAAO,CAAC;AAGf,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,oBAAoB,CAAC;AACrD,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC;AActD,OAAO,KAAK,EAAE,cAAc,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAC;AAmBtE,wBAAgB,mBAAmB,CAAC,EAClC,KAAK,EACL,QAAQ,EACR,UAAU,EACV,QAAQ,GACT,EAAE;IACD,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE;QACR,GAAG,EAAE,MAAM,CAAC;QACZ,IAAI,EAAE,cAAc,CAAC;QACrB,OAAO,EAAE,WAAW,CAAC;KACtB,EAAE,CAAC;IACJ,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,QAAQ,EAAE,SAAS,CAAC;CACrB,2CA6DA;AAED,wBAAgB,WAAW,CAAC,MAAM,EAAE,UAAU,kDA2C7C;AAED,wBAAgB,mBAAmB,CAAC,EAAE,KAAK,EAAE,EAAE,YAAY,2CAoB1D;AAgBD,wBAAgB,qBAAqB,mBAIpC;AAED,wBAAgB,qBAAqB;oBA1KnB,cAAc,WAAW,WAAW,KAAK,IAAI;EA6K9D"}
@@ -4,61 +4,58 @@ import { createContext, useContext, useEffect, useMemo, useRef, useState, } from
4
4
  import { useApiContext, useServerSelectContext } from '../../ui/contexts/api.js';
5
5
  import { DynamicCodeBlock } from 'fumadocs-ui/components/dynamic-codeblock';
6
6
  import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue, } from '../../ui/components/select.js';
7
- import { useEffectEvent } from 'fumadocs-core/utils/use-effect-event';
8
7
  import { joinURL, resolveRequestData, resolveServerUrl, withBase, } from '../../utils/url.js';
9
8
  const CodeExampleContext = createContext(null);
10
9
  export function CodeExampleProvider({ route, examples, initialKey, children, }) {
11
10
  const [key, setKey] = useState(initialKey ?? examples[0].key);
12
11
  const listeners = useRef([]);
13
- const setData = useEffectEvent((data, encoded) => {
14
- for (const example of examples) {
15
- if (example.key === key) {
16
- // persistent changes
17
- example.data = data;
18
- example.encoded = encoded;
19
- break;
20
- }
21
- }
22
- for (const listener of listeners.current) {
23
- listener(data, encoded);
24
- }
25
- });
26
- const updateKey = useEffectEvent((newKey) => {
27
- const example = examples.find((example) => example.key === newKey);
28
- if (!example)
29
- return;
30
- setKey(newKey);
31
- for (const listener of listeners.current) {
32
- listener(example.data, example.encoded);
33
- }
34
- });
35
- const addListener = useEffectEvent((listener) => {
36
- // initial call to listeners to ensure their data is the latest
37
- // this is necessary to avoid race conditions between `useEffect()`
38
- const example = examples.find((example) => example.key === key);
39
- listener(example.data, example.encoded);
40
- listeners.current.push(listener);
41
- });
42
- const removeListener = useEffectEvent((listener) => {
43
- listeners.current = listeners.current.filter((item) => item !== listener);
44
- });
12
+ const examplesRef = useRef(examples);
13
+ examplesRef.current = examples;
45
14
  return (_jsx(CodeExampleContext, { value: useMemo(() => ({
46
15
  key,
47
16
  route,
48
- setKey: updateKey,
49
- examples,
50
- setData,
51
- removeListener,
52
- addListener,
53
- }), [addListener, examples, key, removeListener, route, setData, updateKey]), children: children }));
17
+ setKey: (newKey) => {
18
+ const example = examplesRef.current.find((example) => example.key === newKey);
19
+ if (!example)
20
+ return;
21
+ setKey(newKey);
22
+ for (const listener of listeners.current) {
23
+ listener(example.data, example.encoded);
24
+ }
25
+ },
26
+ getExample: (key) => {
27
+ return examplesRef.current.find((example) => example.key === key);
28
+ },
29
+ setData: (data, encoded) => {
30
+ for (const example of examplesRef.current) {
31
+ if (example.key === key) {
32
+ // persistent changes
33
+ example.data = data;
34
+ example.encoded = encoded;
35
+ break;
36
+ }
37
+ }
38
+ for (const listener of listeners.current) {
39
+ listener(data, encoded);
40
+ }
41
+ },
42
+ removeListener: (listener) => {
43
+ listeners.current = listeners.current.filter((item) => item !== listener);
44
+ },
45
+ addListener: (listener) => {
46
+ // initial call to listeners to ensure their data is the latest
47
+ // this is necessary to avoid race conditions between `useEffect()`
48
+ const example = examplesRef.current.find((example) => example.key === key);
49
+ listener(example.data, example.encoded);
50
+ listeners.current.push(listener);
51
+ },
52
+ }), [key, route]), children: children }));
54
53
  }
55
54
  export function CodeExample(sample) {
56
55
  const { shikiOptions, mediaAdapters } = useApiContext();
57
- const { examples, key, route, addListener, removeListener } = useContext(CodeExampleContext);
56
+ const { getExample, key, route, addListener, removeListener } = useContext(CodeExampleContext);
58
57
  const { server } = useServerSelectContext();
59
- const [data, setData] = useState(() => {
60
- return examples.find((example) => example.key === key).encoded;
61
- });
58
+ const [data, setData] = useState(() => getExample(key).encoded);
62
59
  useEffect(() => {
63
60
  const listener = (_, encoded) => setData(encoded);
64
61
  addListener(listener);
@@ -91,8 +88,8 @@ function SelectDisplay({ item, ...props }) {
91
88
  return (_jsxs("div", { ...props, children: [_jsx("span", { className: "font-medium text-sm", children: item.title }), _jsx("span", { className: "text-fd-muted-foreground", children: item.description })] }));
92
89
  }
93
90
  export function useRequestInitialData() {
94
- const { examples, key } = useContext(CodeExampleContext);
95
- return useMemo(() => examples.find((example) => example.key === key).data, [examples, key]);
91
+ const { getExample, key } = useContext(CodeExampleContext);
92
+ return getExample(key).data;
96
93
  }
97
94
  export function useRequestDataUpdater() {
98
95
  const { setData } = useContext(CodeExampleContext);
@@ -0,0 +1,5 @@
1
+ import type { OutputEntry } from '../../utils/schema-to-pages.js';
2
+ import type { ApiPageProps } from '../../render/api-page.js';
3
+ import type { OpenAPIServer } from '../../server/index.js';
4
+ export declare function toBody(server: OpenAPIServer, entry: OutputEntry): ApiPageProps;
5
+ //# sourceMappingURL=to-body.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"to-body.d.ts","sourceRoot":"","sources":["../../../src/utils/pages/to-body.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,yBAAyB,CAAC;AAC3D,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC;AACtD,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AAE9C,wBAAgB,MAAM,CACpB,MAAM,EAAE,aAAa,EACrB,KAAK,EAAE,WAAW,GACjB,YAAY,CAoBd"}
@@ -0,0 +1,20 @@
1
+ export function toBody(server, entry) {
2
+ if (entry.type === 'operation')
3
+ return server.getAPIPageProps({
4
+ hasHead: false,
5
+ document: entry.schemaId,
6
+ operations: [entry.item],
7
+ });
8
+ if (entry.type === 'webhook')
9
+ return server.getAPIPageProps({
10
+ hasHead: false,
11
+ document: entry.schemaId,
12
+ webhooks: [entry.item],
13
+ });
14
+ return server.getAPIPageProps({
15
+ hasHead: true,
16
+ document: entry.schemaId,
17
+ operations: entry.operations,
18
+ webhooks: entry.webhooks,
19
+ });
20
+ }