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.
@@ -104,7 +104,7 @@ async function resolveJsonRefs(specUrlOrObject) {
104
104
  }
105
105
  }
106
106
  async function loadAndResolveSpec(specUrlOrObject) {
107
- const config = new openapi_core_1.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 (parsed.components) {
126
- for (let [component, type] of Object.entries(parsed.components)) {
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(parsed);
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());
@@ -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: basePath === "" || undefined ? `${id}` : `${basePath}/${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: (_a = tagObject === null || tagObject === void 0 ? void 0 : tagObject["x-displayName"]) !== null && _a !== void 0 ? _a : tag,
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) => createDocItemFn(item, createDocItemFnContext)),
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-1110",
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": "^11.5.4",
44
- "@redocly/openapi-core": "^1.34.3",
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": "^4.1.2",
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": "e4a99880178b660e4ba688b2d13aef1280752322"
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, Config } from "@redocly/openapi-core";
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 = new Config({} as ResolvedConfig);
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 (parsed.components) {
143
- for (let [component, type] of Object.entries(parsed.components) as any) {
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(parsed);
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
+ });
@@ -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: basePath === "" || undefined ? `${id}` : `${basePath}/${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: tagObject?.["x-displayName"] ?? tag,
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