docusaurus-plugin-openapi-docs 0.0.0-351 → 0.0.0-352
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/lib/index.js +32 -7
- package/lib/openapi/openapi.d.ts +4 -3
- package/lib/openapi/openapi.js +35 -19
- package/lib/openapi/types.d.ts +2 -1
- package/lib/sidebars/index.d.ts +2 -1
- package/lib/sidebars/index.js +63 -19
- package/lib/types.d.ts +2 -0
- package/package.json +2 -2
- package/src/index.ts +44 -8
- package/src/openapi/openapi.ts +29 -12
- package/src/openapi/types.ts +2 -1
- package/src/sidebars/index.ts +80 -20
- package/src/types.ts +2 -0
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 = await (0, openapi_1.processOpenapiFiles)(openapiFiles);
|
|
28
|
+
const [loadedApi, tags] = await (0, openapi_1.processOpenapiFiles)(openapiFiles);
|
|
29
29
|
if (!fs_1.default.existsSync(outputDir)) {
|
|
30
30
|
try {
|
|
31
31
|
fs_1.default.mkdirSync(outputDir, { recursive: true });
|
|
@@ -38,7 +38,7 @@ function pluginOpenAPI(context, options) {
|
|
|
38
38
|
// TODO: figure out better way to set default
|
|
39
39
|
if (Object.keys(sidebarOptions !== null && sidebarOptions !== void 0 ? sidebarOptions : {}).length > 0) {
|
|
40
40
|
const sidebarSlice = (0, sidebars_1.default)(sidebarOptions, // TODO: find a better way to handle null
|
|
41
|
-
options, loadedApi);
|
|
41
|
+
options, loadedApi, tags);
|
|
42
42
|
const sidebarSliceTemplate = template
|
|
43
43
|
? fs_1.default.readFileSync(template).toString()
|
|
44
44
|
: `module.exports = {{{slice}}};`;
|
|
@@ -59,7 +59,12 @@ function pluginOpenAPI(context, options) {
|
|
|
59
59
|
? fs_1.default.readFileSync(template).toString()
|
|
60
60
|
: `---
|
|
61
61
|
id: {{{id}}}
|
|
62
|
+
{{^api}}
|
|
63
|
+
sidebar_label: Introduction
|
|
64
|
+
{{/api}}
|
|
65
|
+
{{#api}}
|
|
62
66
|
sidebar_label: {{{title}}}
|
|
67
|
+
{{/api}}
|
|
63
68
|
{{^api}}
|
|
64
69
|
sidebar_position: 0
|
|
65
70
|
{{/api}}
|
|
@@ -76,6 +81,23 @@ sidebar_class_name: "{{{api.method}}} api-method"
|
|
|
76
81
|
---
|
|
77
82
|
|
|
78
83
|
{{{markdown}}}
|
|
84
|
+
`;
|
|
85
|
+
const infoMdTemplate = template
|
|
86
|
+
? fs_1.default.readFileSync(template).toString()
|
|
87
|
+
: `---
|
|
88
|
+
id: {{{id}}}
|
|
89
|
+
sidebar_label: {{{title}}}
|
|
90
|
+
hide_title: true
|
|
91
|
+
---
|
|
92
|
+
|
|
93
|
+
{{{markdown}}}
|
|
94
|
+
|
|
95
|
+
\`\`\`mdx-code-block
|
|
96
|
+
import DocCardList from '@theme/DocCardList';
|
|
97
|
+
import {useCurrentSidebarCategory} from '@docusaurus/theme-common';
|
|
98
|
+
|
|
99
|
+
<DocCardList items={useCurrentSidebarCategory().items}/>
|
|
100
|
+
\`\`\`
|
|
79
101
|
`;
|
|
80
102
|
loadedApi.map(async (item) => {
|
|
81
103
|
const markdown = item.type === "api" ? (0, markdown_1.createApiPageMD)(item) : (0, markdown_1.createInfoPageMD)(item);
|
|
@@ -84,6 +106,7 @@ sidebar_class_name: "{{{api.method}}} api-method"
|
|
|
84
106
|
item.json = JSON.stringify(item.api);
|
|
85
107
|
}
|
|
86
108
|
const view = (0, mustache_1.render)(mdTemplate, item);
|
|
109
|
+
const utils = (0, mustache_1.render)(infoMdTemplate, item);
|
|
87
110
|
if (item.type === "api") {
|
|
88
111
|
if (!fs_1.default.existsSync(`${outputDir}/${item.id}.api.mdx`)) {
|
|
89
112
|
try {
|
|
@@ -97,13 +120,15 @@ sidebar_class_name: "{{{api.method}}} api-method"
|
|
|
97
120
|
}
|
|
98
121
|
// TODO: determine if we actually want/need this
|
|
99
122
|
if (item.type === "info") {
|
|
100
|
-
if (!fs_1.default.existsSync(`${outputDir}
|
|
123
|
+
if (!fs_1.default.existsSync(`${outputDir}/${item.id}.info.mdx`)) {
|
|
101
124
|
try {
|
|
102
|
-
|
|
103
|
-
|
|
125
|
+
(sidebarOptions === null || sidebarOptions === void 0 ? void 0 : sidebarOptions.useInfoAsCategoryLink)
|
|
126
|
+
? fs_1.default.writeFileSync(`${outputDir}/${item.id}.info.mdx`, utils, "utf8")
|
|
127
|
+
: fs_1.default.writeFileSync(`${outputDir}/${item.id}.info.mdx`, view, "utf8");
|
|
128
|
+
console.log(chalk_1.default.green(`Successfully created "${outputDir}/${item.id}.info.mdx"`));
|
|
104
129
|
}
|
|
105
130
|
catch (err) {
|
|
106
|
-
console.error(chalk_1.default.red(`Failed to write "${outputDir}
|
|
131
|
+
console.error(chalk_1.default.red(`Failed to write "${outputDir}/${item.id}.info.mdx"`), chalk_1.default.yellow(err));
|
|
107
132
|
}
|
|
108
133
|
}
|
|
109
134
|
}
|
|
@@ -119,7 +144,7 @@ sidebar_class_name: "{{{api.method}}} api-method"
|
|
|
119
144
|
async function cleanApiDocs(options) {
|
|
120
145
|
const { outputDir } = options;
|
|
121
146
|
const apiDir = path_1.default.join(siteDir, outputDir);
|
|
122
|
-
const apiMdxFiles = await (0, utils_1.Globby)(["*.api.mdx"], {
|
|
147
|
+
const apiMdxFiles = await (0, utils_1.Globby)(["*.api.mdx", "*.info.mdx"], {
|
|
123
148
|
cwd: path_1.default.resolve(apiDir),
|
|
124
149
|
});
|
|
125
150
|
const sidebarFile = await (0, utils_1.Globby)(["sidebar.js"], {
|
package/lib/openapi/openapi.d.ts
CHANGED
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
import { ApiMetadata } from "../types";
|
|
2
|
-
import { OpenApiObjectWithRef } from "./types";
|
|
2
|
+
import { OpenApiObjectWithRef, TagObject } from "./types";
|
|
3
3
|
interface OpenApiFiles {
|
|
4
4
|
source: string;
|
|
5
5
|
sourceDirName: string;
|
|
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[]>;
|
|
10
|
-
export declare function processOpenapiFile(openapiDataWithRefs: OpenApiObjectWithRef): Promise<ApiMetadata[]>;
|
|
9
|
+
export declare function processOpenapiFiles(files: OpenApiFiles[]): Promise<[ApiMetadata[], TagObject[]]>;
|
|
10
|
+
export declare function processOpenapiFile(openapiDataWithRefs: OpenApiObjectWithRef): Promise<[ApiMetadata[], TagObject[]]>;
|
|
11
|
+
export declare function getTagDisplayName(tagName: string, tags: TagObject[]): string;
|
|
11
12
|
export {};
|
package/lib/openapi/openapi.js
CHANGED
|
@@ -9,7 +9,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
9
9
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
10
10
|
};
|
|
11
11
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
12
|
-
exports.processOpenapiFile = exports.processOpenapiFiles = exports.readOpenapiFiles = void 0;
|
|
12
|
+
exports.getTagDisplayName = exports.processOpenapiFile = exports.processOpenapiFiles = exports.readOpenapiFiles = void 0;
|
|
13
13
|
const path_1 = __importDefault(require("path"));
|
|
14
14
|
const utils_1 = require("@docusaurus/utils");
|
|
15
15
|
const openapi_to_postmanv2_1 = __importDefault(require("@paloaltonetworks/openapi-to-postmanv2"));
|
|
@@ -64,22 +64,24 @@ async function createPostmanCollection(openapiData) {
|
|
|
64
64
|
return await jsonToCollection(data);
|
|
65
65
|
}
|
|
66
66
|
function createItems(openapiData) {
|
|
67
|
-
var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m;
|
|
67
|
+
var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o;
|
|
68
68
|
// TODO: Find a better way to handle this
|
|
69
69
|
let items = [];
|
|
70
70
|
// Only create an info page if we have a description.
|
|
71
71
|
if (openapiData.info.description) {
|
|
72
|
+
const infoId = (0, lodash_1.kebabCase)(openapiData.info.title);
|
|
72
73
|
const infoPage = {
|
|
73
74
|
type: "info",
|
|
74
|
-
id:
|
|
75
|
-
unversionedId:
|
|
76
|
-
title:
|
|
75
|
+
id: infoId,
|
|
76
|
+
unversionedId: infoId,
|
|
77
|
+
title: openapiData.info.title,
|
|
77
78
|
description: openapiData.info.description,
|
|
78
|
-
slug: "/
|
|
79
|
+
slug: "/" + infoId,
|
|
79
80
|
frontMatter: {},
|
|
80
81
|
info: {
|
|
81
82
|
...openapiData.info,
|
|
82
|
-
|
|
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",
|
|
83
85
|
},
|
|
84
86
|
};
|
|
85
87
|
items.push(infoPage);
|
|
@@ -87,16 +89,16 @@ function createItems(openapiData) {
|
|
|
87
89
|
for (let [path, pathObject] of Object.entries(openapiData.paths)) {
|
|
88
90
|
const { $ref, description, parameters, servers, summary, ...rest } = pathObject;
|
|
89
91
|
for (let [method, operationObject] of Object.entries({ ...rest })) {
|
|
90
|
-
const title = (
|
|
92
|
+
const title = (_d = (_c = operationObject.summary) !== null && _c !== void 0 ? _c : operationObject.operationId) !== null && _d !== void 0 ? _d : "Missing summary";
|
|
91
93
|
if (operationObject.description === undefined) {
|
|
92
94
|
operationObject.description =
|
|
93
|
-
(
|
|
95
|
+
(_f = (_e = operationObject.summary) !== null && _e !== void 0 ? _e : operationObject.operationId) !== null && _f !== void 0 ? _f : "";
|
|
94
96
|
}
|
|
95
97
|
const baseId = (0, lodash_1.kebabCase)(title);
|
|
96
|
-
const servers = (
|
|
97
|
-
const security = (
|
|
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;
|
|
98
100
|
// Add security schemes so we know how to handle security.
|
|
99
|
-
const securitySchemes = (
|
|
101
|
+
const securitySchemes = (_k = openapiData.components) === null || _k === void 0 ? void 0 : _k.securitySchemes;
|
|
100
102
|
// Make sure schemes are lowercase. See: https://github.com/cloud-annotations/docusaurus-plugin-openapi/issues/79
|
|
101
103
|
if (securitySchemes) {
|
|
102
104
|
for (let securityScheme of Object.values(securitySchemes)) {
|
|
@@ -106,7 +108,7 @@ function createItems(openapiData) {
|
|
|
106
108
|
}
|
|
107
109
|
}
|
|
108
110
|
let jsonRequestBodyExample;
|
|
109
|
-
const body = (
|
|
111
|
+
const body = (_m = (_l = operationObject.requestBody) === null || _l === void 0 ? void 0 : _l.content) === null || _m === void 0 ? void 0 : _m["application/json"];
|
|
110
112
|
if (body === null || body === void 0 ? void 0 : body.schema) {
|
|
111
113
|
jsonRequestBodyExample = (0, createExample_1.sampleFromSchema)(body.schema);
|
|
112
114
|
}
|
|
@@ -122,7 +124,7 @@ function createItems(openapiData) {
|
|
|
122
124
|
frontMatter: {},
|
|
123
125
|
api: {
|
|
124
126
|
...defaults,
|
|
125
|
-
tags: (
|
|
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 : []); }),
|
|
126
128
|
method,
|
|
127
129
|
path,
|
|
128
130
|
servers,
|
|
@@ -192,14 +194,23 @@ async function readOpenapiFiles(openapiPath, _options) {
|
|
|
192
194
|
exports.readOpenapiFiles = readOpenapiFiles;
|
|
193
195
|
async function processOpenapiFiles(files) {
|
|
194
196
|
const promises = files.map(async (file) => {
|
|
195
|
-
const
|
|
196
|
-
|
|
197
|
+
const processedFile = await processOpenapiFile(file.data);
|
|
198
|
+
const itemsObjectsArray = processedFile[0].map((item) => ({
|
|
197
199
|
...item,
|
|
198
200
|
}));
|
|
201
|
+
const tags = processedFile[1];
|
|
202
|
+
return [itemsObjectsArray, tags];
|
|
199
203
|
});
|
|
200
204
|
const metadata = await Promise.all(promises);
|
|
201
|
-
const items = metadata
|
|
202
|
-
|
|
205
|
+
const items = metadata
|
|
206
|
+
.map(function (x) {
|
|
207
|
+
return x[0];
|
|
208
|
+
})
|
|
209
|
+
.flat();
|
|
210
|
+
const tags = metadata.map(function (x) {
|
|
211
|
+
return x[1];
|
|
212
|
+
});
|
|
213
|
+
return [items, tags];
|
|
203
214
|
}
|
|
204
215
|
exports.processOpenapiFiles = processOpenapiFiles;
|
|
205
216
|
async function processOpenapiFile(openapiDataWithRefs) {
|
|
@@ -207,7 +218,11 @@ async function processOpenapiFile(openapiDataWithRefs) {
|
|
|
207
218
|
const postmanCollection = await createPostmanCollection(openapiData);
|
|
208
219
|
const items = createItems(openapiData);
|
|
209
220
|
bindCollectionToApiItems(items, postmanCollection);
|
|
210
|
-
|
|
221
|
+
let tags = [];
|
|
222
|
+
if (openapiData.tags !== undefined) {
|
|
223
|
+
tags = openapiData.tags;
|
|
224
|
+
}
|
|
225
|
+
return [items, tags];
|
|
211
226
|
}
|
|
212
227
|
exports.processOpenapiFile = processOpenapiFile;
|
|
213
228
|
// order for picking items as a display name of tags
|
|
@@ -229,3 +244,4 @@ function getTagDisplayName(tagName, tags) {
|
|
|
229
244
|
// always default to the tagName
|
|
230
245
|
return tagName;
|
|
231
246
|
}
|
|
247
|
+
exports.getTagDisplayName = getTagDisplayName;
|
package/lib/openapi/types.d.ts
CHANGED
|
@@ -29,6 +29,7 @@ export interface InfoObject {
|
|
|
29
29
|
contact?: ContactObject;
|
|
30
30
|
license?: LicenseObject;
|
|
31
31
|
version: string;
|
|
32
|
+
tags?: String[];
|
|
32
33
|
}
|
|
33
34
|
export interface ContactObject {
|
|
34
35
|
name?: string;
|
|
@@ -237,7 +238,7 @@ export interface LinkObject {
|
|
|
237
238
|
export declare type HeaderObject = Omit<ParameterObject, "name" | "in">;
|
|
238
239
|
export declare type HeaderObjectWithRef = Omit<ParameterObjectWithRef, "name" | "in">;
|
|
239
240
|
export interface TagObject {
|
|
240
|
-
name
|
|
241
|
+
name?: string;
|
|
241
242
|
description?: string;
|
|
242
243
|
externalDocs?: ExternalDocumentationObject;
|
|
243
244
|
"x-displayName"?: string;
|
package/lib/sidebars/index.d.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
1
|
import { ProcessedSidebar } from "@docusaurus/plugin-content-docs/src/sidebars/types";
|
|
2
|
+
import { TagObject } from "../openapi/types";
|
|
2
3
|
import type { SidebarOptions, APIOptions, ApiMetadata } from "../types";
|
|
3
|
-
export default function generateSidebarSlice(sidebarOptions: SidebarOptions, options: APIOptions, api: ApiMetadata[]): ProcessedSidebar;
|
|
4
|
+
export default function generateSidebarSlice(sidebarOptions: SidebarOptions, options: APIOptions, api: ApiMetadata[], tags: TagObject[]): ProcessedSidebar;
|
package/lib/sidebars/index.js
CHANGED
|
@@ -10,25 +10,30 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
10
10
|
};
|
|
11
11
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
12
12
|
const clsx_1 = __importDefault(require("clsx"));
|
|
13
|
+
const lodash_1 = require("lodash");
|
|
13
14
|
const uniq_1 = __importDefault(require("lodash/uniq"));
|
|
14
15
|
function isApiItem(item) {
|
|
15
16
|
return item.type === "api";
|
|
16
17
|
}
|
|
17
|
-
function
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
// type: "link" as const,
|
|
22
|
-
// label: item.title,
|
|
23
|
-
// href: item.permalink,
|
|
24
|
-
// docId: item.id,
|
|
25
|
-
// };
|
|
26
|
-
// });
|
|
18
|
+
function isInfoItem(item) {
|
|
19
|
+
return item.type === "info";
|
|
20
|
+
}
|
|
21
|
+
function groupByTags(items, sidebarOptions, options, tags) {
|
|
27
22
|
const { outputDir } = options;
|
|
28
|
-
const { sidebarCollapsed, sidebarCollapsible, customProps } = sidebarOptions;
|
|
23
|
+
const { sidebarCollapsed, sidebarCollapsible, customProps, categoryLinkSource, } = sidebarOptions;
|
|
24
|
+
const linkSource = categoryLinkSource !== null && categoryLinkSource !== void 0 ? categoryLinkSource : "tag";
|
|
29
25
|
const apiItems = items.filter(isApiItem);
|
|
26
|
+
const infoItems = items.filter(isInfoItem);
|
|
27
|
+
const intros = infoItems.map((item) => {
|
|
28
|
+
return {
|
|
29
|
+
id: item.id,
|
|
30
|
+
title: item.title,
|
|
31
|
+
description: item.description,
|
|
32
|
+
tags: item.info.tags,
|
|
33
|
+
};
|
|
34
|
+
});
|
|
30
35
|
// TODO: make sure we only take the first tag
|
|
31
|
-
const
|
|
36
|
+
const tags_ = (0, uniq_1.default)(apiItems
|
|
32
37
|
.flatMap((item) => item.api.tags)
|
|
33
38
|
.filter((item) => !!item));
|
|
34
39
|
// TODO: optimize this or make it a function
|
|
@@ -51,11 +56,47 @@ function groupByTags(items, sidebarOptions, options) {
|
|
|
51
56
|
}, item.api.method),
|
|
52
57
|
};
|
|
53
58
|
}
|
|
54
|
-
|
|
59
|
+
let introDoc = undefined;
|
|
60
|
+
if (linkSource === "info") {
|
|
61
|
+
const infoItem = infoItems[0];
|
|
62
|
+
const id = infoItem.id;
|
|
63
|
+
introDoc = {
|
|
64
|
+
type: "doc",
|
|
65
|
+
id: `${basePath}/${id}`,
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
const tagged = tags_
|
|
55
69
|
.map((tag) => {
|
|
70
|
+
// TODO: should we also use the info.title as generated-index title?
|
|
71
|
+
const infoObject = intros.find((i) => i.tags.includes(tag));
|
|
72
|
+
const tagObject = tags.flat().find((t) => {
|
|
73
|
+
var _a;
|
|
74
|
+
return (_a = (tag === t.name || tag === t["x-displayName"])) !== null && _a !== void 0 ? _a : {
|
|
75
|
+
name: tag,
|
|
76
|
+
description: `${tag} Index`,
|
|
77
|
+
};
|
|
78
|
+
});
|
|
79
|
+
// TODO: perhaps move all this into a getLinkConfig() function
|
|
80
|
+
let linkConfig = undefined;
|
|
81
|
+
if (infoObject !== undefined && linkSource === "info") {
|
|
82
|
+
linkConfig = {
|
|
83
|
+
type: "doc",
|
|
84
|
+
id: `${basePath}/${infoObject.id}`,
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
if (tagObject !== undefined && linkSource === "tag") {
|
|
88
|
+
const linkDescription = tagObject === null || tagObject === void 0 ? void 0 : tagObject.description;
|
|
89
|
+
linkConfig = {
|
|
90
|
+
type: "generated-index",
|
|
91
|
+
title: tag,
|
|
92
|
+
description: linkDescription,
|
|
93
|
+
slug: "/category/" + (0, lodash_1.kebabCase)(tag),
|
|
94
|
+
};
|
|
95
|
+
}
|
|
56
96
|
return {
|
|
57
97
|
type: "category",
|
|
58
98
|
label: tag,
|
|
99
|
+
link: linkConfig,
|
|
59
100
|
collapsible: sidebarCollapsible,
|
|
60
101
|
collapsed: sidebarCollapsed,
|
|
61
102
|
items: apiItems
|
|
@@ -64,25 +105,28 @@ function groupByTags(items, sidebarOptions, options) {
|
|
|
64
105
|
};
|
|
65
106
|
})
|
|
66
107
|
.filter((item) => item.items.length > 0); // Filter out any categories with no items.
|
|
108
|
+
// TODO: determine how we want to handle these
|
|
67
109
|
// const untagged = [
|
|
68
|
-
// // TODO: determine if needed and how
|
|
69
110
|
// {
|
|
70
111
|
// type: "category" as const,
|
|
71
112
|
// label: "UNTAGGED",
|
|
72
|
-
//
|
|
73
|
-
//
|
|
113
|
+
// collapsible: sidebarCollapsible,
|
|
114
|
+
// collapsed: sidebarCollapsed,
|
|
74
115
|
// items: apiItems
|
|
75
|
-
// //@ts-ignore
|
|
76
116
|
// .filter(({ api }) => api.tags === undefined || api.tags.length === 0)
|
|
77
117
|
// .map(createDocItem),
|
|
78
118
|
// },
|
|
79
119
|
// ];
|
|
120
|
+
// Shift intro doc to top of sidebar
|
|
121
|
+
if (introDoc && linkSource === "info") {
|
|
122
|
+
tagged.unshift(introDoc);
|
|
123
|
+
}
|
|
80
124
|
return [...tagged];
|
|
81
125
|
}
|
|
82
|
-
function generateSidebarSlice(sidebarOptions, options, api) {
|
|
126
|
+
function generateSidebarSlice(sidebarOptions, options, api, tags) {
|
|
83
127
|
let sidebarSlice = [];
|
|
84
128
|
if (sidebarOptions.groupPathsBy === "tags") {
|
|
85
|
-
sidebarSlice = groupByTags(api, sidebarOptions, options);
|
|
129
|
+
sidebarSlice = groupByTags(api, sidebarOptions, options, tags);
|
|
86
130
|
}
|
|
87
131
|
return sidebarSlice;
|
|
88
132
|
}
|
package/lib/types.d.ts
CHANGED
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-352",
|
|
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": "dc9b85400c4cf3f675641234bf38c398b5ba9dd0"
|
|
64
64
|
}
|
package/src/index.ts
CHANGED
|
@@ -32,8 +32,7 @@ export default function pluginOpenAPI(
|
|
|
32
32
|
|
|
33
33
|
try {
|
|
34
34
|
const openapiFiles = await readOpenapiFiles(contentPath, {});
|
|
35
|
-
const loadedApi = await processOpenapiFiles(openapiFiles);
|
|
36
|
-
|
|
35
|
+
const [loadedApi, tags] = await processOpenapiFiles(openapiFiles);
|
|
37
36
|
if (!fs.existsSync(outputDir)) {
|
|
38
37
|
try {
|
|
39
38
|
fs.mkdirSync(outputDir, { recursive: true });
|
|
@@ -51,7 +50,8 @@ export default function pluginOpenAPI(
|
|
|
51
50
|
const sidebarSlice = generateSidebarSlice(
|
|
52
51
|
sidebarOptions!, // TODO: find a better way to handle null
|
|
53
52
|
options,
|
|
54
|
-
loadedApi
|
|
53
|
+
loadedApi,
|
|
54
|
+
tags
|
|
55
55
|
);
|
|
56
56
|
|
|
57
57
|
const sidebarSliceTemplate = template
|
|
@@ -81,7 +81,12 @@ export default function pluginOpenAPI(
|
|
|
81
81
|
? fs.readFileSync(template).toString()
|
|
82
82
|
: `---
|
|
83
83
|
id: {{{id}}}
|
|
84
|
+
{{^api}}
|
|
85
|
+
sidebar_label: Introduction
|
|
86
|
+
{{/api}}
|
|
87
|
+
{{#api}}
|
|
84
88
|
sidebar_label: {{{title}}}
|
|
89
|
+
{{/api}}
|
|
85
90
|
{{^api}}
|
|
86
91
|
sidebar_position: 0
|
|
87
92
|
{{/api}}
|
|
@@ -100,6 +105,24 @@ sidebar_class_name: "{{{api.method}}} api-method"
|
|
|
100
105
|
{{{markdown}}}
|
|
101
106
|
`;
|
|
102
107
|
|
|
108
|
+
const infoMdTemplate = template
|
|
109
|
+
? fs.readFileSync(template).toString()
|
|
110
|
+
: `---
|
|
111
|
+
id: {{{id}}}
|
|
112
|
+
sidebar_label: {{{title}}}
|
|
113
|
+
hide_title: true
|
|
114
|
+
---
|
|
115
|
+
|
|
116
|
+
{{{markdown}}}
|
|
117
|
+
|
|
118
|
+
\`\`\`mdx-code-block
|
|
119
|
+
import DocCardList from '@theme/DocCardList';
|
|
120
|
+
import {useCurrentSidebarCategory} from '@docusaurus/theme-common';
|
|
121
|
+
|
|
122
|
+
<DocCardList items={useCurrentSidebarCategory().items}/>
|
|
123
|
+
\`\`\`
|
|
124
|
+
`;
|
|
125
|
+
|
|
103
126
|
loadedApi.map(async (item) => {
|
|
104
127
|
const markdown =
|
|
105
128
|
item.type === "api" ? createApiPageMD(item) : createInfoPageMD(item);
|
|
@@ -108,6 +131,7 @@ sidebar_class_name: "{{{api.method}}} api-method"
|
|
|
108
131
|
item.json = JSON.stringify(item.api);
|
|
109
132
|
}
|
|
110
133
|
const view = render(mdTemplate, item);
|
|
134
|
+
const utils = render(infoMdTemplate, item);
|
|
111
135
|
|
|
112
136
|
if (item.type === "api") {
|
|
113
137
|
if (!fs.existsSync(`${outputDir}/${item.id}.api.mdx`)) {
|
|
@@ -129,15 +153,27 @@ sidebar_class_name: "{{{api.method}}} api-method"
|
|
|
129
153
|
|
|
130
154
|
// TODO: determine if we actually want/need this
|
|
131
155
|
if (item.type === "info") {
|
|
132
|
-
if (!fs.existsSync(`${outputDir}
|
|
156
|
+
if (!fs.existsSync(`${outputDir}/${item.id}.info.mdx`)) {
|
|
133
157
|
try {
|
|
134
|
-
|
|
158
|
+
sidebarOptions?.useInfoAsCategoryLink
|
|
159
|
+
? fs.writeFileSync(
|
|
160
|
+
`${outputDir}/${item.id}.info.mdx`,
|
|
161
|
+
utils,
|
|
162
|
+
"utf8"
|
|
163
|
+
)
|
|
164
|
+
: fs.writeFileSync(
|
|
165
|
+
`${outputDir}/${item.id}.info.mdx`,
|
|
166
|
+
view,
|
|
167
|
+
"utf8"
|
|
168
|
+
);
|
|
135
169
|
console.log(
|
|
136
|
-
chalk.green(
|
|
170
|
+
chalk.green(
|
|
171
|
+
`Successfully created "${outputDir}/${item.id}.info.mdx"`
|
|
172
|
+
)
|
|
137
173
|
);
|
|
138
174
|
} catch (err) {
|
|
139
175
|
console.error(
|
|
140
|
-
chalk.red(`Failed to write "${outputDir}
|
|
176
|
+
chalk.red(`Failed to write "${outputDir}/${item.id}.info.mdx"`),
|
|
141
177
|
chalk.yellow(err)
|
|
142
178
|
);
|
|
143
179
|
}
|
|
@@ -155,7 +191,7 @@ sidebar_class_name: "{{{api.method}}} api-method"
|
|
|
155
191
|
async function cleanApiDocs(options: APIOptions) {
|
|
156
192
|
const { outputDir } = options;
|
|
157
193
|
const apiDir = path.join(siteDir, outputDir);
|
|
158
|
-
const apiMdxFiles = await Globby(["*.api.mdx"], {
|
|
194
|
+
const apiMdxFiles = await Globby(["*.api.mdx", "*.info.mdx"], {
|
|
159
195
|
cwd: path.resolve(apiDir),
|
|
160
196
|
});
|
|
161
197
|
const sidebarFile = await Globby(["sidebar.js"], {
|
package/src/openapi/openapi.ts
CHANGED
|
@@ -81,16 +81,20 @@ function createItems(openapiData: OpenApiObject): ApiMetadata[] {
|
|
|
81
81
|
|
|
82
82
|
// Only create an info page if we have a description.
|
|
83
83
|
if (openapiData.info.description) {
|
|
84
|
+
const infoId = kebabCase(openapiData.info.title);
|
|
84
85
|
const infoPage: PartialPage<InfoPageMetadata> = {
|
|
85
86
|
type: "info",
|
|
86
|
-
id:
|
|
87
|
-
unversionedId:
|
|
88
|
-
title:
|
|
87
|
+
id: infoId,
|
|
88
|
+
unversionedId: infoId,
|
|
89
|
+
title: openapiData.info.title,
|
|
89
90
|
description: openapiData.info.description,
|
|
90
|
-
slug: "/
|
|
91
|
+
slug: "/" + infoId,
|
|
91
92
|
frontMatter: {},
|
|
92
93
|
info: {
|
|
93
94
|
...openapiData.info,
|
|
95
|
+
tags: openapiData.tags?.map((tagName) =>
|
|
96
|
+
getTagDisplayName(tagName.name!, openapiData.tags ?? [])
|
|
97
|
+
),
|
|
94
98
|
title: openapiData.info.title ?? "Introduction",
|
|
95
99
|
},
|
|
96
100
|
};
|
|
@@ -245,34 +249,47 @@ export async function readOpenapiFiles(
|
|
|
245
249
|
|
|
246
250
|
export async function processOpenapiFiles(
|
|
247
251
|
files: OpenApiFiles[]
|
|
248
|
-
): Promise<ApiMetadata[]> {
|
|
252
|
+
): Promise<[ApiMetadata[], TagObject[]]> {
|
|
249
253
|
const promises = files.map(async (file) => {
|
|
250
|
-
const
|
|
251
|
-
|
|
254
|
+
const processedFile = await processOpenapiFile(file.data);
|
|
255
|
+
const itemsObjectsArray = processedFile[0].map((item) => ({
|
|
252
256
|
...item,
|
|
253
257
|
}));
|
|
258
|
+
const tags = processedFile[1];
|
|
259
|
+
return [itemsObjectsArray, tags];
|
|
254
260
|
});
|
|
255
261
|
const metadata = await Promise.all(promises);
|
|
256
|
-
const items = metadata
|
|
257
|
-
|
|
262
|
+
const items = metadata
|
|
263
|
+
.map(function (x) {
|
|
264
|
+
return x[0];
|
|
265
|
+
})
|
|
266
|
+
.flat();
|
|
267
|
+
const tags = metadata.map(function (x) {
|
|
268
|
+
return x[1];
|
|
269
|
+
});
|
|
270
|
+
return [items as ApiMetadata[], tags as TagObject[]];
|
|
258
271
|
}
|
|
259
272
|
|
|
260
273
|
export async function processOpenapiFile(
|
|
261
274
|
openapiDataWithRefs: OpenApiObjectWithRef
|
|
262
|
-
): Promise<ApiMetadata[]> {
|
|
275
|
+
): Promise<[ApiMetadata[], TagObject[]]> {
|
|
263
276
|
const openapiData = await resolveRefs(openapiDataWithRefs);
|
|
264
277
|
const postmanCollection = await createPostmanCollection(openapiData);
|
|
265
278
|
const items = createItems(openapiData);
|
|
266
279
|
|
|
267
280
|
bindCollectionToApiItems(items, postmanCollection);
|
|
268
281
|
|
|
269
|
-
|
|
282
|
+
let tags: TagObject[] = [];
|
|
283
|
+
if (openapiData.tags !== undefined) {
|
|
284
|
+
tags = openapiData.tags;
|
|
285
|
+
}
|
|
286
|
+
return [items, tags];
|
|
270
287
|
}
|
|
271
288
|
|
|
272
289
|
// order for picking items as a display name of tags
|
|
273
290
|
const tagDisplayNameProperties = ["x-displayName", "name"] as const;
|
|
274
291
|
|
|
275
|
-
function getTagDisplayName(tagName: string, tags: TagObject[]): string {
|
|
292
|
+
export function getTagDisplayName(tagName: string, tags: TagObject[]): string {
|
|
276
293
|
// find the very own tagObject
|
|
277
294
|
const tagObject = tags.find((tagObject) => tagObject.name === tagName) ?? {
|
|
278
295
|
// if none found, just fake one
|
package/src/openapi/types.ts
CHANGED
|
@@ -40,6 +40,7 @@ export interface InfoObject {
|
|
|
40
40
|
contact?: ContactObject;
|
|
41
41
|
license?: LicenseObject;
|
|
42
42
|
version: string;
|
|
43
|
+
tags?: String[];
|
|
43
44
|
}
|
|
44
45
|
|
|
45
46
|
export interface ContactObject {
|
|
@@ -294,7 +295,7 @@ export type HeaderObject = Omit<ParameterObject, "name" | "in">;
|
|
|
294
295
|
export type HeaderObjectWithRef = Omit<ParameterObjectWithRef, "name" | "in">;
|
|
295
296
|
|
|
296
297
|
export interface TagObject {
|
|
297
|
-
name
|
|
298
|
+
name?: string;
|
|
298
299
|
description?: string;
|
|
299
300
|
externalDocs?: ExternalDocumentationObject;
|
|
300
301
|
"x-displayName"?: string;
|
package/src/sidebars/index.ts
CHANGED
|
@@ -7,11 +7,14 @@
|
|
|
7
7
|
|
|
8
8
|
import {
|
|
9
9
|
ProcessedSidebar,
|
|
10
|
+
SidebarItemCategoryLinkConfig,
|
|
10
11
|
SidebarItemDoc,
|
|
11
12
|
} from "@docusaurus/plugin-content-docs/src/sidebars/types";
|
|
12
13
|
import clsx from "clsx";
|
|
14
|
+
import { kebabCase } from "lodash";
|
|
13
15
|
import uniq from "lodash/uniq";
|
|
14
16
|
|
|
17
|
+
import { TagObject } from "../openapi/types";
|
|
15
18
|
import type {
|
|
16
19
|
SidebarOptions,
|
|
17
20
|
APIOptions,
|
|
@@ -23,28 +26,38 @@ function isApiItem(item: ApiMetadata): item is ApiMetadata {
|
|
|
23
26
|
return item.type === "api";
|
|
24
27
|
}
|
|
25
28
|
|
|
29
|
+
function isInfoItem(item: ApiMetadata): item is ApiMetadata {
|
|
30
|
+
return item.type === "info";
|
|
31
|
+
}
|
|
32
|
+
|
|
26
33
|
function groupByTags(
|
|
27
34
|
items: ApiPageMetadata[],
|
|
28
35
|
sidebarOptions: SidebarOptions,
|
|
29
|
-
options: APIOptions
|
|
36
|
+
options: APIOptions,
|
|
37
|
+
tags: TagObject[]
|
|
30
38
|
): ProcessedSidebar {
|
|
31
|
-
// TODO: Figure out how to handle these
|
|
32
|
-
// const intros = items.filter(isInfoItem).map((item) => {
|
|
33
|
-
// return {
|
|
34
|
-
// type: "link" as const,
|
|
35
|
-
// label: item.title,
|
|
36
|
-
// href: item.permalink,
|
|
37
|
-
// docId: item.id,
|
|
38
|
-
// };
|
|
39
|
-
// });
|
|
40
|
-
|
|
41
39
|
const { outputDir } = options;
|
|
42
|
-
const {
|
|
40
|
+
const {
|
|
41
|
+
sidebarCollapsed,
|
|
42
|
+
sidebarCollapsible,
|
|
43
|
+
customProps,
|
|
44
|
+
categoryLinkSource,
|
|
45
|
+
} = sidebarOptions;
|
|
46
|
+
const linkSource = categoryLinkSource ?? "tag";
|
|
43
47
|
|
|
44
48
|
const apiItems = items.filter(isApiItem);
|
|
49
|
+
const infoItems = items.filter(isInfoItem);
|
|
50
|
+
const intros = infoItems.map((item: any) => {
|
|
51
|
+
return {
|
|
52
|
+
id: item.id,
|
|
53
|
+
title: item.title,
|
|
54
|
+
description: item.description,
|
|
55
|
+
tags: item.info.tags,
|
|
56
|
+
};
|
|
57
|
+
});
|
|
45
58
|
|
|
46
59
|
// TODO: make sure we only take the first tag
|
|
47
|
-
const
|
|
60
|
+
const tags_ = uniq(
|
|
48
61
|
apiItems
|
|
49
62
|
.flatMap((item) => item.api.tags)
|
|
50
63
|
.filter((item): item is string => !!item)
|
|
@@ -74,11 +87,51 @@ function groupByTags(
|
|
|
74
87
|
};
|
|
75
88
|
}
|
|
76
89
|
|
|
77
|
-
|
|
90
|
+
let introDoc = undefined;
|
|
91
|
+
if (linkSource === "info") {
|
|
92
|
+
const infoItem = infoItems[0];
|
|
93
|
+
const id = infoItem.id;
|
|
94
|
+
introDoc = {
|
|
95
|
+
type: "doc" as const,
|
|
96
|
+
id: `${basePath}/${id}`,
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
const tagged = tags_
|
|
78
101
|
.map((tag) => {
|
|
102
|
+
// TODO: should we also use the info.title as generated-index title?
|
|
103
|
+
const infoObject = intros.find((i) => i.tags.includes(tag));
|
|
104
|
+
const tagObject = tags.flat().find(
|
|
105
|
+
(t) =>
|
|
106
|
+
(tag === t.name || tag === t["x-displayName"]) ?? {
|
|
107
|
+
name: tag,
|
|
108
|
+
description: `${tag} Index`,
|
|
109
|
+
}
|
|
110
|
+
);
|
|
111
|
+
|
|
112
|
+
// TODO: perhaps move all this into a getLinkConfig() function
|
|
113
|
+
let linkConfig = undefined;
|
|
114
|
+
if (infoObject !== undefined && linkSource === "info") {
|
|
115
|
+
linkConfig = {
|
|
116
|
+
type: "doc",
|
|
117
|
+
id: `${basePath}/${infoObject.id}`,
|
|
118
|
+
} as SidebarItemCategoryLinkConfig;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
if (tagObject !== undefined && linkSource === "tag") {
|
|
122
|
+
const linkDescription = tagObject?.description;
|
|
123
|
+
linkConfig = {
|
|
124
|
+
type: "generated-index" as "generated-index",
|
|
125
|
+
title: tag,
|
|
126
|
+
description: linkDescription,
|
|
127
|
+
slug: "/category/" + kebabCase(tag),
|
|
128
|
+
} as SidebarItemCategoryLinkConfig;
|
|
129
|
+
}
|
|
130
|
+
|
|
79
131
|
return {
|
|
80
132
|
type: "category" as const,
|
|
81
133
|
label: tag,
|
|
134
|
+
link: linkConfig,
|
|
82
135
|
collapsible: sidebarCollapsible,
|
|
83
136
|
collapsed: sidebarCollapsed,
|
|
84
137
|
items: apiItems
|
|
@@ -88,33 +141,40 @@ function groupByTags(
|
|
|
88
141
|
})
|
|
89
142
|
.filter((item) => item.items.length > 0); // Filter out any categories with no items.
|
|
90
143
|
|
|
144
|
+
// TODO: determine how we want to handle these
|
|
91
145
|
// const untagged = [
|
|
92
|
-
// // TODO: determine if needed and how
|
|
93
146
|
// {
|
|
94
147
|
// type: "category" as const,
|
|
95
148
|
// label: "UNTAGGED",
|
|
96
|
-
//
|
|
97
|
-
//
|
|
149
|
+
// collapsible: sidebarCollapsible,
|
|
150
|
+
// collapsed: sidebarCollapsed,
|
|
98
151
|
// items: apiItems
|
|
99
|
-
// //@ts-ignore
|
|
100
152
|
// .filter(({ api }) => api.tags === undefined || api.tags.length === 0)
|
|
101
153
|
// .map(createDocItem),
|
|
102
154
|
// },
|
|
103
155
|
// ];
|
|
156
|
+
|
|
157
|
+
// Shift intro doc to top of sidebar
|
|
158
|
+
if (introDoc && linkSource === "info") {
|
|
159
|
+
tagged.unshift(introDoc as any);
|
|
160
|
+
}
|
|
161
|
+
|
|
104
162
|
return [...tagged];
|
|
105
163
|
}
|
|
106
164
|
|
|
107
165
|
export default function generateSidebarSlice(
|
|
108
166
|
sidebarOptions: SidebarOptions,
|
|
109
167
|
options: APIOptions,
|
|
110
|
-
api: ApiMetadata[]
|
|
168
|
+
api: ApiMetadata[],
|
|
169
|
+
tags: TagObject[]
|
|
111
170
|
) {
|
|
112
171
|
let sidebarSlice: ProcessedSidebar = [];
|
|
113
172
|
if (sidebarOptions.groupPathsBy === "tags") {
|
|
114
173
|
sidebarSlice = groupByTags(
|
|
115
174
|
api as ApiPageMetadata[],
|
|
116
175
|
sidebarOptions,
|
|
117
|
-
options
|
|
176
|
+
options,
|
|
177
|
+
tags
|
|
118
178
|
);
|
|
119
179
|
}
|
|
120
180
|
return sidebarSlice;
|
package/src/types.ts
CHANGED
|
@@ -90,6 +90,8 @@ export interface ApiNavLink {
|
|
|
90
90
|
|
|
91
91
|
export interface SidebarOptions {
|
|
92
92
|
groupPathsBy?: string;
|
|
93
|
+
useInfoAsCategoryLink?: boolean; // TODO: confirm name of option
|
|
94
|
+
categoryLinkSource?: string;
|
|
93
95
|
customProps?: { [key: string]: unknown };
|
|
94
96
|
sidebarCollapsible?: boolean;
|
|
95
97
|
sidebarCollapsed?: boolean;
|