docusaurus-plugin-openapi-docs 0.0.0-1110 → 0.0.0-1151
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/openapi/utils/loadAndResolveSpec.js +5 -4
- package/lib/sidebars/index.js +22 -5
- package/lib/sidebars/index.test.d.ts +1 -0
- package/lib/sidebars/index.test.js +68 -0
- package/package.json +5 -5
- package/src/openapi/utils/loadAndResolveSpec.ts +8 -6
- package/src/sidebars/index.test.ts +94 -0
- package/src/sidebars/index.ts +26 -7
|
@@ -104,7 +104,7 @@ async function resolveJsonRefs(specUrlOrObject) {
|
|
|
104
104
|
}
|
|
105
105
|
}
|
|
106
106
|
async function loadAndResolveSpec(specUrlOrObject) {
|
|
107
|
-
const config =
|
|
107
|
+
const config = await (0, openapi_core_1.createConfig)({});
|
|
108
108
|
const bundleOpts = {
|
|
109
109
|
config,
|
|
110
110
|
base: process.cwd(),
|
|
@@ -121,9 +121,10 @@ async function loadAndResolveSpec(specUrlOrObject) {
|
|
|
121
121
|
// Force dereference ?
|
|
122
122
|
// bundleOpts["dereference"] = true;
|
|
123
123
|
const { bundle: { parsed }, } = await (0, openapi_core_1.bundle)(bundleOpts);
|
|
124
|
+
const parsedSpec = parsed;
|
|
124
125
|
//Pre-processing before resolving JSON refs
|
|
125
|
-
if (
|
|
126
|
-
for (let [component, type] of Object.entries(
|
|
126
|
+
if (parsedSpec.components) {
|
|
127
|
+
for (let [component, type] of Object.entries(parsedSpec.components)) {
|
|
127
128
|
if (component === "schemas") {
|
|
128
129
|
for (let [schemaKey, schemaValue] of Object.entries(type)) {
|
|
129
130
|
const title = schemaValue["title"];
|
|
@@ -134,7 +135,7 @@ async function loadAndResolveSpec(specUrlOrObject) {
|
|
|
134
135
|
}
|
|
135
136
|
}
|
|
136
137
|
}
|
|
137
|
-
const resolved = await resolveJsonRefs(
|
|
138
|
+
const resolved = await resolveJsonRefs(parsedSpec);
|
|
138
139
|
// Force serialization and replace circular $ref pointers
|
|
139
140
|
// @ts-ignore
|
|
140
141
|
const serialized = JSON.stringify(resolved, serializer());
|
package/lib/sidebars/index.js
CHANGED
|
@@ -45,7 +45,7 @@ const createDocItem = (item, { sidebarOptions: { customProps }, basePath }) => {
|
|
|
45
45
|
className: className ? className : undefined,
|
|
46
46
|
};
|
|
47
47
|
};
|
|
48
|
-
function groupByTags(items, sidebarOptions, options, tags, docPath) {
|
|
48
|
+
function groupByTags(items, sidebarOptions, options, tags, docPath, tagGroupKey) {
|
|
49
49
|
var _a, _b;
|
|
50
50
|
let { outputDir, label, showSchemas } = options;
|
|
51
51
|
// Remove trailing slash before proceeding
|
|
@@ -102,9 +102,11 @@ function groupByTags(items, sidebarOptions, options, tags, docPath) {
|
|
|
102
102
|
if (infoItems.length === 1) {
|
|
103
103
|
const infoItem = infoItems[0];
|
|
104
104
|
const id = infoItem.id;
|
|
105
|
+
const docId = basePath === "" || undefined ? `${id}` : `${basePath}/${id}`;
|
|
105
106
|
rootIntroDoc = {
|
|
106
107
|
type: "doc",
|
|
107
|
-
id:
|
|
108
|
+
id: docId,
|
|
109
|
+
...(tagGroupKey && { key: (0, lodash_1.kebabCase)(`${tagGroupKey}-${docId}`) }),
|
|
108
110
|
};
|
|
109
111
|
}
|
|
110
112
|
const tagged = apiTags
|
|
@@ -146,13 +148,27 @@ function groupByTags(items, sidebarOptions, options, tags, docPath) {
|
|
|
146
148
|
}
|
|
147
149
|
const taggedApiItems = apiItems.filter((item) => { var _a; return !!((_a = item.api.tags) === null || _a === void 0 ? void 0 : _a.includes(tag)); });
|
|
148
150
|
const taggedSchemaItems = schemaItems.filter((item) => { var _a; return !!((_a = item.schema["x-tags"]) === null || _a === void 0 ? void 0 : _a.includes(tag)); });
|
|
151
|
+
const categoryLabel = (_a = tagObject === null || tagObject === void 0 ? void 0 : tagObject["x-displayName"]) !== null && _a !== void 0 ? _a : tag;
|
|
152
|
+
const categoryKey = tagGroupKey
|
|
153
|
+
? (0, lodash_1.kebabCase)(`${tagGroupKey}-${categoryLabel}`)
|
|
154
|
+
: undefined;
|
|
149
155
|
return {
|
|
150
156
|
type: "category",
|
|
151
|
-
label:
|
|
157
|
+
label: categoryLabel,
|
|
158
|
+
...(categoryKey && { key: categoryKey }),
|
|
152
159
|
link: linkConfig,
|
|
153
160
|
collapsible: sidebarCollapsible,
|
|
154
161
|
collapsed: sidebarCollapsed,
|
|
155
|
-
items: [...taggedSchemaItems, ...taggedApiItems].map((item) =>
|
|
162
|
+
items: [...taggedSchemaItems, ...taggedApiItems].map((item) => {
|
|
163
|
+
const docItem = createDocItemFn(item, createDocItemFnContext);
|
|
164
|
+
if (tagGroupKey && docItem.type === "doc") {
|
|
165
|
+
return {
|
|
166
|
+
...docItem,
|
|
167
|
+
key: (0, lodash_1.kebabCase)(`${tagGroupKey}-${tag}-${docItem.id}`),
|
|
168
|
+
};
|
|
169
|
+
}
|
|
170
|
+
return docItem;
|
|
171
|
+
}),
|
|
156
172
|
};
|
|
157
173
|
})
|
|
158
174
|
.filter((item) => item.items.length > 0); // Filter out any categories with no items.
|
|
@@ -208,12 +224,13 @@ function generateSidebarSlice(sidebarOptions, options, api, tags, docPath, tagGr
|
|
|
208
224
|
filteredTags.push(tag);
|
|
209
225
|
}
|
|
210
226
|
});
|
|
227
|
+
const tagGroupKey = (0, lodash_1.kebabCase)(tagGroup.name);
|
|
211
228
|
const groupCategory = {
|
|
212
229
|
type: "category",
|
|
213
230
|
label: tagGroup.name,
|
|
214
231
|
collapsible: true,
|
|
215
232
|
collapsed: true,
|
|
216
|
-
items: groupByTags(api, sidebarOptions, options, [filteredTags], docPath),
|
|
233
|
+
items: groupByTags(api, sidebarOptions, options, [filteredTags], docPath, tagGroupKey),
|
|
217
234
|
};
|
|
218
235
|
if (options.showSchemas) {
|
|
219
236
|
// For the first tagGroup, save the generated "Schemas" category for later.
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/* ============================================================================
|
|
3
|
+
* Copyright (c) Palo Alto Networks
|
|
4
|
+
*
|
|
5
|
+
* This source code is licensed under the MIT license found in the
|
|
6
|
+
* LICENSE file in the root directory of this source tree.
|
|
7
|
+
* ========================================================================== */
|
|
8
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
9
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
10
|
+
};
|
|
11
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
12
|
+
const index_1 = __importDefault(require("./index"));
|
|
13
|
+
describe("generateSidebarSlice", () => {
|
|
14
|
+
describe("tagGroup with overlapping tags", () => {
|
|
15
|
+
const mockApiItems = [
|
|
16
|
+
{
|
|
17
|
+
type: "api",
|
|
18
|
+
id: "get-books",
|
|
19
|
+
unversionedId: "get-books",
|
|
20
|
+
title: "Get Books",
|
|
21
|
+
description: "",
|
|
22
|
+
source: "",
|
|
23
|
+
sourceDirName: "",
|
|
24
|
+
permalink: "/get-books",
|
|
25
|
+
frontMatter: {},
|
|
26
|
+
api: {
|
|
27
|
+
method: "get",
|
|
28
|
+
path: "/books",
|
|
29
|
+
tags: ["Books", "Deprecated"],
|
|
30
|
+
jsonRequestBodyExample: "",
|
|
31
|
+
info: { title: "Test API", version: "1.0.0" },
|
|
32
|
+
},
|
|
33
|
+
},
|
|
34
|
+
];
|
|
35
|
+
const mockTags = [
|
|
36
|
+
[
|
|
37
|
+
{ name: "Books", description: "Book operations" },
|
|
38
|
+
{ name: "Deprecated", description: "Deprecated endpoints" },
|
|
39
|
+
],
|
|
40
|
+
];
|
|
41
|
+
const mockTagGroups = [
|
|
42
|
+
{ name: "Library", tags: ["Books"] },
|
|
43
|
+
{ name: "Deprecation", tags: ["Deprecated"] },
|
|
44
|
+
];
|
|
45
|
+
function collectKeys(obj) {
|
|
46
|
+
const keys = [];
|
|
47
|
+
JSON.stringify(obj, (k, v) => {
|
|
48
|
+
if (k === "key" && typeof v === "string") {
|
|
49
|
+
keys.push(v);
|
|
50
|
+
}
|
|
51
|
+
return v;
|
|
52
|
+
});
|
|
53
|
+
return keys;
|
|
54
|
+
}
|
|
55
|
+
it("should generate unique keys for items appearing in multiple tagGroups", () => {
|
|
56
|
+
const result = (0, index_1.default)({ groupPathsBy: "tagGroup" }, { outputDir: "docs/test", specPath: "" }, mockApiItems, mockTags, "", mockTagGroups);
|
|
57
|
+
const keys = collectKeys(result);
|
|
58
|
+
expect(keys.length).toBeGreaterThan(0);
|
|
59
|
+
expect(new Set(keys).size).toBe(keys.length);
|
|
60
|
+
});
|
|
61
|
+
it("should include tagGroup name in keys to differentiate same items", () => {
|
|
62
|
+
const result = (0, index_1.default)({ groupPathsBy: "tagGroup" }, { outputDir: "docs/test", specPath: "" }, mockApiItems, mockTags, "", mockTagGroups);
|
|
63
|
+
const keys = collectKeys(result);
|
|
64
|
+
expect(keys.filter((k) => k.includes("library")).length).toBeGreaterThan(0);
|
|
65
|
+
expect(keys.filter((k) => k.includes("deprecation")).length).toBeGreaterThan(0);
|
|
66
|
+
});
|
|
67
|
+
});
|
|
68
|
+
});
|
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-1151",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"keywords": [
|
|
7
7
|
"openapi",
|
|
@@ -40,10 +40,10 @@
|
|
|
40
40
|
"eslint-plugin-prettier": "^5.5.1"
|
|
41
41
|
},
|
|
42
42
|
"dependencies": {
|
|
43
|
-
"@apidevtools/json-schema-ref-parser": "^
|
|
44
|
-
"@redocly/openapi-core": "^
|
|
43
|
+
"@apidevtools/json-schema-ref-parser": "^15.3.3",
|
|
44
|
+
"@redocly/openapi-core": "^2.25.2",
|
|
45
45
|
"allof-merge": "^0.6.6",
|
|
46
|
-
"chalk": "^
|
|
46
|
+
"chalk": "^5.6.2",
|
|
47
47
|
"clsx": "^2.1.1",
|
|
48
48
|
"fs-extra": "^11.3.0",
|
|
49
49
|
"json-pointer": "^0.6.2",
|
|
@@ -65,5 +65,5 @@
|
|
|
65
65
|
"engines": {
|
|
66
66
|
"node": ">=14"
|
|
67
67
|
},
|
|
68
|
-
"gitHead": "
|
|
68
|
+
"gitHead": "ce9f21391974edb2ad7a1a7c2e39700a27bc5123"
|
|
69
69
|
}
|
|
@@ -6,9 +6,8 @@
|
|
|
6
6
|
* ========================================================================== */
|
|
7
7
|
|
|
8
8
|
import $RefParser from "@apidevtools/json-schema-ref-parser";
|
|
9
|
-
import { bundle,
|
|
9
|
+
import { bundle, createConfig } from "@redocly/openapi-core";
|
|
10
10
|
import type { Source, Document } from "@redocly/openapi-core";
|
|
11
|
-
import { ResolvedConfig } from "@redocly/openapi-core/lib/config";
|
|
12
11
|
import chalk from "chalk";
|
|
13
12
|
// @ts-ignore
|
|
14
13
|
import { convertObj } from "swagger2openapi";
|
|
@@ -116,7 +115,7 @@ async function resolveJsonRefs(specUrlOrObject: object | string) {
|
|
|
116
115
|
}
|
|
117
116
|
|
|
118
117
|
export async function loadAndResolveSpec(specUrlOrObject: object | string) {
|
|
119
|
-
const config =
|
|
118
|
+
const config = await createConfig({});
|
|
120
119
|
const bundleOpts = {
|
|
121
120
|
config,
|
|
122
121
|
base: process.cwd(),
|
|
@@ -137,10 +136,13 @@ export async function loadAndResolveSpec(specUrlOrObject: object | string) {
|
|
|
137
136
|
const {
|
|
138
137
|
bundle: { parsed },
|
|
139
138
|
} = await bundle(bundleOpts);
|
|
139
|
+
const parsedSpec = parsed as any;
|
|
140
140
|
|
|
141
141
|
//Pre-processing before resolving JSON refs
|
|
142
|
-
if (
|
|
143
|
-
for (let [component, type] of Object.entries(
|
|
142
|
+
if (parsedSpec.components) {
|
|
143
|
+
for (let [component, type] of Object.entries(
|
|
144
|
+
parsedSpec.components
|
|
145
|
+
) as any) {
|
|
144
146
|
if (component === "schemas") {
|
|
145
147
|
for (let [schemaKey, schemaValue] of Object.entries(type) as any) {
|
|
146
148
|
const title: string | undefined = schemaValue["title"];
|
|
@@ -152,7 +154,7 @@ export async function loadAndResolveSpec(specUrlOrObject: object | string) {
|
|
|
152
154
|
}
|
|
153
155
|
}
|
|
154
156
|
|
|
155
|
-
const resolved = await resolveJsonRefs(
|
|
157
|
+
const resolved = await resolveJsonRefs(parsedSpec);
|
|
156
158
|
|
|
157
159
|
// Force serialization and replace circular $ref pointers
|
|
158
160
|
// @ts-ignore
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
/* ============================================================================
|
|
2
|
+
* Copyright (c) Palo Alto Networks
|
|
3
|
+
*
|
|
4
|
+
* This source code is licensed under the MIT license found in the
|
|
5
|
+
* LICENSE file in the root directory of this source tree.
|
|
6
|
+
* ========================================================================== */
|
|
7
|
+
|
|
8
|
+
import type { TagGroupObject, TagObject } from "../openapi/types";
|
|
9
|
+
import type { ApiMetadata } from "../types";
|
|
10
|
+
import generateSidebarSlice from "./index";
|
|
11
|
+
|
|
12
|
+
describe("generateSidebarSlice", () => {
|
|
13
|
+
describe("tagGroup with overlapping tags", () => {
|
|
14
|
+
const mockApiItems: ApiMetadata[] = [
|
|
15
|
+
{
|
|
16
|
+
type: "api",
|
|
17
|
+
id: "get-books",
|
|
18
|
+
unversionedId: "get-books",
|
|
19
|
+
title: "Get Books",
|
|
20
|
+
description: "",
|
|
21
|
+
source: "",
|
|
22
|
+
sourceDirName: "",
|
|
23
|
+
permalink: "/get-books",
|
|
24
|
+
frontMatter: {},
|
|
25
|
+
api: {
|
|
26
|
+
method: "get",
|
|
27
|
+
path: "/books",
|
|
28
|
+
tags: ["Books", "Deprecated"],
|
|
29
|
+
jsonRequestBodyExample: "",
|
|
30
|
+
info: { title: "Test API", version: "1.0.0" },
|
|
31
|
+
},
|
|
32
|
+
} as ApiMetadata,
|
|
33
|
+
];
|
|
34
|
+
|
|
35
|
+
const mockTags: TagObject[][] = [
|
|
36
|
+
[
|
|
37
|
+
{ name: "Books", description: "Book operations" },
|
|
38
|
+
{ name: "Deprecated", description: "Deprecated endpoints" },
|
|
39
|
+
],
|
|
40
|
+
];
|
|
41
|
+
|
|
42
|
+
const mockTagGroups: TagGroupObject[] = [
|
|
43
|
+
{ name: "Library", tags: ["Books"] },
|
|
44
|
+
{ name: "Deprecation", tags: ["Deprecated"] },
|
|
45
|
+
];
|
|
46
|
+
|
|
47
|
+
function collectKeys(obj: unknown): string[] {
|
|
48
|
+
const keys: string[] = [];
|
|
49
|
+
JSON.stringify(obj, (k, v) => {
|
|
50
|
+
if (k === "key" && typeof v === "string") {
|
|
51
|
+
keys.push(v);
|
|
52
|
+
}
|
|
53
|
+
return v;
|
|
54
|
+
});
|
|
55
|
+
return keys;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
it("should generate unique keys for items appearing in multiple tagGroups", () => {
|
|
59
|
+
const result = generateSidebarSlice(
|
|
60
|
+
{ groupPathsBy: "tagGroup" },
|
|
61
|
+
{ outputDir: "docs/test", specPath: "" },
|
|
62
|
+
mockApiItems,
|
|
63
|
+
mockTags,
|
|
64
|
+
"",
|
|
65
|
+
mockTagGroups
|
|
66
|
+
);
|
|
67
|
+
|
|
68
|
+
const keys = collectKeys(result);
|
|
69
|
+
|
|
70
|
+
expect(keys.length).toBeGreaterThan(0);
|
|
71
|
+
expect(new Set(keys).size).toBe(keys.length);
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
it("should include tagGroup name in keys to differentiate same items", () => {
|
|
75
|
+
const result = generateSidebarSlice(
|
|
76
|
+
{ groupPathsBy: "tagGroup" },
|
|
77
|
+
{ outputDir: "docs/test", specPath: "" },
|
|
78
|
+
mockApiItems,
|
|
79
|
+
mockTags,
|
|
80
|
+
"",
|
|
81
|
+
mockTagGroups
|
|
82
|
+
);
|
|
83
|
+
|
|
84
|
+
const keys = collectKeys(result);
|
|
85
|
+
|
|
86
|
+
expect(keys.filter((k) => k.includes("library")).length).toBeGreaterThan(
|
|
87
|
+
0
|
|
88
|
+
);
|
|
89
|
+
expect(
|
|
90
|
+
keys.filter((k) => k.includes("deprecation")).length
|
|
91
|
+
).toBeGreaterThan(0);
|
|
92
|
+
});
|
|
93
|
+
});
|
|
94
|
+
});
|
package/src/sidebars/index.ts
CHANGED
|
@@ -77,7 +77,8 @@ function groupByTags(
|
|
|
77
77
|
sidebarOptions: SidebarOptions,
|
|
78
78
|
options: APIOptions,
|
|
79
79
|
tags: TagObject[][],
|
|
80
|
-
docPath: string
|
|
80
|
+
docPath: string,
|
|
81
|
+
tagGroupKey?: string
|
|
81
82
|
): ProcessedSidebar {
|
|
82
83
|
let { outputDir, label, showSchemas } = options;
|
|
83
84
|
|
|
@@ -152,9 +153,11 @@ function groupByTags(
|
|
|
152
153
|
if (infoItems.length === 1) {
|
|
153
154
|
const infoItem = infoItems[0];
|
|
154
155
|
const id = infoItem.id;
|
|
156
|
+
const docId = basePath === "" || undefined ? `${id}` : `${basePath}/${id}`;
|
|
155
157
|
rootIntroDoc = {
|
|
156
158
|
type: "doc" as const,
|
|
157
|
-
id:
|
|
159
|
+
id: docId,
|
|
160
|
+
...(tagGroupKey && { key: kebabCase(`${tagGroupKey}-${docId}`) }),
|
|
158
161
|
};
|
|
159
162
|
}
|
|
160
163
|
|
|
@@ -219,15 +222,28 @@ function groupByTags(
|
|
|
219
222
|
(item) => !!item.schema["x-tags"]?.includes(tag)
|
|
220
223
|
);
|
|
221
224
|
|
|
225
|
+
const categoryLabel = tagObject?.["x-displayName"] ?? tag;
|
|
226
|
+
const categoryKey = tagGroupKey
|
|
227
|
+
? kebabCase(`${tagGroupKey}-${categoryLabel}`)
|
|
228
|
+
: undefined;
|
|
229
|
+
|
|
222
230
|
return {
|
|
223
231
|
type: "category" as const,
|
|
224
|
-
label:
|
|
232
|
+
label: categoryLabel,
|
|
233
|
+
...(categoryKey && { key: categoryKey }),
|
|
225
234
|
link: linkConfig,
|
|
226
235
|
collapsible: sidebarCollapsible,
|
|
227
236
|
collapsed: sidebarCollapsed,
|
|
228
|
-
items: [...taggedSchemaItems, ...taggedApiItems].map((item) =>
|
|
229
|
-
createDocItemFn(item, createDocItemFnContext)
|
|
230
|
-
|
|
237
|
+
items: [...taggedSchemaItems, ...taggedApiItems].map((item) => {
|
|
238
|
+
const docItem = createDocItemFn(item, createDocItemFnContext);
|
|
239
|
+
if (tagGroupKey && docItem.type === "doc") {
|
|
240
|
+
return {
|
|
241
|
+
...docItem,
|
|
242
|
+
key: kebabCase(`${tagGroupKey}-${tag}-${docItem.id}`),
|
|
243
|
+
};
|
|
244
|
+
}
|
|
245
|
+
return docItem;
|
|
246
|
+
}),
|
|
231
247
|
};
|
|
232
248
|
})
|
|
233
249
|
.filter((item) => item.items.length > 0); // Filter out any categories with no items.
|
|
@@ -296,6 +312,8 @@ export default function generateSidebarSlice(
|
|
|
296
312
|
}
|
|
297
313
|
});
|
|
298
314
|
|
|
315
|
+
const tagGroupKey = kebabCase(tagGroup.name);
|
|
316
|
+
|
|
299
317
|
const groupCategory = {
|
|
300
318
|
type: "category" as const,
|
|
301
319
|
label: tagGroup.name,
|
|
@@ -306,7 +324,8 @@ export default function generateSidebarSlice(
|
|
|
306
324
|
sidebarOptions,
|
|
307
325
|
options,
|
|
308
326
|
[filteredTags],
|
|
309
|
-
docPath
|
|
327
|
+
docPath,
|
|
328
|
+
tagGroupKey
|
|
310
329
|
),
|
|
311
330
|
};
|
|
312
331
|
|