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 +2 -2
- package/lib/index.js +37 -3
- package/lib/markdown/index.d.ts +2 -1
- package/lib/markdown/index.js +5 -1
- package/lib/openapi/createExample.js +59 -49
- package/lib/openapi/openapi.d.ts +3 -3
- package/lib/openapi/openapi.js +43 -17
- package/lib/sidebars/index.js +26 -22
- package/lib/types.d.ts +7 -2
- package/package.json +2 -2
- package/src/index.ts +54 -4
- package/src/markdown/index.ts +5 -1
- package/src/openapi/createExample.ts +59 -50
- package/src/openapi/openapi.ts +48 -8
- package/src/sidebars/index.ts +29 -22
- package/src/types.ts +8 -1
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: "
|
|
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
|
|
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"
|
|
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"], {
|
package/lib/markdown/index.d.ts
CHANGED
|
@@ -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;
|
package/lib/markdown/index.js
CHANGED
|
@@ -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
|
-
|
|
35
|
-
|
|
36
|
-
|
|
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
|
-
|
|
60
|
-
|
|
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
|
-
|
|
63
|
-
|
|
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
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
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
|
|
79
|
+
return obj;
|
|
73
80
|
}
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
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 (
|
|
81
|
-
|
|
90
|
+
if (schema.enum) {
|
|
91
|
+
if (schema.default) {
|
|
92
|
+
return schema.default;
|
|
93
|
+
}
|
|
94
|
+
return normalizeArray(schema.enum)[0];
|
|
82
95
|
}
|
|
83
|
-
return
|
|
96
|
+
return primitive(schema);
|
|
84
97
|
}
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
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 = {}) {
|
package/lib/openapi/openapi.d.ts
CHANGED
|
@@ -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 {};
|
package/lib/openapi/openapi.js
CHANGED
|
@@ -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
|
-
|
|
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: (
|
|
84
|
-
title: (
|
|
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 = (
|
|
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
|
-
(
|
|
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 = (
|
|
99
|
-
const 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 = (
|
|
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 = (
|
|
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: (
|
|
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) {
|
package/lib/sidebars/index.js
CHANGED
|
@@ -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
|
|
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 (
|
|
80
|
+
if (taggedInfoObject !== undefined && categoryLinkSource === "info") {
|
|
81
81
|
linkConfig = {
|
|
82
82
|
type: "doc",
|
|
83
|
-
id: `${basePath}/${
|
|
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
|
|
88
|
+
const tagId = (0, lodash_1.kebabCase)(tagObject.name);
|
|
89
89
|
linkConfig = {
|
|
90
|
-
type: "
|
|
91
|
-
|
|
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
|
-
//
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
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 === "
|
|
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-
|
|
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": "
|
|
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(
|
|
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"
|
|
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"], {
|
package/src/markdown/index.ts
CHANGED
|
@@ -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
|
-
|
|
53
|
+
try {
|
|
54
|
+
let { type, example, allOf, properties, items } = schema;
|
|
52
55
|
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
+
if (example !== undefined) {
|
|
57
|
+
return example;
|
|
58
|
+
}
|
|
56
59
|
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
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
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
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
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
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
|
|
96
|
+
return obj;
|
|
92
97
|
}
|
|
93
|
-
return obj;
|
|
94
|
-
}
|
|
95
98
|
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
99
|
+
if (type === "array") {
|
|
100
|
+
if (Array.isArray(items?.anyOf)) {
|
|
101
|
+
return items?.anyOf.map((item) => sampleFromSchema(item));
|
|
102
|
+
}
|
|
100
103
|
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
+
if (Array.isArray(items?.oneOf)) {
|
|
105
|
+
return items?.oneOf.map((item) => sampleFromSchema(item));
|
|
106
|
+
}
|
|
104
107
|
|
|
105
|
-
|
|
106
|
-
|
|
108
|
+
return [sampleFromSchema(items)];
|
|
109
|
+
}
|
|
107
110
|
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
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
|
-
|
|
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 = {}) {
|
package/src/openapi/openapi.ts
CHANGED
|
@@ -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 {
|
|
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(
|
|
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
|
-
|
|
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
|
|
package/src/sidebars/index.ts
CHANGED
|
@@ -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
|
|
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 (
|
|
116
|
+
if (taggedInfoObject !== undefined && categoryLinkSource === "info") {
|
|
114
117
|
linkConfig = {
|
|
115
118
|
type: "doc",
|
|
116
|
-
id: `${basePath}/${
|
|
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
|
|
125
|
+
const tagId = kebabCase(tagObject.name);
|
|
123
126
|
linkConfig = {
|
|
124
|
-
type: "
|
|
125
|
-
|
|
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
|
-
//
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
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 === "
|
|
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 {
|