docusaurus-plugin-openapi-docs 0.0.0-697 → 0.0.0-699

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/README.md CHANGED
@@ -159,6 +159,7 @@ The `docusaurus-plugin-openapi-docs` plugin can be configured with the following
159
159
  | `baseUrl` | `string` | `null` | _Optional:_ Version base URL used when generating version selector dropdown menu. |
160
160
  | `versions` | `object` | `null` | _Optional:_ Set of options for versioning configuration. See below for a list of supported options. |
161
161
  | `markdownGenerators` | `object` | `null` | _Optional:_ Customize MDX content with a set of options for specifying markdown generator functions. See below for a list of supported options. |
162
+ | `showSchemas` | `boolean` | `null` | _Optional:_ If set to `true`, generates schema pages and adds them to the sidebar. |
162
163
 
163
164
  `sidebarOptions` can be configured with the following options:
164
165
 
package/lib/index.js CHANGED
@@ -77,7 +77,7 @@ function pluginOpenAPIDocs(context, options) {
77
77
  let docRouteBasePath = docData ? docData.routeBasePath : undefined;
78
78
  let docPath = docData ? (docData.path ? docData.path : "docs") : undefined;
79
79
  async function generateApiDocs(options, pluginId) {
80
- var _a, _b, _c;
80
+ var _a, _b, _c, _d;
81
81
  let { specPath, outputDir, template, markdownGenerators, downloadUrl, sidebarOptions, } = options;
82
82
  // Remove trailing slash before proceeding
83
83
  outputDir = outputDir.replace(/\/$/, "");
@@ -195,20 +195,35 @@ import {useCurrentSidebarCategory} from '@docusaurus/theme-common';
195
195
  <DocCardList items={useCurrentSidebarCategory().items}/>
196
196
  \`\`\`
197
197
  `;
198
+ const schemaMdTemplate = `---
199
+ id: {{{id}}}
200
+ title: "{{{title}}}"
201
+ description: "{{{frontMatter.description}}}"
202
+ sidebar_label: "{{{title}}}"
203
+ hide_title: true
204
+ schema: true
205
+ custom_edit_url: null
206
+ ---
207
+
208
+ {{{markdown}}}
209
+ `;
198
210
  const apiPageGenerator = (_a = markdownGenerators === null || markdownGenerators === void 0 ? void 0 : markdownGenerators.createApiPageMD) !== null && _a !== void 0 ? _a : markdown_1.createApiPageMD;
199
211
  const infoPageGenerator = (_b = markdownGenerators === null || markdownGenerators === void 0 ? void 0 : markdownGenerators.createInfoPageMD) !== null && _b !== void 0 ? _b : markdown_1.createInfoPageMD;
200
212
  const tagPageGenerator = (_c = markdownGenerators === null || markdownGenerators === void 0 ? void 0 : markdownGenerators.createTagPageMD) !== null && _c !== void 0 ? _c : markdown_1.createTagPageMD;
213
+ const schemaPageGenerator = (_d = markdownGenerators === null || markdownGenerators === void 0 ? void 0 : markdownGenerators.createSchemaPageMD) !== null && _d !== void 0 ? _d : markdown_1.createSchemaPageMD;
214
+ const pageGeneratorByType = {
215
+ api: apiPageGenerator,
216
+ info: infoPageGenerator,
217
+ tag: tagPageGenerator,
218
+ schema: schemaPageGenerator,
219
+ };
201
220
  loadedApi.map(async (item) => {
202
221
  if (item.type === "info") {
203
222
  if (downloadUrl && isURL(downloadUrl)) {
204
223
  item.downloadUrl = downloadUrl;
205
224
  }
206
225
  }
207
- const markdown = item.type === "api"
208
- ? apiPageGenerator(item)
209
- : item.type === "info"
210
- ? infoPageGenerator(item)
211
- : tagPageGenerator(item);
226
+ const markdown = pageGeneratorByType[item.type](item);
212
227
  item.markdown = markdown;
213
228
  if (item.type === "api") {
214
229
  // opportunity to compress JSON
@@ -274,6 +289,32 @@ import {useCurrentSidebarCategory} from '@docusaurus/theme-common';
274
289
  }
275
290
  }
276
291
  }
292
+ if (item.type === "schema") {
293
+ if (!fs_1.default.existsSync(`${outputDir}/schemas/${item.id}.schema.mdx`)) {
294
+ if (!fs_1.default.existsSync(`${outputDir}/schemas`)) {
295
+ try {
296
+ fs_1.default.mkdirSync(`${outputDir}/schemas`, { recursive: true });
297
+ console.log(chalk_1.default.green(`Successfully created "${outputDir}/schemas"`));
298
+ }
299
+ catch (err) {
300
+ console.error(chalk_1.default.red(`Failed to create "${outputDir}/schemas"`), chalk_1.default.yellow(err));
301
+ }
302
+ }
303
+ try {
304
+ // kebabCase(arg) returns 0-length string when arg is undefined
305
+ if (item.id.length === 0) {
306
+ throw Error("Schema must have title defined");
307
+ }
308
+ // eslint-disable-next-line testing-library/render-result-naming-convention
309
+ const schemaView = (0, mustache_1.render)(schemaMdTemplate, item);
310
+ fs_1.default.writeFileSync(`${outputDir}/schemas/${item.id}.schema.mdx`, schemaView, "utf8");
311
+ console.log(chalk_1.default.green(`Successfully created "${outputDir}/${item.id}.schema.mdx"`));
312
+ }
313
+ catch (err) {
314
+ console.error(chalk_1.default.red(`Failed to write "${outputDir}/${item.id}.schema.mdx"`), chalk_1.default.yellow(err));
315
+ }
316
+ }
317
+ }
277
318
  return;
278
319
  });
279
320
  return;
@@ -290,6 +331,10 @@ import {useCurrentSidebarCategory} from '@docusaurus/theme-common';
290
331
  cwd: path_1.default.resolve(apiDir),
291
332
  deep: 1,
292
333
  });
334
+ const schemaMdxFiles = await (0, utils_1.Globby)(["*.schema.mdx"], {
335
+ cwd: path_1.default.resolve(apiDir, "schemas"),
336
+ deep: 1,
337
+ });
293
338
  const sidebarFile = await (0, utils_1.Globby)(["sidebar.js"], {
294
339
  cwd: path_1.default.resolve(apiDir),
295
340
  deep: 1,
@@ -302,6 +347,14 @@ import {useCurrentSidebarCategory} from '@docusaurus/theme-common';
302
347
  console.log(chalk_1.default.green(`Cleanup succeeded for "${apiDir}/${mdx}"`));
303
348
  }
304
349
  }));
350
+ schemaMdxFiles.map((mdx) => fs_1.default.unlink(`${apiDir}/schemas/${mdx}`, (err) => {
351
+ if (err) {
352
+ console.error(chalk_1.default.red(`Cleanup failed for "${apiDir}/schemas/${mdx}"`), chalk_1.default.yellow(err));
353
+ }
354
+ else {
355
+ console.log(chalk_1.default.green(`Cleanup succeeded for "${apiDir}/schemas/${mdx}"`));
356
+ }
357
+ }));
305
358
  sidebarFile.map((sidebar) => fs_1.default.unlink(`${apiDir}/${sidebar}`, (err) => {
306
359
  if (err) {
307
360
  console.error(chalk_1.default.red(`Cleanup failed for "${apiDir}/${sidebar}"`), chalk_1.default.yellow(err));
@@ -1,4 +1,5 @@
1
- import { ApiPageMetadata, InfoPageMetadata, TagPageMetadata } from "../types";
1
+ import { ApiPageMetadata, InfoPageMetadata, SchemaPageMetadata, TagPageMetadata } from "../types";
2
2
  export declare function createApiPageMD({ title, api: { deprecated, "x-deprecated-description": deprecatedDescription, description, method, path, extensions, parameters, requestBody, responses, callbacks, }, infoPath, frontMatter, }: ApiPageMetadata): string;
3
3
  export declare function createInfoPageMD({ info: { title, version, description, contact, license, termsOfService, logo, darkLogo, }, securitySchemes, downloadUrl, }: InfoPageMetadata): string;
4
4
  export declare function createTagPageMD({ tag: { description } }: TagPageMetadata): string;
5
+ export declare function createSchemaPageMD({ schema }: SchemaPageMetadata): string;
@@ -6,7 +6,7 @@
6
6
  * LICENSE file in the root directory of this source tree.
7
7
  * ========================================================================== */
8
8
  Object.defineProperty(exports, "__esModule", { value: true });
9
- exports.createTagPageMD = exports.createInfoPageMD = exports.createApiPageMD = void 0;
9
+ exports.createSchemaPageMD = exports.createTagPageMD = exports.createInfoPageMD = exports.createApiPageMD = void 0;
10
10
  const createAuthentication_1 = require("./createAuthentication");
11
11
  const createAuthorization_1 = require("./createAuthorization");
12
12
  const createCallbacks_1 = require("./createCallbacks");
@@ -21,6 +21,7 @@ const createMethodEndpoint_1 = require("./createMethodEndpoint");
21
21
  const createParamsDetails_1 = require("./createParamsDetails");
22
22
  const createRequestBodyDetails_1 = require("./createRequestBodyDetails");
23
23
  const createRequestHeader_1 = require("./createRequestHeader");
24
+ const createSchema_1 = require("./createSchema");
24
25
  const createStatusCodes_1 = require("./createStatusCodes");
25
26
  const createTermsOfService_1 = require("./createTermsOfService");
26
27
  const createVendorExtensions_1 = require("./createVendorExtensions");
@@ -84,3 +85,18 @@ function createTagPageMD({ tag: { description } }) {
84
85
  return (0, utils_1.render)([(0, createDescription_1.createDescription)(description)]);
85
86
  }
86
87
  exports.createTagPageMD = createTagPageMD;
88
+ function createSchemaPageMD({ schema }) {
89
+ const { title = "", description } = schema;
90
+ return (0, utils_1.render)([
91
+ `import DiscriminatorTabs from "@theme/DiscriminatorTabs";\n`,
92
+ `import SchemaItem from "@theme/SchemaItem";\n`,
93
+ `import SchemaTabs from "@theme/SchemaTabs";\n`,
94
+ `import TabItem from "@theme/TabItem";\n\n`,
95
+ (0, createHeading_1.createHeading)(title.replace(utils_1.lessThan, "&lt;").replace(utils_1.greaterThan, "&gt;")),
96
+ (0, createDescription_1.createDescription)(description),
97
+ (0, utils_1.create)("ul", {
98
+ children: (0, createSchema_1.createNodes)(schema, "response"),
99
+ }),
100
+ ]);
101
+ }
102
+ exports.createSchemaPageMD = createSchemaPageMD;
@@ -61,7 +61,7 @@ async function createPostmanCollection(openapiData) {
61
61
  return await jsonToCollection(data);
62
62
  }
63
63
  function createItems(openapiData, options, sidebarOptions) {
64
- var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p, _q, _r, _s, _t, _u, _v, _w, _x, _y, _z, _0;
64
+ var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p, _q, _r, _s, _t, _u, _v, _w, _x, _y, _z, _0, _1, _2, _3, _4;
65
65
  // TODO: Find a better way to handle this
66
66
  let items = [];
67
67
  const infoIdSpaces = openapiData.info.title.replace(" ", "-").toLowerCase();
@@ -324,9 +324,42 @@ function createItems(openapiData, options, sidebarOptions) {
324
324
  items.push(apiPage);
325
325
  }
326
326
  }
327
+ if ((options === null || options === void 0 ? void 0 : options.showSchemas) === true) {
328
+ // Gather schemas
329
+ for (let [schema, schemaObject] of Object.entries((_1 = (_0 = openapiData === null || openapiData === void 0 ? void 0 : openapiData.components) === null || _0 === void 0 ? void 0 : _0.schemas) !== null && _1 !== void 0 ? _1 : {})) {
330
+ const baseIdSpaces = (_3 = (_2 = schemaObject === null || schemaObject === void 0 ? void 0 : schemaObject.title) === null || _2 === void 0 ? void 0 : _2.replace(" ", "-").toLowerCase()) !== null && _3 !== void 0 ? _3 : "";
331
+ const baseId = (0, kebabCase_1.default)(baseIdSpaces);
332
+ const schemaDescription = schemaObject.description;
333
+ let splitDescription;
334
+ if (schemaDescription) {
335
+ splitDescription = schemaDescription.match(/[^\r\n]+/g);
336
+ }
337
+ const schemaPage = {
338
+ type: "schema",
339
+ id: baseId,
340
+ infoId: infoId !== null && infoId !== void 0 ? infoId : "",
341
+ unversionedId: baseId,
342
+ title: schemaObject.title
343
+ ? schemaObject.title.replace(/((?:^|[^\\])(?:\\{2})*)"/g, "$1'")
344
+ : schema,
345
+ description: schemaObject.description
346
+ ? schemaObject.description.replace(/((?:^|[^\\])(?:\\{2})*)"/g, "$1'")
347
+ : "",
348
+ frontMatter: {
349
+ description: splitDescription
350
+ ? splitDescription[0]
351
+ .replace(/((?:^|[^\\])(?:\\{2})*)"/g, "$1'")
352
+ .replace(/\s+$/, "")
353
+ : "",
354
+ },
355
+ schema: schemaObject,
356
+ };
357
+ items.push(schemaPage);
358
+ }
359
+ }
327
360
  if ((sidebarOptions === null || sidebarOptions === void 0 ? void 0 : sidebarOptions.categoryLinkSource) === "tag") {
328
361
  // Get global tags
329
- const tags = (_0 = openapiData.tags) !== null && _0 !== void 0 ? _0 : [];
362
+ const tags = (_4 = openapiData.tags) !== null && _4 !== void 0 ? _4 : [];
330
363
  // Get operation tags
331
364
  const apiItems = items.filter((item) => {
332
365
  return item.type === "api";
@@ -375,7 +408,9 @@ function bindCollectionToApiItems(items, postmanCollection) {
375
408
  .getPath({ unresolved: true }) // unresolved returns "/:variableName" instead of "/<type>"
376
409
  .replace(/(?<![a-z0-9-_]+):([a-z0-9-_]+)/gi, "{$1}"); // replace "/:variableName" with "/{variableName}"
377
410
  const apiItem = items.find((item) => {
378
- if (item.type === "info" || item.type === "tag") {
411
+ if (item.type === "info" ||
412
+ item.type === "tag" ||
413
+ item.type === "schema") {
379
414
  return false;
380
415
  }
381
416
  return item.api.path === path && item.api.method === method;
package/lib/options.js CHANGED
@@ -34,6 +34,7 @@ exports.OptionsSchema = utils_validation_1.Joi.object({
34
34
  showExtensions: utils_validation_1.Joi.boolean(),
35
35
  sidebarOptions: sidebarOptions,
36
36
  markdownGenerators: markdownGenerators,
37
+ showSchemas: utils_validation_1.Joi.boolean(),
37
38
  version: utils_validation_1.Joi.string().when("versions", {
38
39
  is: utils_validation_1.Joi.exist(),
39
40
  then: utils_validation_1.Joi.required(),
@@ -20,6 +20,9 @@ function isApiItem(item) {
20
20
  function isInfoItem(item) {
21
21
  return item.type === "info";
22
22
  }
23
+ function isSchemaItem(item) {
24
+ return item.type === "schema";
25
+ }
23
26
  function groupByTags(items, sidebarOptions, options, tags, docPath) {
24
27
  let { outputDir, label } = options;
25
28
  // Remove trailing slash before proceeding
@@ -27,6 +30,7 @@ function groupByTags(items, sidebarOptions, options, tags, docPath) {
27
30
  const { sidebarCollapsed, sidebarCollapsible, customProps, categoryLinkSource, } = sidebarOptions;
28
31
  const apiItems = items.filter(isApiItem);
29
32
  const infoItems = items.filter(isInfoItem);
33
+ const schemaItems = items.filter(isSchemaItem);
30
34
  const intros = infoItems.map((item) => {
31
35
  return {
32
36
  id: item.id,
@@ -56,16 +60,21 @@ function groupByTags(items, sidebarOptions, options, tags, docPath) {
56
60
  var _a, _b;
57
61
  const sidebar_label = item.frontMatter.sidebar_label;
58
62
  const title = item.title;
59
- const id = item.id;
63
+ const id = item.type === "schema" ? `schemas/${item.id}` : item.id;
64
+ const className = item.type === "api"
65
+ ? (0, clsx_1.default)({
66
+ "menu__list-item--deprecated": item.api.deprecated,
67
+ "api-method": !!item.api.method,
68
+ }, item.api.method)
69
+ : (0, clsx_1.default)({
70
+ "menu__list-item--deprecated": item.schema.deprecated,
71
+ });
60
72
  return {
61
73
  type: "doc",
62
- id: basePath === "" || undefined ? `${item.id}` : `${basePath}/${item.id}`,
74
+ id: basePath === "" || undefined ? `${id}` : `${basePath}/${id}`,
63
75
  label: (_b = (_a = sidebar_label) !== null && _a !== void 0 ? _a : title) !== null && _b !== void 0 ? _b : id,
64
76
  customProps: customProps,
65
- className: (0, clsx_1.default)({
66
- "menu__list-item--deprecated": item.api.deprecated,
67
- "api-method": !!item.api.method,
68
- }, item.api.method),
77
+ className: className ? className : undefined,
69
78
  };
70
79
  }
71
80
  let rootIntroDoc = undefined;
@@ -147,12 +156,24 @@ function groupByTags(items, sidebarOptions, options, tags, docPath) {
147
156
  },
148
157
  ];
149
158
  }
159
+ let schemas = [];
160
+ if (schemaItems.length > 0) {
161
+ schemas = [
162
+ {
163
+ type: "category",
164
+ label: "Schemas",
165
+ collapsible: sidebarCollapsible,
166
+ collapsed: sidebarCollapsed,
167
+ items: schemaItems.map(createDocItem),
168
+ },
169
+ ];
170
+ }
150
171
  // Shift root intro doc to top of sidebar
151
172
  // TODO: Add input validation for categoryLinkSource options
152
173
  if (rootIntroDoc && categoryLinkSource !== "info") {
153
174
  tagged.unshift(rootIntroDoc);
154
175
  }
155
- return [...tagged, ...untagged];
176
+ return [...tagged, ...untagged, ...schemas];
156
177
  }
157
178
  function generateSidebarSlice(sidebarOptions, options, api, tags, docPath) {
158
179
  let sidebarSlice = [];
package/lib/types.d.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  import type Request from "@paloaltonetworks/postman-collection";
2
- import { InfoObject, OperationObject, SecuritySchemeObject, TagObject } from "./openapi/types";
2
+ import { InfoObject, OperationObject, SchemaObject, SecuritySchemeObject, TagObject } from "./openapi/types";
3
3
  export type { PropSidebarItemCategory, SidebarItemLink, PropSidebar, PropSidebarItem, } from "@docusaurus/plugin-content-docs-types";
4
4
  export interface PluginOptions {
5
5
  id?: string;
@@ -24,11 +24,13 @@ export interface APIOptions {
24
24
  };
25
25
  proxy?: string;
26
26
  markdownGenerators?: MarkdownGenerator;
27
+ showSchemas?: boolean;
27
28
  }
28
29
  export interface MarkdownGenerator {
29
30
  createApiPageMD?: (pageData: ApiPageMetadata) => string;
30
31
  createInfoPageMD?: (pageData: InfoPageMetadata) => string;
31
32
  createTagPageMD?: (pageData: TagPageMetadata) => string;
33
+ createSchemaPageMD?: (pageData: SchemaPageMetadata) => string;
32
34
  }
33
35
  export interface SidebarOptions {
34
36
  groupPathsBy?: string;
@@ -48,7 +50,7 @@ export interface APIVersionOptions {
48
50
  export interface LoadedContent {
49
51
  loadedApi: ApiMetadata[];
50
52
  }
51
- export declare type ApiMetadata = ApiPageMetadata | InfoPageMetadata | TagPageMetadata;
53
+ export declare type ApiMetadata = ApiPageMetadata | InfoPageMetadata | TagPageMetadata | SchemaPageMetadata;
52
54
  export interface ApiMetadataBase {
53
55
  sidebar?: string;
54
56
  previous?: ApiNavLink;
@@ -100,6 +102,11 @@ export interface TagPageMetadata extends ApiMetadataBase {
100
102
  tag: TagObject;
101
103
  markdown?: string;
102
104
  }
105
+ export interface SchemaPageMetadata extends ApiMetadataBase {
106
+ type: "schema";
107
+ schema: SchemaObject;
108
+ markdown?: string;
109
+ }
103
110
  export declare type ApiInfo = InfoObject;
104
111
  export interface ApiNavLink {
105
112
  title: string;
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "docusaurus-plugin-openapi-docs",
3
3
  "description": "OpenAPI plugin for Docusaurus.",
4
- "version": "0.0.0-697",
4
+ "version": "0.0.0-699",
5
5
  "license": "MIT",
6
6
  "keywords": [
7
7
  "openapi",
@@ -60,5 +60,5 @@
60
60
  "engines": {
61
61
  "node": ">=14"
62
62
  },
63
- "gitHead": "a6bbe786231f3938c6333379a036c614afb27029"
63
+ "gitHead": "ef318267f54c34bedf50f0b309c9bb30fd665b53"
64
64
  }
package/src/index.ts CHANGED
@@ -14,11 +14,25 @@ import { Globby, posixPath } from "@docusaurus/utils";
14
14
  import chalk from "chalk";
15
15
  import { render } from "mustache";
16
16
 
17
- import { createApiPageMD, createInfoPageMD, createTagPageMD } from "./markdown";
17
+ import {
18
+ createApiPageMD,
19
+ createInfoPageMD,
20
+ createSchemaPageMD,
21
+ createTagPageMD,
22
+ } from "./markdown";
18
23
  import { readOpenapiFiles, processOpenapiFiles } from "./openapi";
19
24
  import { OptionsSchema } from "./options";
20
25
  import generateSidebarSlice from "./sidebars";
21
- import type { PluginOptions, LoadedContent, APIOptions } from "./types";
26
+ import type {
27
+ PluginOptions,
28
+ LoadedContent,
29
+ APIOptions,
30
+ ApiMetadata,
31
+ ApiPageMetadata,
32
+ InfoPageMetadata,
33
+ TagPageMetadata,
34
+ SchemaPageMetadata,
35
+ } from "./types";
22
36
 
23
37
  export function isURL(str: string): boolean {
24
38
  return /^(https?:)\/\//m.test(str);
@@ -244,12 +258,43 @@ import {useCurrentSidebarCategory} from '@docusaurus/theme-common';
244
258
  \`\`\`
245
259
  `;
246
260
 
261
+ const schemaMdTemplate = `---
262
+ id: {{{id}}}
263
+ title: "{{{title}}}"
264
+ description: "{{{frontMatter.description}}}"
265
+ sidebar_label: "{{{title}}}"
266
+ hide_title: true
267
+ schema: true
268
+ custom_edit_url: null
269
+ ---
270
+
271
+ {{{markdown}}}
272
+ `;
273
+
247
274
  const apiPageGenerator =
248
275
  markdownGenerators?.createApiPageMD ?? createApiPageMD;
249
276
  const infoPageGenerator =
250
277
  markdownGenerators?.createInfoPageMD ?? createInfoPageMD;
251
278
  const tagPageGenerator =
252
279
  markdownGenerators?.createTagPageMD ?? createTagPageMD;
280
+ const schemaPageGenerator =
281
+ markdownGenerators?.createSchemaPageMD ?? createSchemaPageMD;
282
+
283
+ const pageGeneratorByType: {
284
+ [key in ApiMetadata["type"]]: (
285
+ pageData: {
286
+ api: ApiPageMetadata;
287
+ info: InfoPageMetadata;
288
+ tag: TagPageMetadata;
289
+ schema: SchemaPageMetadata;
290
+ }[key]
291
+ ) => string;
292
+ } = {
293
+ api: apiPageGenerator,
294
+ info: infoPageGenerator,
295
+ tag: tagPageGenerator,
296
+ schema: schemaPageGenerator,
297
+ };
253
298
 
254
299
  loadedApi.map(async (item) => {
255
300
  if (item.type === "info") {
@@ -257,12 +302,7 @@ import {useCurrentSidebarCategory} from '@docusaurus/theme-common';
257
302
  item.downloadUrl = downloadUrl;
258
303
  }
259
304
  }
260
- const markdown =
261
- item.type === "api"
262
- ? apiPageGenerator(item)
263
- : item.type === "info"
264
- ? infoPageGenerator(item)
265
- : tagPageGenerator(item);
305
+ const markdown = pageGeneratorByType[item.type](item as any);
266
306
  item.markdown = markdown;
267
307
  if (item.type === "api") {
268
308
  // opportunity to compress JSON
@@ -363,6 +403,49 @@ import {useCurrentSidebarCategory} from '@docusaurus/theme-common';
363
403
  }
364
404
  }
365
405
  }
406
+
407
+ if (item.type === "schema") {
408
+ if (!fs.existsSync(`${outputDir}/schemas/${item.id}.schema.mdx`)) {
409
+ if (!fs.existsSync(`${outputDir}/schemas`)) {
410
+ try {
411
+ fs.mkdirSync(`${outputDir}/schemas`, { recursive: true });
412
+ console.log(
413
+ chalk.green(`Successfully created "${outputDir}/schemas"`)
414
+ );
415
+ } catch (err) {
416
+ console.error(
417
+ chalk.red(`Failed to create "${outputDir}/schemas"`),
418
+ chalk.yellow(err)
419
+ );
420
+ }
421
+ }
422
+ try {
423
+ // kebabCase(arg) returns 0-length string when arg is undefined
424
+ if (item.id.length === 0) {
425
+ throw Error("Schema must have title defined");
426
+ }
427
+ // eslint-disable-next-line testing-library/render-result-naming-convention
428
+ const schemaView = render(schemaMdTemplate, item);
429
+ fs.writeFileSync(
430
+ `${outputDir}/schemas/${item.id}.schema.mdx`,
431
+ schemaView,
432
+ "utf8"
433
+ );
434
+ console.log(
435
+ chalk.green(
436
+ `Successfully created "${outputDir}/${item.id}.schema.mdx"`
437
+ )
438
+ );
439
+ } catch (err) {
440
+ console.error(
441
+ chalk.red(
442
+ `Failed to write "${outputDir}/${item.id}.schema.mdx"`
443
+ ),
444
+ chalk.yellow(err)
445
+ );
446
+ }
447
+ }
448
+ }
366
449
  return;
367
450
  });
368
451
 
@@ -380,6 +463,10 @@ import {useCurrentSidebarCategory} from '@docusaurus/theme-common';
380
463
  cwd: path.resolve(apiDir),
381
464
  deep: 1,
382
465
  });
466
+ const schemaMdxFiles = await Globby(["*.schema.mdx"], {
467
+ cwd: path.resolve(apiDir, "schemas"),
468
+ deep: 1,
469
+ });
383
470
  const sidebarFile = await Globby(["sidebar.js"], {
384
471
  cwd: path.resolve(apiDir),
385
472
  deep: 1,
@@ -397,6 +484,21 @@ import {useCurrentSidebarCategory} from '@docusaurus/theme-common';
397
484
  })
398
485
  );
399
486
 
487
+ schemaMdxFiles.map((mdx) =>
488
+ fs.unlink(`${apiDir}/schemas/${mdx}`, (err) => {
489
+ if (err) {
490
+ console.error(
491
+ chalk.red(`Cleanup failed for "${apiDir}/schemas/${mdx}"`),
492
+ chalk.yellow(err)
493
+ );
494
+ } else {
495
+ console.log(
496
+ chalk.green(`Cleanup succeeded for "${apiDir}/schemas/${mdx}"`)
497
+ );
498
+ }
499
+ })
500
+ );
501
+
400
502
  sidebarFile.map((sidebar) =>
401
503
  fs.unlink(`${apiDir}/${sidebar}`, (err) => {
402
504
  if (err) {
@@ -11,7 +11,12 @@ import {
11
11
  MediaTypeObject,
12
12
  SecuritySchemeObject,
13
13
  } from "../openapi/types";
14
- import { ApiPageMetadata, InfoPageMetadata, TagPageMetadata } from "../types";
14
+ import {
15
+ ApiPageMetadata,
16
+ InfoPageMetadata,
17
+ SchemaPageMetadata,
18
+ TagPageMetadata,
19
+ } from "../types";
15
20
  import { createAuthentication } from "./createAuthentication";
16
21
  import { createAuthorization } from "./createAuthorization";
17
22
  import { createCallbacks } from "./createCallbacks";
@@ -26,11 +31,12 @@ import { createMethodEndpoint } from "./createMethodEndpoint";
26
31
  import { createParamsDetails } from "./createParamsDetails";
27
32
  import { createRequestBodyDetails } from "./createRequestBodyDetails";
28
33
  import { createRequestHeader } from "./createRequestHeader";
34
+ import { createNodes } from "./createSchema";
29
35
  import { createStatusCodes } from "./createStatusCodes";
30
36
  import { createTermsOfService } from "./createTermsOfService";
31
37
  import { createVendorExtensions } from "./createVendorExtensions";
32
38
  import { createVersionBadge } from "./createVersionBadge";
33
- import { greaterThan, lessThan, render } from "./utils";
39
+ import { create, greaterThan, lessThan, render } from "./utils";
34
40
 
35
41
  interface RequestBodyProps {
36
42
  title: string;
@@ -130,3 +136,18 @@ export function createInfoPageMD({
130
136
  export function createTagPageMD({ tag: { description } }: TagPageMetadata) {
131
137
  return render([createDescription(description)]);
132
138
  }
139
+
140
+ export function createSchemaPageMD({ schema }: SchemaPageMetadata) {
141
+ const { title = "", description } = schema;
142
+ return render([
143
+ `import DiscriminatorTabs from "@theme/DiscriminatorTabs";\n`,
144
+ `import SchemaItem from "@theme/SchemaItem";\n`,
145
+ `import SchemaTabs from "@theme/SchemaTabs";\n`,
146
+ `import TabItem from "@theme/TabItem";\n\n`,
147
+ createHeading(title.replace(lessThan, "&lt;").replace(greaterThan, "&gt;")),
148
+ createDescription(description),
149
+ create("ul", {
150
+ children: createNodes(schema, "response"),
151
+ }),
152
+ ]);
153
+ }
@@ -24,6 +24,7 @@ import {
24
24
  APIOptions,
25
25
  ApiPageMetadata,
26
26
  InfoPageMetadata,
27
+ SchemaPageMetadata,
27
28
  SidebarOptions,
28
29
  TagPageMetadata,
29
30
  } from "../types";
@@ -409,6 +410,46 @@ function createItems(
409
410
  }
410
411
  }
411
412
 
413
+ if (options?.showSchemas === true) {
414
+ // Gather schemas
415
+ for (let [schema, schemaObject] of Object.entries(
416
+ openapiData?.components?.schemas ?? {}
417
+ )) {
418
+ const baseIdSpaces =
419
+ schemaObject?.title?.replace(" ", "-").toLowerCase() ?? "";
420
+ const baseId = kebabCase(baseIdSpaces);
421
+
422
+ const schemaDescription = schemaObject.description;
423
+ let splitDescription: any;
424
+ if (schemaDescription) {
425
+ splitDescription = schemaDescription.match(/[^\r\n]+/g);
426
+ }
427
+
428
+ const schemaPage: PartialPage<SchemaPageMetadata> = {
429
+ type: "schema",
430
+ id: baseId,
431
+ infoId: infoId ?? "",
432
+ unversionedId: baseId,
433
+ title: schemaObject.title
434
+ ? schemaObject.title.replace(/((?:^|[^\\])(?:\\{2})*)"/g, "$1'")
435
+ : schema,
436
+ description: schemaObject.description
437
+ ? schemaObject.description.replace(/((?:^|[^\\])(?:\\{2})*)"/g, "$1'")
438
+ : "",
439
+ frontMatter: {
440
+ description: splitDescription
441
+ ? splitDescription[0]
442
+ .replace(/((?:^|[^\\])(?:\\{2})*)"/g, "$1'")
443
+ .replace(/\s+$/, "")
444
+ : "",
445
+ },
446
+ schema: schemaObject,
447
+ };
448
+
449
+ items.push(schemaPage);
450
+ }
451
+ }
452
+
412
453
  if (sidebarOptions?.categoryLinkSource === "tag") {
413
454
  // Get global tags
414
455
  const tags: TagObject[] = openapiData.tags ?? [];
@@ -471,7 +512,11 @@ function bindCollectionToApiItems(
471
512
  .getPath({ unresolved: true }) // unresolved returns "/:variableName" instead of "/<type>"
472
513
  .replace(/(?<![a-z0-9-_]+):([a-z0-9-_]+)/gi, "{$1}"); // replace "/:variableName" with "/{variableName}"
473
514
  const apiItem = items.find((item) => {
474
- if (item.type === "info" || item.type === "tag") {
515
+ if (
516
+ item.type === "info" ||
517
+ item.type === "tag" ||
518
+ item.type === "schema"
519
+ ) {
475
520
  return false;
476
521
  }
477
522
  return item.api.path === path && item.api.method === method;
package/src/options.ts CHANGED
@@ -37,6 +37,7 @@ export const OptionsSchema = Joi.object({
37
37
  showExtensions: Joi.boolean(),
38
38
  sidebarOptions: sidebarOptions,
39
39
  markdownGenerators: markdownGenerators,
40
+ showSchemas: Joi.boolean(),
40
41
  version: Joi.string().when("versions", {
41
42
  is: Joi.exist(),
42
43
  then: Joi.required(),
@@ -24,6 +24,7 @@ import type {
24
24
  APIOptions,
25
25
  ApiPageMetadata,
26
26
  ApiMetadata,
27
+ SchemaPageMetadata,
27
28
  } from "../types";
28
29
 
29
30
  function isApiItem(item: ApiMetadata): item is ApiMetadata {
@@ -34,6 +35,10 @@ function isInfoItem(item: ApiMetadata): item is ApiMetadata {
34
35
  return item.type === "info";
35
36
  }
36
37
 
38
+ function isSchemaItem(item: ApiMetadata): item is ApiMetadata {
39
+ return item.type === "schema";
40
+ }
41
+
37
42
  function groupByTags(
38
43
  items: ApiPageMetadata[],
39
44
  sidebarOptions: SidebarOptions,
@@ -55,6 +60,7 @@ function groupByTags(
55
60
 
56
61
  const apiItems = items.filter(isApiItem);
57
62
  const infoItems = items.filter(isInfoItem);
63
+ const schemaItems = items.filter(isSchemaItem);
58
64
  const intros = infoItems.map((item: any) => {
59
65
  return {
60
66
  id: item.id,
@@ -85,23 +91,30 @@ function groupByTags(
85
91
  const basePath = docPath
86
92
  ? outputDir.split(docPath!)[1].replace(/^\/+/g, "")
87
93
  : outputDir.slice(outputDir.indexOf("/", 1)).replace(/^\/+/g, "");
88
- function createDocItem(item: ApiPageMetadata): SidebarItemDoc {
94
+ function createDocItem(
95
+ item: ApiPageMetadata | SchemaPageMetadata
96
+ ): SidebarItemDoc {
89
97
  const sidebar_label = item.frontMatter.sidebar_label;
90
98
  const title = item.title;
91
- const id = item.id;
99
+ const id = item.type === "schema" ? `schemas/${item.id}` : item.id;
100
+ const className =
101
+ item.type === "api"
102
+ ? clsx(
103
+ {
104
+ "menu__list-item--deprecated": item.api.deprecated,
105
+ "api-method": !!item.api.method,
106
+ },
107
+ item.api.method
108
+ )
109
+ : clsx({
110
+ "menu__list-item--deprecated": item.schema.deprecated,
111
+ });
92
112
  return {
93
113
  type: "doc" as const,
94
- id:
95
- basePath === "" || undefined ? `${item.id}` : `${basePath}/${item.id}`,
114
+ id: basePath === "" || undefined ? `${id}` : `${basePath}/${id}`,
96
115
  label: (sidebar_label as string) ?? title ?? id,
97
116
  customProps: customProps,
98
- className: clsx(
99
- {
100
- "menu__list-item--deprecated": item.api.deprecated,
101
- "api-method": !!item.api.method,
102
- },
103
- item.api.method
104
- ),
117
+ className: className ? className : undefined,
105
118
  };
106
119
  }
107
120
 
@@ -201,13 +214,26 @@ function groupByTags(
201
214
  ];
202
215
  }
203
216
 
217
+ let schemas: SidebarItemCategory[] = [];
218
+ if (schemaItems.length > 0) {
219
+ schemas = [
220
+ {
221
+ type: "category" as const,
222
+ label: "Schemas",
223
+ collapsible: sidebarCollapsible!,
224
+ collapsed: sidebarCollapsed!,
225
+ items: schemaItems.map(createDocItem),
226
+ },
227
+ ];
228
+ }
229
+
204
230
  // Shift root intro doc to top of sidebar
205
231
  // TODO: Add input validation for categoryLinkSource options
206
232
  if (rootIntroDoc && categoryLinkSource !== "info") {
207
233
  tagged.unshift(rootIntroDoc as any);
208
234
  }
209
235
 
210
- return [...tagged, ...untagged];
236
+ return [...tagged, ...untagged, ...schemas];
211
237
  }
212
238
 
213
239
  export default function generateSidebarSlice(
package/src/types.ts CHANGED
@@ -10,6 +10,7 @@ import type Request from "@paloaltonetworks/postman-collection";
10
10
  import {
11
11
  InfoObject,
12
12
  OperationObject,
13
+ SchemaObject,
13
14
  SecuritySchemeObject,
14
15
  TagObject,
15
16
  } from "./openapi/types";
@@ -44,12 +45,14 @@ export interface APIOptions {
44
45
  };
45
46
  proxy?: string;
46
47
  markdownGenerators?: MarkdownGenerator;
48
+ showSchemas?: boolean;
47
49
  }
48
50
 
49
51
  export interface MarkdownGenerator {
50
52
  createApiPageMD?: (pageData: ApiPageMetadata) => string;
51
53
  createInfoPageMD?: (pageData: InfoPageMetadata) => string;
52
54
  createTagPageMD?: (pageData: TagPageMetadata) => string;
55
+ createSchemaPageMD?: (pageData: SchemaPageMetadata) => string;
53
56
  }
54
57
 
55
58
  export interface SidebarOptions {
@@ -72,7 +75,11 @@ export interface LoadedContent {
72
75
  // loadedDocs: DocPageMetadata[]; TODO: cleanup
73
76
  }
74
77
 
75
- export type ApiMetadata = ApiPageMetadata | InfoPageMetadata | TagPageMetadata;
78
+ export type ApiMetadata =
79
+ | ApiPageMetadata
80
+ | InfoPageMetadata
81
+ | TagPageMetadata
82
+ | SchemaPageMetadata;
76
83
 
77
84
  export interface ApiMetadataBase {
78
85
  sidebar?: string;
@@ -131,6 +138,12 @@ export interface TagPageMetadata extends ApiMetadataBase {
131
138
  markdown?: string;
132
139
  }
133
140
 
141
+ export interface SchemaPageMetadata extends ApiMetadataBase {
142
+ type: "schema";
143
+ schema: SchemaObject;
144
+ markdown?: string;
145
+ }
146
+
134
147
  export type ApiInfo = InfoObject;
135
148
 
136
149
  export interface ApiNavLink {