docusaurus-plugin-openapi-docs 0.0.0-356 → 0.0.0-361

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
@@ -80,7 +80,7 @@ Here is an example of properly configuring your `docusaurus.config.js` file for
80
80
  specPath: "examples/petstore.yaml", // Path to designated spec file
81
81
  outputDir: "api/petstore", // Output directory for generated .mdx docs
82
82
  sidebarOptions: {
83
- groupPathsBy: "tags",
83
+ groupPathsBy: "tag",
84
84
  },
85
85
  },
86
86
  burgers: {
@@ -112,7 +112,7 @@ Here is an example of properly configuring your `docusaurus.config.js` file for
112
112
 
113
113
  | Name | Type | Default | Description |
114
114
  | -------------------- | --------- | ------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
115
- | `groupPathsBy` | `string` | `null` | Organize and group sidebar slice by specified option. Note: Currently, `groupPathsBy` only contains support for grouping by "tags". |
115
+ | `groupPathsBy` | `string` | `null` | Organize and group sidebar slice by specified option. Note: Currently, `groupPathsBy` only contains support for grouping by `tag`. |
116
116
  | `categoryLinkSource` | `string` | `null` | Defines what source to use for rendering category link pages when grouping paths by tag. <br/></br>The supported options are as follows: <br/></br> `tag`: Sets the category link config type to `generated-index` and uses the tag description as the link config description. <br/><br/>`info`: Sets the category link config type to `doc` and renders the `info` section as the category link (recommended only for multi/micro-spec scenarios). |
117
117
  | `sidebarCollapsible` | `boolean` | `true` | Whether sidebar categories are collapsible by default. |
118
118
  | `sidebarCollapsed` | `boolean` | `true` | Whether sidebar categories are collapsed by default. |
package/lib/index.js CHANGED
@@ -25,7 +25,7 @@ function pluginOpenAPI(context, options) {
25
25
  const contentPath = path_1.default.resolve(siteDir, specPath);
26
26
  try {
27
27
  const openapiFiles = await (0, openapi_1.readOpenapiFiles)(contentPath, {});
28
- const [loadedApi, tags] = await (0, openapi_1.processOpenapiFiles)(openapiFiles);
28
+ const [loadedApi, tags] = await (0, openapi_1.processOpenapiFiles)(openapiFiles, sidebarOptions);
29
29
  if (!fs_1.default.existsSync(outputDir)) {
30
30
  try {
31
31
  fs_1.default.mkdirSync(outputDir, { recursive: true });
@@ -96,17 +96,40 @@ hide_title: true
96
96
  import DocCardList from '@theme/DocCardList';
97
97
  import {useCurrentSidebarCategory} from '@docusaurus/theme-common';
98
98
 
99
+ <DocCardList items={useCurrentSidebarCategory().items}/>
100
+ \`\`\`
101
+ `;
102
+ const tagMdTemplate = template
103
+ ? fs_1.default.readFileSync(template).toString()
104
+ : `---
105
+ id: {{{id}}}
106
+ title: {{{description}}}
107
+ description: {{{description}}}
108
+ ---
109
+
110
+ {{{markdown}}}
111
+
112
+ \`\`\`mdx-code-block
113
+ import DocCardList from '@theme/DocCardList';
114
+ import {useCurrentSidebarCategory} from '@docusaurus/theme-common';
115
+
99
116
  <DocCardList items={useCurrentSidebarCategory().items}/>
100
117
  \`\`\`
101
118
  `;
102
119
  loadedApi.map(async (item) => {
103
- const markdown = item.type === "api" ? (0, markdown_1.createApiPageMD)(item) : (0, markdown_1.createInfoPageMD)(item);
120
+ const markdown = item.type === "api"
121
+ ? (0, markdown_1.createApiPageMD)(item)
122
+ : item.type === "info"
123
+ ? (0, markdown_1.createInfoPageMD)(item)
124
+ : (0, markdown_1.createTagPageMD)(item);
104
125
  item.markdown = markdown;
105
126
  if (item.type === "api") {
106
127
  item.json = JSON.stringify(item.api);
107
128
  }
108
129
  const view = (0, mustache_1.render)(mdTemplate, item);
109
130
  const utils = (0, mustache_1.render)(infoMdTemplate, item);
131
+ // eslint-disable-next-line testing-library/render-result-naming-convention
132
+ const tagUtils = (0, mustache_1.render)(tagMdTemplate, item);
110
133
  if (item.type === "api") {
111
134
  if (!fs_1.default.existsSync(`${outputDir}/${item.id}.api.mdx`)) {
112
135
  try {
@@ -132,6 +155,17 @@ import {useCurrentSidebarCategory} from '@docusaurus/theme-common';
132
155
  }
133
156
  }
134
157
  }
158
+ if (item.type === "tag") {
159
+ if (!fs_1.default.existsSync(`${outputDir}/${item.id}.tag.mdx`)) {
160
+ try {
161
+ fs_1.default.writeFileSync(`${outputDir}/${item.id}.tag.mdx`, tagUtils, "utf8");
162
+ console.log(chalk_1.default.green(`Successfully created "${outputDir}/${item.id}.tag.mdx"`));
163
+ }
164
+ catch (err) {
165
+ console.error(chalk_1.default.red(`Failed to write "${outputDir}/${item.id}.tag.mdx"`), chalk_1.default.yellow(err));
166
+ }
167
+ }
168
+ }
135
169
  return;
136
170
  });
137
171
  return;
@@ -144,7 +178,7 @@ import {useCurrentSidebarCategory} from '@docusaurus/theme-common';
144
178
  async function cleanApiDocs(options) {
145
179
  const { outputDir } = options;
146
180
  const apiDir = path_1.default.join(siteDir, outputDir);
147
- const apiMdxFiles = await (0, utils_1.Globby)(["*.api.mdx", "*.info.mdx"], {
181
+ const apiMdxFiles = await (0, utils_1.Globby)(["*.api.mdx", "*.info.mdx", "*.tag.mdx"], {
148
182
  cwd: path_1.default.resolve(apiDir),
149
183
  });
150
184
  const sidebarFile = await (0, utils_1.Globby)(["sidebar.js"], {
@@ -1,3 +1,4 @@
1
- import { ApiPageMetadata, InfoPageMetadata } from "../types";
1
+ import { ApiPageMetadata, InfoPageMetadata, TagPageMetadata } from "../types";
2
2
  export declare function createApiPageMD({ title, api: { deprecated, "x-deprecated-description": deprecatedDescription, description, parameters, requestBody, responses, }, }: ApiPageMetadata): string;
3
3
  export declare function createInfoPageMD({ info: { title, version, description, contact, license, termsOfService }, }: InfoPageMetadata): string;
4
+ export declare function createTagPageMD({ tag: { description } }: TagPageMetadata): 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.createInfoPageMD = exports.createApiPageMD = void 0;
9
+ exports.createTagPageMD = exports.createInfoPageMD = exports.createApiPageMD = void 0;
10
10
  const lodash_1 = require("lodash");
11
11
  const createContactInfo_1 = require("./createContactInfo");
12
12
  const createDeprecationNotice_1 = require("./createDeprecationNotice");
@@ -47,3 +47,7 @@ function createInfoPageMD({ info: { title, version, description, contact, licens
47
47
  ]);
48
48
  }
49
49
  exports.createInfoPageMD = createInfoPageMD;
50
+ function createTagPageMD({ tag: { description } }) {
51
+ return (0, utils_1.render)([(0, createDescription_1.createDescription)(description)]);
52
+ }
53
+ exports.createTagPageMD = createTagPageMD;
@@ -5,8 +5,12 @@
5
5
  * This source code is licensed under the MIT license found in the
6
6
  * LICENSE file in the root directory of this source tree.
7
7
  * ========================================================================== */
8
+ var __importDefault = (this && this.__importDefault) || function (mod) {
9
+ return (mod && mod.__esModule) ? mod : { "default": mod };
10
+ };
8
11
  Object.defineProperty(exports, "__esModule", { value: true });
9
12
  exports.sampleFromSchema = void 0;
13
+ const chalk_1 = __importDefault(require("chalk"));
10
14
  const primitives = {
11
15
  string: {
12
16
  default: () => "string",
@@ -31,64 +35,70 @@ const primitives = {
31
35
  array: {},
32
36
  };
33
37
  const sampleFromSchema = (schema = {}) => {
34
- let { type, example, allOf, properties, items } = schema;
35
- if (example !== undefined) {
36
- return example;
37
- }
38
- if (allOf) {
39
- // TODO: We are just assuming it will always be an object for now
40
- let obj = {
41
- type: "object",
42
- properties: {},
43
- required: [], // NOTE: We shouldn't need to worry about required
44
- };
45
- for (let item of allOf) {
46
- if (item.properties) {
47
- obj.properties = {
48
- ...obj.properties,
49
- ...item.properties,
50
- };
51
- }
52
- }
53
- return (0, exports.sampleFromSchema)(obj);
54
- }
55
- if (!type) {
56
- if (properties) {
57
- type = "object";
38
+ try {
39
+ let { type, example, allOf, properties, items } = schema;
40
+ if (example !== undefined) {
41
+ return example;
58
42
  }
59
- else if (items) {
60
- type = "array";
43
+ if (allOf) {
44
+ // TODO: We are just assuming it will always be an object for now
45
+ let obj = {
46
+ type: "object",
47
+ properties: {},
48
+ required: [], // NOTE: We shouldn't need to worry about required
49
+ };
50
+ for (let item of allOf) {
51
+ if (item.properties) {
52
+ obj.properties = {
53
+ ...obj.properties,
54
+ ...item.properties,
55
+ };
56
+ }
57
+ }
58
+ return (0, exports.sampleFromSchema)(obj);
61
59
  }
62
- else {
63
- return;
60
+ if (!type) {
61
+ if (properties) {
62
+ type = "object";
63
+ }
64
+ else if (items) {
65
+ type = "array";
66
+ }
67
+ else {
68
+ return;
69
+ }
64
70
  }
65
- }
66
- if (type === "object") {
67
- let obj = {};
68
- for (let [name, prop] of Object.entries(properties !== null && properties !== void 0 ? properties : {})) {
69
- if (prop.deprecated) {
70
- continue;
71
+ if (type === "object") {
72
+ let obj = {};
73
+ for (let [name, prop] of Object.entries(properties !== null && properties !== void 0 ? properties : {})) {
74
+ if (prop.deprecated) {
75
+ continue;
76
+ }
77
+ obj[name] = (0, exports.sampleFromSchema)(prop);
71
78
  }
72
- obj[name] = (0, exports.sampleFromSchema)(prop);
79
+ return obj;
73
80
  }
74
- return obj;
75
- }
76
- if (type === "array") {
77
- if (Array.isArray(items === null || items === void 0 ? void 0 : items.anyOf)) {
78
- return items === null || items === void 0 ? void 0 : items.anyOf.map((item) => (0, exports.sampleFromSchema)(item));
81
+ if (type === "array") {
82
+ if (Array.isArray(items === null || items === void 0 ? void 0 : items.anyOf)) {
83
+ return items === null || items === void 0 ? void 0 : items.anyOf.map((item) => (0, exports.sampleFromSchema)(item));
84
+ }
85
+ if (Array.isArray(items === null || items === void 0 ? void 0 : items.oneOf)) {
86
+ return items === null || items === void 0 ? void 0 : items.oneOf.map((item) => (0, exports.sampleFromSchema)(item));
87
+ }
88
+ return [(0, exports.sampleFromSchema)(items)];
79
89
  }
80
- if (Array.isArray(items === null || items === void 0 ? void 0 : items.oneOf)) {
81
- return items === null || items === void 0 ? void 0 : items.oneOf.map((item) => (0, exports.sampleFromSchema)(item));
90
+ if (schema.enum) {
91
+ if (schema.default) {
92
+ return schema.default;
93
+ }
94
+ return normalizeArray(schema.enum)[0];
82
95
  }
83
- return [(0, exports.sampleFromSchema)(items)];
96
+ return primitive(schema);
84
97
  }
85
- if (schema.enum) {
86
- if (schema.default) {
87
- return schema.default;
88
- }
89
- return normalizeArray(schema.enum)[0];
98
+ catch (err) {
99
+ console.error(chalk_1.default.yellow("WARNING: failed to create example from schema object:", err));
100
+ return;
90
101
  }
91
- return primitive(schema);
92
102
  };
93
103
  exports.sampleFromSchema = sampleFromSchema;
94
104
  function primitive(schema = {}) {
@@ -1,4 +1,4 @@
1
- import { ApiMetadata } from "../types";
1
+ import { ApiMetadata, SidebarOptions } from "../types";
2
2
  import { OpenApiObjectWithRef, TagObject } from "./types";
3
3
  interface OpenApiFiles {
4
4
  source: string;
@@ -6,7 +6,7 @@ interface OpenApiFiles {
6
6
  data: OpenApiObjectWithRef;
7
7
  }
8
8
  export declare function readOpenapiFiles(openapiPath: string, _options: {}): Promise<OpenApiFiles[]>;
9
- export declare function processOpenapiFiles(files: OpenApiFiles[]): Promise<[ApiMetadata[], TagObject[]]>;
10
- export declare function processOpenapiFile(openapiDataWithRefs: OpenApiObjectWithRef): Promise<[ApiMetadata[], TagObject[]]>;
9
+ export declare function processOpenapiFiles(files: OpenApiFiles[], sidebarOptions: SidebarOptions): Promise<[ApiMetadata[], TagObject[]]>;
10
+ export declare function processOpenapiFile(openapiDataWithRefs: OpenApiObjectWithRef, sidebarOptions: SidebarOptions): Promise<[ApiMetadata[], TagObject[]]>;
11
11
  export declare function getTagDisplayName(tagName: string, tags: TagObject[]): string;
12
12
  export {};
@@ -63,12 +63,38 @@ async function createPostmanCollection(openapiData) {
63
63
  }
64
64
  return await jsonToCollection(data);
65
65
  }
66
- function createItems(openapiData) {
67
- var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o;
66
+ function createItems(openapiData, sidebarOptions) {
67
+ var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p;
68
68
  // TODO: Find a better way to handle this
69
69
  let items = [];
70
- // Only create an info page if we have a description.
70
+ if ((sidebarOptions === null || sidebarOptions === void 0 ? void 0 : sidebarOptions.categoryLinkSource) === "tag") {
71
+ // Only create an tag pages if categoryLinkSource set to tag.
72
+ const tags = (_a = openapiData.tags) !== null && _a !== void 0 ? _a : [];
73
+ // eslint-disable-next-line array-callback-return
74
+ tags
75
+ .filter((tag) => { var _a; return !((_a = tag.description) === null || _a === void 0 ? void 0 : _a.includes("SchemaDefinition")); })
76
+ // eslint-disable-next-line array-callback-return
77
+ .map((tag) => {
78
+ var _a;
79
+ const description = getTagDisplayName(tag.name, (_a = openapiData.tags) !== null && _a !== void 0 ? _a : []);
80
+ const tagId = (0, lodash_1.kebabCase)(tag.name);
81
+ const tagPage = {
82
+ type: "tag",
83
+ id: tagId,
84
+ unversionedId: tagId,
85
+ title: description !== null && description !== void 0 ? description : "",
86
+ description: description !== null && description !== void 0 ? description : "",
87
+ slug: "/" + tagId,
88
+ frontMatter: {},
89
+ tag: {
90
+ ...tag,
91
+ },
92
+ };
93
+ items.push(tagPage);
94
+ });
95
+ }
71
96
  if (openapiData.info.description) {
97
+ // Only create an info page if we have a description.
72
98
  const infoId = (0, lodash_1.kebabCase)(openapiData.info.title);
73
99
  const infoPage = {
74
100
  type: "info",
@@ -80,8 +106,8 @@ function createItems(openapiData) {
80
106
  frontMatter: {},
81
107
  info: {
82
108
  ...openapiData.info,
83
- tags: (_a = openapiData.tags) === null || _a === void 0 ? void 0 : _a.map((tagName) => { var _a; return getTagDisplayName(tagName.name, (_a = openapiData.tags) !== null && _a !== void 0 ? _a : []); }),
84
- title: (_b = openapiData.info.title) !== null && _b !== void 0 ? _b : "Introduction",
109
+ tags: (_b = openapiData.tags) === null || _b === void 0 ? void 0 : _b.map((tagName) => { var _a; return getTagDisplayName(tagName.name, (_a = openapiData.tags) !== null && _a !== void 0 ? _a : []); }),
110
+ title: (_c = openapiData.info.title) !== null && _c !== void 0 ? _c : "Introduction",
85
111
  },
86
112
  };
87
113
  items.push(infoPage);
@@ -89,16 +115,16 @@ function createItems(openapiData) {
89
115
  for (let [path, pathObject] of Object.entries(openapiData.paths)) {
90
116
  const { $ref, description, parameters, servers, summary, ...rest } = pathObject;
91
117
  for (let [method, operationObject] of Object.entries({ ...rest })) {
92
- const title = (_d = (_c = operationObject.summary) !== null && _c !== void 0 ? _c : operationObject.operationId) !== null && _d !== void 0 ? _d : "Missing summary";
118
+ const title = (_e = (_d = operationObject.summary) !== null && _d !== void 0 ? _d : operationObject.operationId) !== null && _e !== void 0 ? _e : "Missing summary";
93
119
  if (operationObject.description === undefined) {
94
120
  operationObject.description =
95
- (_f = (_e = operationObject.summary) !== null && _e !== void 0 ? _e : operationObject.operationId) !== null && _f !== void 0 ? _f : "";
121
+ (_g = (_f = operationObject.summary) !== null && _f !== void 0 ? _f : operationObject.operationId) !== null && _g !== void 0 ? _g : "";
96
122
  }
97
123
  const baseId = (0, lodash_1.kebabCase)(title);
98
- const servers = (_h = (_g = operationObject.servers) !== null && _g !== void 0 ? _g : pathObject.servers) !== null && _h !== void 0 ? _h : openapiData.servers;
99
- const security = (_j = operationObject.security) !== null && _j !== void 0 ? _j : openapiData.security;
124
+ const servers = (_j = (_h = operationObject.servers) !== null && _h !== void 0 ? _h : pathObject.servers) !== null && _j !== void 0 ? _j : openapiData.servers;
125
+ const security = (_k = operationObject.security) !== null && _k !== void 0 ? _k : openapiData.security;
100
126
  // Add security schemes so we know how to handle security.
101
- const securitySchemes = (_k = openapiData.components) === null || _k === void 0 ? void 0 : _k.securitySchemes;
127
+ const securitySchemes = (_l = openapiData.components) === null || _l === void 0 ? void 0 : _l.securitySchemes;
102
128
  // Make sure schemes are lowercase. See: https://github.com/cloud-annotations/docusaurus-plugin-openapi/issues/79
103
129
  if (securitySchemes) {
104
130
  for (let securityScheme of Object.values(securitySchemes)) {
@@ -108,7 +134,7 @@ function createItems(openapiData) {
108
134
  }
109
135
  }
110
136
  let jsonRequestBodyExample;
111
- const body = (_m = (_l = operationObject.requestBody) === null || _l === void 0 ? void 0 : _l.content) === null || _m === void 0 ? void 0 : _m["application/json"];
137
+ const body = (_o = (_m = operationObject.requestBody) === null || _m === void 0 ? void 0 : _m.content) === null || _o === void 0 ? void 0 : _o["application/json"];
112
138
  if (body === null || body === void 0 ? void 0 : body.schema) {
113
139
  jsonRequestBodyExample = (0, createExample_1.sampleFromSchema)(body.schema);
114
140
  }
@@ -124,7 +150,7 @@ function createItems(openapiData) {
124
150
  frontMatter: {},
125
151
  api: {
126
152
  ...defaults,
127
- tags: (_o = operationObject.tags) === null || _o === void 0 ? void 0 : _o.map((tagName) => { var _a; return getTagDisplayName(tagName, (_a = openapiData.tags) !== null && _a !== void 0 ? _a : []); }),
153
+ tags: (_p = operationObject.tags) === null || _p === void 0 ? void 0 : _p.map((tagName) => { var _a; return getTagDisplayName(tagName, (_a = openapiData.tags) !== null && _a !== void 0 ? _a : []); }),
128
154
  method,
129
155
  path,
130
156
  servers,
@@ -149,7 +175,7 @@ function bindCollectionToApiItems(items, postmanCollection) {
149
175
  .getPath({ unresolved: true }) // unresolved returns "/:variableName" instead of "/<type>"
150
176
  .replace(/:([a-z0-9-_]+)/gi, "{$1}"); // replace "/:variableName" with "/{variableName}"
151
177
  const apiItem = items.find((item) => {
152
- if (item.type === "info") {
178
+ if (item.type === "info" || item.type === "tag") {
153
179
  return false;
154
180
  }
155
181
  return item.api.path === path && item.api.method === method;
@@ -192,9 +218,9 @@ async function readOpenapiFiles(openapiPath, _options) {
192
218
  ];
193
219
  }
194
220
  exports.readOpenapiFiles = readOpenapiFiles;
195
- async function processOpenapiFiles(files) {
221
+ async function processOpenapiFiles(files, sidebarOptions) {
196
222
  const promises = files.map(async (file) => {
197
- const processedFile = await processOpenapiFile(file.data);
223
+ const processedFile = await processOpenapiFile(file.data, sidebarOptions);
198
224
  const itemsObjectsArray = processedFile[0].map((item) => ({
199
225
  ...item,
200
226
  }));
@@ -213,10 +239,10 @@ async function processOpenapiFiles(files) {
213
239
  return [items, tags];
214
240
  }
215
241
  exports.processOpenapiFiles = processOpenapiFiles;
216
- async function processOpenapiFile(openapiDataWithRefs) {
242
+ async function processOpenapiFile(openapiDataWithRefs, sidebarOptions) {
217
243
  const openapiData = await resolveRefs(openapiDataWithRefs);
218
244
  const postmanCollection = await createPostmanCollection(openapiData);
219
- const items = createItems(openapiData);
245
+ const items = createItems(openapiData, sidebarOptions);
220
246
  bindCollectionToApiItems(items, postmanCollection);
221
247
  let tags = [];
222
248
  if (openapiData.tags !== undefined) {
@@ -67,7 +67,7 @@ function groupByTags(items, sidebarOptions, options, tags) {
67
67
  const tagged = apiTags
68
68
  .map((tag) => {
69
69
  // Map info object to tag
70
- const infoObject = intros.find((i) => i.tags.includes(tag));
70
+ const taggedInfoObject = intros.find((i) => i.tags ? i.tags.includes(tag) : undefined);
71
71
  const tagObject = tags.flat().find((t) => {
72
72
  var _a;
73
73
  return (_a = (tag === t.name || tag === t["x-displayName"])) !== null && _a !== void 0 ? _a : {
@@ -77,20 +77,18 @@ function groupByTags(items, sidebarOptions, options, tags) {
77
77
  });
78
78
  // TODO: perhaps move this into a getLinkConfig() function
79
79
  let linkConfig = undefined;
80
- if (infoObject !== undefined && categoryLinkSource === "info") {
80
+ if (taggedInfoObject !== undefined && categoryLinkSource === "info") {
81
81
  linkConfig = {
82
82
  type: "doc",
83
- id: `${basePath}/${infoObject.id}`,
83
+ id: `${basePath}/${taggedInfoObject.id}`,
84
84
  };
85
85
  }
86
86
  // TODO: perhaps move this into a getLinkConfig() function
87
87
  if (tagObject !== undefined && categoryLinkSource === "tag") {
88
- const linkDescription = tagObject === null || tagObject === void 0 ? void 0 : tagObject.description;
88
+ const tagId = (0, lodash_1.kebabCase)(tagObject.name);
89
89
  linkConfig = {
90
- type: "generated-index",
91
- title: tag,
92
- description: linkDescription,
93
- slug: "/category/" + (0, lodash_1.kebabCase)(tag),
90
+ type: "doc",
91
+ id: `${basePath}/${tagId}`,
94
92
  };
95
93
  }
96
94
  // Default behavior
@@ -113,28 +111,34 @@ function groupByTags(items, sidebarOptions, options, tags) {
113
111
  };
114
112
  })
115
113
  .filter((item) => item.items.length > 0); // Filter out any categories with no items.
116
- // TODO: determine how we want to handle these
117
- // const untagged = [
118
- // {
119
- // type: "category" as const,
120
- // label: "UNTAGGED",
121
- // collapsible: sidebarCollapsible,
122
- // collapsed: sidebarCollapsed,
123
- // items: apiItems
124
- // .filter(({ api }) => api.tags === undefined || api.tags.length === 0)
125
- // .map(createDocItem),
126
- // },
127
- // ];
114
+ // Handle items with no tag
115
+ const untaggedItems = apiItems
116
+ .filter(({ api }) => api.tags === undefined || api.tags.length === 0)
117
+ .map(createDocItem);
118
+ let untagged = [];
119
+ if (untaggedItems.length > 0) {
120
+ untagged = [
121
+ {
122
+ type: "category",
123
+ label: "UNTAGGED",
124
+ collapsible: sidebarCollapsible,
125
+ collapsed: sidebarCollapsed,
126
+ items: apiItems
127
+ .filter(({ api }) => api.tags === undefined || api.tags.length === 0)
128
+ .map(createDocItem),
129
+ },
130
+ ];
131
+ }
128
132
  // Shift root intro doc to top of sidebar
129
133
  // TODO: Add input validation for categoryLinkSource options
130
134
  if (rootIntroDoc && categoryLinkSource !== "info") {
131
135
  tagged.unshift(rootIntroDoc);
132
136
  }
133
- return [...tagged];
137
+ return [...tagged, ...untagged];
134
138
  }
135
139
  function generateSidebarSlice(sidebarOptions, options, api, tags) {
136
140
  let sidebarSlice = [];
137
- if (sidebarOptions.groupPathsBy === "tags") {
141
+ if (sidebarOptions.groupPathsBy === "tag") {
138
142
  sidebarSlice = groupByTags(api, sidebarOptions, options, tags);
139
143
  }
140
144
  return 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 } from "./openapi/types";
2
+ import { InfoObject, OperationObject, 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;
@@ -16,7 +16,7 @@ export interface APIOptions {
16
16
  export interface LoadedContent {
17
17
  loadedApi: ApiMetadata[];
18
18
  }
19
- export declare type ApiMetadata = ApiPageMetadata | InfoPageMetadata;
19
+ export declare type ApiMetadata = ApiPageMetadata | InfoPageMetadata | TagPageMetadata;
20
20
  export interface ApiMetadataBase {
21
21
  sidebar?: string;
22
22
  previous?: ApiNavLink;
@@ -53,6 +53,11 @@ export interface InfoPageMetadata extends ApiMetadataBase {
53
53
  info: ApiInfo;
54
54
  markdown?: string;
55
55
  }
56
+ export interface TagPageMetadata extends ApiMetadataBase {
57
+ type: "tag";
58
+ tag: TagObject;
59
+ markdown?: string;
60
+ }
56
61
  export declare type ApiInfo = InfoObject;
57
62
  export interface ApiNavLink {
58
63
  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-356",
4
+ "version": "0.0.0-361",
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": "16f3e2a33ddf094ea9c5f75ff497b18694505cba"
63
+ "gitHead": "e87c5db5b36a32b85f7ba0aaea0a0280be498954"
64
64
  }
package/src/index.ts CHANGED
@@ -13,7 +13,7 @@ import { Globby } from "@docusaurus/utils";
13
13
  import chalk from "chalk";
14
14
  import { render } from "mustache";
15
15
 
16
- import { createApiPageMD, createInfoPageMD } from "./markdown";
16
+ import { createApiPageMD, createInfoPageMD, createTagPageMD } from "./markdown";
17
17
  import { readOpenapiFiles, processOpenapiFiles } from "./openapi";
18
18
  import generateSidebarSlice from "./sidebars";
19
19
  import type { PluginOptions, LoadedContent, APIOptions } from "./types";
@@ -32,7 +32,10 @@ export default function pluginOpenAPI(
32
32
 
33
33
  try {
34
34
  const openapiFiles = await readOpenapiFiles(contentPath, {});
35
- const [loadedApi, tags] = await processOpenapiFiles(openapiFiles);
35
+ const [loadedApi, tags] = await processOpenapiFiles(
36
+ openapiFiles,
37
+ sidebarOptions!
38
+ );
36
39
  if (!fs.existsSync(outputDir)) {
37
40
  try {
38
41
  fs.mkdirSync(outputDir, { recursive: true });
@@ -119,19 +122,43 @@ hide_title: true
119
122
  import DocCardList from '@theme/DocCardList';
120
123
  import {useCurrentSidebarCategory} from '@docusaurus/theme-common';
121
124
 
125
+ <DocCardList items={useCurrentSidebarCategory().items}/>
126
+ \`\`\`
127
+ `;
128
+
129
+ const tagMdTemplate = template
130
+ ? fs.readFileSync(template).toString()
131
+ : `---
132
+ id: {{{id}}}
133
+ title: {{{description}}}
134
+ description: {{{description}}}
135
+ ---
136
+
137
+ {{{markdown}}}
138
+
139
+ \`\`\`mdx-code-block
140
+ import DocCardList from '@theme/DocCardList';
141
+ import {useCurrentSidebarCategory} from '@docusaurus/theme-common';
142
+
122
143
  <DocCardList items={useCurrentSidebarCategory().items}/>
123
144
  \`\`\`
124
145
  `;
125
146
 
126
147
  loadedApi.map(async (item) => {
127
148
  const markdown =
128
- item.type === "api" ? createApiPageMD(item) : createInfoPageMD(item);
149
+ item.type === "api"
150
+ ? createApiPageMD(item)
151
+ : item.type === "info"
152
+ ? createInfoPageMD(item)
153
+ : createTagPageMD(item);
129
154
  item.markdown = markdown;
130
155
  if (item.type === "api") {
131
156
  item.json = JSON.stringify(item.api);
132
157
  }
133
158
  const view = render(mdTemplate, item);
134
159
  const utils = render(infoMdTemplate, item);
160
+ // eslint-disable-next-line testing-library/render-result-naming-convention
161
+ const tagUtils = render(tagMdTemplate, item);
135
162
 
136
163
  if (item.type === "api") {
137
164
  if (!fs.existsSync(`${outputDir}/${item.id}.api.mdx`)) {
@@ -179,8 +206,31 @@ import {useCurrentSidebarCategory} from '@docusaurus/theme-common';
179
206
  }
180
207
  }
181
208
  }
209
+
210
+ if (item.type === "tag") {
211
+ if (!fs.existsSync(`${outputDir}/${item.id}.tag.mdx`)) {
212
+ try {
213
+ fs.writeFileSync(
214
+ `${outputDir}/${item.id}.tag.mdx`,
215
+ tagUtils,
216
+ "utf8"
217
+ );
218
+ console.log(
219
+ chalk.green(
220
+ `Successfully created "${outputDir}/${item.id}.tag.mdx"`
221
+ )
222
+ );
223
+ } catch (err) {
224
+ console.error(
225
+ chalk.red(`Failed to write "${outputDir}/${item.id}.tag.mdx"`),
226
+ chalk.yellow(err)
227
+ );
228
+ }
229
+ }
230
+ }
182
231
  return;
183
232
  });
233
+
184
234
  return;
185
235
  } catch (e) {
186
236
  console.error(chalk.red(`Loading of api failed for "${contentPath}"`));
@@ -191,7 +241,7 @@ import {useCurrentSidebarCategory} from '@docusaurus/theme-common';
191
241
  async function cleanApiDocs(options: APIOptions) {
192
242
  const { outputDir } = options;
193
243
  const apiDir = path.join(siteDir, outputDir);
194
- const apiMdxFiles = await Globby(["*.api.mdx", "*.info.mdx"], {
244
+ const apiMdxFiles = await Globby(["*.api.mdx", "*.info.mdx", "*.tag.mdx"], {
195
245
  cwd: path.resolve(apiDir),
196
246
  });
197
247
  const sidebarFile = await Globby(["sidebar.js"], {
@@ -8,7 +8,7 @@
8
8
  import { escape } from "lodash";
9
9
 
10
10
  import { ContactObject, LicenseObject } from "../openapi/types";
11
- import { ApiPageMetadata, InfoPageMetadata } from "../types";
11
+ import { ApiPageMetadata, InfoPageMetadata, TagPageMetadata } from "../types";
12
12
  import { createContactInfo } from "./createContactInfo";
13
13
  import { createDeprecationNotice } from "./createDeprecationNotice";
14
14
  import { createDescription } from "./createDescription";
@@ -60,3 +60,7 @@ export function createInfoPageMD({
60
60
  createLicense(license as LicenseObject),
61
61
  ]);
62
62
  }
63
+
64
+ export function createTagPageMD({ tag: { description } }: TagPageMetadata) {
65
+ return render([createDescription(description)]);
66
+ }
@@ -5,6 +5,8 @@
5
5
  * LICENSE file in the root directory of this source tree.
6
6
  * ========================================================================== */
7
7
 
8
+ import chalk from "chalk";
9
+
8
10
  import { SchemaObject } from "./types";
9
11
 
10
12
  interface OASTypeToTypeMap {
@@ -48,71 +50,78 @@ const primitives: Primitives = {
48
50
  };
49
51
 
50
52
  export const sampleFromSchema = (schema: SchemaObject = {}): any => {
51
- let { type, example, allOf, properties, items } = schema;
53
+ try {
54
+ let { type, example, allOf, properties, items } = schema;
52
55
 
53
- if (example !== undefined) {
54
- return example;
55
- }
56
+ if (example !== undefined) {
57
+ return example;
58
+ }
56
59
 
57
- if (allOf) {
58
- // TODO: We are just assuming it will always be an object for now
59
- let obj: SchemaObject = {
60
- type: "object",
61
- properties: {},
62
- required: [], // NOTE: We shouldn't need to worry about required
63
- };
64
- for (let item of allOf) {
65
- if (item.properties) {
66
- obj.properties = {
67
- ...obj.properties,
68
- ...item.properties,
69
- };
60
+ if (allOf) {
61
+ // TODO: We are just assuming it will always be an object for now
62
+ let obj: SchemaObject = {
63
+ type: "object",
64
+ properties: {},
65
+ required: [], // NOTE: We shouldn't need to worry about required
66
+ };
67
+ for (let item of allOf) {
68
+ if (item.properties) {
69
+ obj.properties = {
70
+ ...obj.properties,
71
+ ...item.properties,
72
+ };
73
+ }
70
74
  }
75
+ return sampleFromSchema(obj);
71
76
  }
72
- return sampleFromSchema(obj);
73
- }
74
77
 
75
- if (!type) {
76
- if (properties) {
77
- type = "object";
78
- } else if (items) {
79
- type = "array";
80
- } else {
81
- return;
78
+ if (!type) {
79
+ if (properties) {
80
+ type = "object";
81
+ } else if (items) {
82
+ type = "array";
83
+ } else {
84
+ return;
85
+ }
82
86
  }
83
- }
84
87
 
85
- if (type === "object") {
86
- let obj: any = {};
87
- for (let [name, prop] of Object.entries(properties ?? {})) {
88
- if (prop.deprecated) {
89
- continue;
88
+ if (type === "object") {
89
+ let obj: any = {};
90
+ for (let [name, prop] of Object.entries(properties ?? {})) {
91
+ if (prop.deprecated) {
92
+ continue;
93
+ }
94
+ obj[name] = sampleFromSchema(prop);
90
95
  }
91
- obj[name] = sampleFromSchema(prop);
96
+ return obj;
92
97
  }
93
- return obj;
94
- }
95
98
 
96
- if (type === "array") {
97
- if (Array.isArray(items?.anyOf)) {
98
- return items?.anyOf.map((item) => sampleFromSchema(item));
99
- }
99
+ if (type === "array") {
100
+ if (Array.isArray(items?.anyOf)) {
101
+ return items?.anyOf.map((item) => sampleFromSchema(item));
102
+ }
100
103
 
101
- if (Array.isArray(items?.oneOf)) {
102
- return items?.oneOf.map((item) => sampleFromSchema(item));
103
- }
104
+ if (Array.isArray(items?.oneOf)) {
105
+ return items?.oneOf.map((item) => sampleFromSchema(item));
106
+ }
104
107
 
105
- return [sampleFromSchema(items)];
106
- }
108
+ return [sampleFromSchema(items)];
109
+ }
107
110
 
108
- if (schema.enum) {
109
- if (schema.default) {
110
- return schema.default;
111
+ if (schema.enum) {
112
+ if (schema.default) {
113
+ return schema.default;
114
+ }
115
+ return normalizeArray(schema.enum)[0];
111
116
  }
112
- return normalizeArray(schema.enum)[0];
113
- }
114
117
 
115
- return primitive(schema);
118
+ return primitive(schema);
119
+ } catch (err) {
120
+ console.error(
121
+ chalk.yellow("WARNING: failed to create example from schema object:", err)
122
+ );
123
+ return;
124
+ }
116
125
  };
117
126
 
118
127
  function primitive(schema: SchemaObject = {}) {
@@ -17,7 +17,13 @@ import yaml from "js-yaml";
17
17
  import JsonRefs from "json-refs";
18
18
  import { kebabCase } from "lodash";
19
19
 
20
- import { ApiMetadata, ApiPageMetadata, InfoPageMetadata } from "../types";
20
+ import {
21
+ ApiMetadata,
22
+ ApiPageMetadata,
23
+ InfoPageMetadata,
24
+ SidebarOptions,
25
+ TagPageMetadata,
26
+ } from "../types";
21
27
  import { sampleFromSchema } from "./createExample";
22
28
  import { OpenApiObject, OpenApiObjectWithRef, TagObject } from "./types";
23
29
 
@@ -75,12 +81,44 @@ async function createPostmanCollection(
75
81
 
76
82
  type PartialPage<T> = Omit<T, "permalink" | "source" | "sourceDirName">;
77
83
 
78
- function createItems(openapiData: OpenApiObject): ApiMetadata[] {
84
+ function createItems(
85
+ openapiData: OpenApiObject,
86
+ sidebarOptions: SidebarOptions
87
+ ): ApiMetadata[] {
79
88
  // TODO: Find a better way to handle this
80
89
  let items: PartialPage<ApiMetadata>[] = [];
81
90
 
82
- // Only create an info page if we have a description.
91
+ if (sidebarOptions?.categoryLinkSource === "tag") {
92
+ // Only create an tag pages if categoryLinkSource set to tag.
93
+ const tags: TagObject[] = openapiData.tags ?? [];
94
+ // eslint-disable-next-line array-callback-return
95
+ tags
96
+ .filter((tag) => !tag.description?.includes("SchemaDefinition"))
97
+ // eslint-disable-next-line array-callback-return
98
+ .map((tag) => {
99
+ const description = getTagDisplayName(
100
+ tag.name!,
101
+ openapiData.tags ?? []
102
+ );
103
+ const tagId = kebabCase(tag.name);
104
+ const tagPage: PartialPage<TagPageMetadata> = {
105
+ type: "tag",
106
+ id: tagId,
107
+ unversionedId: tagId,
108
+ title: description ?? "",
109
+ description: description ?? "",
110
+ slug: "/" + tagId,
111
+ frontMatter: {},
112
+ tag: {
113
+ ...tag,
114
+ },
115
+ };
116
+ items.push(tagPage);
117
+ });
118
+ }
119
+
83
120
  if (openapiData.info.description) {
121
+ // Only create an info page if we have a description.
84
122
  const infoId = kebabCase(openapiData.info.title);
85
123
  const infoPage: PartialPage<InfoPageMetadata> = {
86
124
  type: "info",
@@ -186,7 +224,7 @@ function bindCollectionToApiItems(
186
224
  .replace(/:([a-z0-9-_]+)/gi, "{$1}"); // replace "/:variableName" with "/{variableName}"
187
225
 
188
226
  const apiItem = items.find((item) => {
189
- if (item.type === "info") {
227
+ if (item.type === "info" || item.type === "tag") {
190
228
  return false;
191
229
  }
192
230
  return item.api.path === path && item.api.method === method;
@@ -248,10 +286,11 @@ export async function readOpenapiFiles(
248
286
  }
249
287
 
250
288
  export async function processOpenapiFiles(
251
- files: OpenApiFiles[]
289
+ files: OpenApiFiles[],
290
+ sidebarOptions: SidebarOptions
252
291
  ): Promise<[ApiMetadata[], TagObject[]]> {
253
292
  const promises = files.map(async (file) => {
254
- const processedFile = await processOpenapiFile(file.data);
293
+ const processedFile = await processOpenapiFile(file.data, sidebarOptions);
255
294
  const itemsObjectsArray = processedFile[0].map((item) => ({
256
295
  ...item,
257
296
  }));
@@ -271,11 +310,12 @@ export async function processOpenapiFiles(
271
310
  }
272
311
 
273
312
  export async function processOpenapiFile(
274
- openapiDataWithRefs: OpenApiObjectWithRef
313
+ openapiDataWithRefs: OpenApiObjectWithRef,
314
+ sidebarOptions: SidebarOptions
275
315
  ): Promise<[ApiMetadata[], TagObject[]]> {
276
316
  const openapiData = await resolveRefs(openapiDataWithRefs);
277
317
  const postmanCollection = await createPostmanCollection(openapiData);
278
- const items = createItems(openapiData);
318
+ const items = createItems(openapiData, sidebarOptions);
279
319
 
280
320
  bindCollectionToApiItems(items, postmanCollection);
281
321
 
@@ -7,6 +7,7 @@
7
7
 
8
8
  import {
9
9
  ProcessedSidebar,
10
+ SidebarItemCategory,
10
11
  SidebarItemCategoryLinkConfig,
11
12
  SidebarItemDoc,
12
13
  } from "@docusaurus/plugin-content-docs/src/sidebars/types";
@@ -99,7 +100,9 @@ function groupByTags(
99
100
  const tagged = apiTags
100
101
  .map((tag) => {
101
102
  // Map info object to tag
102
- const infoObject = intros.find((i) => i.tags.includes(tag));
103
+ const taggedInfoObject = intros.find((i) =>
104
+ i.tags ? i.tags.includes(tag) : undefined
105
+ );
103
106
  const tagObject = tags.flat().find(
104
107
  (t) =>
105
108
  (tag === t.name || tag === t["x-displayName"]) ?? {
@@ -110,21 +113,19 @@ function groupByTags(
110
113
 
111
114
  // TODO: perhaps move this into a getLinkConfig() function
112
115
  let linkConfig = undefined;
113
- if (infoObject !== undefined && categoryLinkSource === "info") {
116
+ if (taggedInfoObject !== undefined && categoryLinkSource === "info") {
114
117
  linkConfig = {
115
118
  type: "doc",
116
- id: `${basePath}/${infoObject.id}`,
119
+ id: `${basePath}/${taggedInfoObject.id}`,
117
120
  } as SidebarItemCategoryLinkConfig;
118
121
  }
119
122
 
120
123
  // TODO: perhaps move this into a getLinkConfig() function
121
124
  if (tagObject !== undefined && categoryLinkSource === "tag") {
122
- const linkDescription = tagObject?.description;
125
+ const tagId = kebabCase(tagObject.name);
123
126
  linkConfig = {
124
- type: "generated-index" as "generated-index",
125
- title: tag,
126
- description: linkDescription,
127
- slug: "/category/" + kebabCase(tag),
127
+ type: "doc",
128
+ id: `${basePath}/${tagId}`,
128
129
  } as SidebarItemCategoryLinkConfig;
129
130
  }
130
131
 
@@ -150,18 +151,24 @@ function groupByTags(
150
151
  })
151
152
  .filter((item) => item.items.length > 0); // Filter out any categories with no items.
152
153
 
153
- // TODO: determine how we want to handle these
154
- // const untagged = [
155
- // {
156
- // type: "category" as const,
157
- // label: "UNTAGGED",
158
- // collapsible: sidebarCollapsible,
159
- // collapsed: sidebarCollapsed,
160
- // items: apiItems
161
- // .filter(({ api }) => api.tags === undefined || api.tags.length === 0)
162
- // .map(createDocItem),
163
- // },
164
- // ];
154
+ // Handle items with no tag
155
+ const untaggedItems = apiItems
156
+ .filter(({ api }) => api.tags === undefined || api.tags.length === 0)
157
+ .map(createDocItem);
158
+ let untagged: SidebarItemCategory[] = [];
159
+ if (untaggedItems.length > 0) {
160
+ untagged = [
161
+ {
162
+ type: "category" as const,
163
+ label: "UNTAGGED",
164
+ collapsible: sidebarCollapsible!,
165
+ collapsed: sidebarCollapsed!,
166
+ items: apiItems
167
+ .filter(({ api }) => api.tags === undefined || api.tags.length === 0)
168
+ .map(createDocItem),
169
+ },
170
+ ];
171
+ }
165
172
 
166
173
  // Shift root intro doc to top of sidebar
167
174
  // TODO: Add input validation for categoryLinkSource options
@@ -169,7 +176,7 @@ function groupByTags(
169
176
  tagged.unshift(rootIntroDoc as any);
170
177
  }
171
178
 
172
- return [...tagged];
179
+ return [...tagged, ...untagged];
173
180
  }
174
181
 
175
182
  export default function generateSidebarSlice(
@@ -179,7 +186,7 @@ export default function generateSidebarSlice(
179
186
  tags: TagObject[]
180
187
  ) {
181
188
  let sidebarSlice: ProcessedSidebar = [];
182
- if (sidebarOptions.groupPathsBy === "tags") {
189
+ if (sidebarOptions.groupPathsBy === "tag") {
183
190
  sidebarSlice = groupByTags(
184
191
  api as ApiPageMetadata[],
185
192
  sidebarOptions,
package/src/types.ts CHANGED
@@ -11,6 +11,7 @@ import {
11
11
  InfoObject,
12
12
  OperationObject,
13
13
  SecuritySchemeObject,
14
+ TagObject,
14
15
  } from "./openapi/types";
15
16
 
16
17
  export type {
@@ -38,7 +39,7 @@ export interface LoadedContent {
38
39
  // loadedDocs: DocPageMetadata[]; TODO: cleanup
39
40
  }
40
41
 
41
- export type ApiMetadata = ApiPageMetadata | InfoPageMetadata;
42
+ export type ApiMetadata = ApiPageMetadata | InfoPageMetadata | TagPageMetadata;
42
43
 
43
44
  export interface ApiMetadataBase {
44
45
  sidebar?: string;
@@ -81,6 +82,12 @@ export interface InfoPageMetadata extends ApiMetadataBase {
81
82
  markdown?: string;
82
83
  }
83
84
 
85
+ export interface TagPageMetadata extends ApiMetadataBase {
86
+ type: "tag";
87
+ tag: TagObject;
88
+ markdown?: string;
89
+ }
90
+
84
91
  export type ApiInfo = InfoObject;
85
92
 
86
93
  export interface ApiNavLink {