docusaurus-plugin-openapi-docs 0.0.0-1000

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.
Files changed (163) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +361 -0
  3. package/lib/index.d.ts +9 -0
  4. package/lib/index.js +709 -0
  5. package/lib/markdown/createArrayBracket.d.ts +2 -0
  6. package/lib/markdown/createArrayBracket.js +36 -0
  7. package/lib/markdown/createAuthentication.d.ts +2 -0
  8. package/lib/markdown/createAuthentication.js +171 -0
  9. package/lib/markdown/createAuthorization.d.ts +1 -0
  10. package/lib/markdown/createAuthorization.js +15 -0
  11. package/lib/markdown/createCallbackMethodEndpoint.d.ts +1 -0
  12. package/lib/markdown/createCallbackMethodEndpoint.js +20 -0
  13. package/lib/markdown/createCallbacks.d.ts +6 -0
  14. package/lib/markdown/createCallbacks.js +77 -0
  15. package/lib/markdown/createContactInfo.d.ts +2 -0
  16. package/lib/markdown/createContactInfo.js +39 -0
  17. package/lib/markdown/createDeprecationNotice.d.ts +6 -0
  18. package/lib/markdown/createDeprecationNotice.js +20 -0
  19. package/lib/markdown/createDescription.d.ts +1 -0
  20. package/lib/markdown/createDescription.js +13 -0
  21. package/lib/markdown/createDetails.d.ts +2 -0
  22. package/lib/markdown/createDetails.js +17 -0
  23. package/lib/markdown/createDetailsSummary.d.ts +2 -0
  24. package/lib/markdown/createDetailsSummary.js +17 -0
  25. package/lib/markdown/createDownload.d.ts +1 -0
  26. package/lib/markdown/createDownload.js +16 -0
  27. package/lib/markdown/createHeading.d.ts +1 -0
  28. package/lib/markdown/createHeading.js +20 -0
  29. package/lib/markdown/createLicense.d.ts +2 -0
  30. package/lib/markdown/createLicense.js +32 -0
  31. package/lib/markdown/createLogo.d.ts +2 -0
  32. package/lib/markdown/createLogo.js +18 -0
  33. package/lib/markdown/createMethodEndpoint.d.ts +1 -0
  34. package/lib/markdown/createMethodEndpoint.js +20 -0
  35. package/lib/markdown/createParamsDetails.d.ts +6 -0
  36. package/lib/markdown/createParamsDetails.js +18 -0
  37. package/lib/markdown/createRequestBodyDetails.d.ts +13 -0
  38. package/lib/markdown/createRequestBodyDetails.js +13 -0
  39. package/lib/markdown/createRequestHeader.d.ts +1 -0
  40. package/lib/markdown/createRequestHeader.js +21 -0
  41. package/lib/markdown/createRequestSchema.d.ts +14 -0
  42. package/lib/markdown/createRequestSchema.js +20 -0
  43. package/lib/markdown/createResponseSchema.d.ts +14 -0
  44. package/lib/markdown/createResponseSchema.js +20 -0
  45. package/lib/markdown/createSchema.d.ts +9 -0
  46. package/lib/markdown/createSchema.js +668 -0
  47. package/lib/markdown/createSchema.test.d.ts +1 -0
  48. package/lib/markdown/createSchema.test.js +913 -0
  49. package/lib/markdown/createStatusCodes.d.ts +9 -0
  50. package/lib/markdown/createStatusCodes.js +63 -0
  51. package/lib/markdown/createTermsOfService.d.ts +1 -0
  52. package/lib/markdown/createTermsOfService.js +31 -0
  53. package/lib/markdown/createVendorExtensions.d.ts +1 -0
  54. package/lib/markdown/createVendorExtensions.js +24 -0
  55. package/lib/markdown/createVersionBadge.d.ts +1 -0
  56. package/lib/markdown/createVersionBadge.js +19 -0
  57. package/lib/markdown/index.d.ts +5 -0
  58. package/lib/markdown/index.js +92 -0
  59. package/lib/markdown/schema.d.ts +3 -0
  60. package/lib/markdown/schema.js +154 -0
  61. package/lib/markdown/schema.test.d.ts +1 -0
  62. package/lib/markdown/schema.test.js +181 -0
  63. package/lib/markdown/utils.d.ts +20 -0
  64. package/lib/markdown/utils.js +68 -0
  65. package/lib/openapi/createRequestExample.d.ts +2 -0
  66. package/lib/openapi/createRequestExample.js +191 -0
  67. package/lib/openapi/createResponseExample.d.ts +2 -0
  68. package/lib/openapi/createResponseExample.js +192 -0
  69. package/lib/openapi/index.d.ts +1 -0
  70. package/lib/openapi/index.js +12 -0
  71. package/lib/openapi/openapi.d.ts +12 -0
  72. package/lib/openapi/openapi.js +544 -0
  73. package/lib/openapi/openapi.test.d.ts +1 -0
  74. package/lib/openapi/openapi.test.js +33 -0
  75. package/lib/openapi/types.d.ts +354 -0
  76. package/lib/openapi/types.js +8 -0
  77. package/lib/openapi/utils/loadAndResolveSpec.d.ts +2 -0
  78. package/lib/openapi/utils/loadAndResolveSpec.js +153 -0
  79. package/lib/openapi/utils/services/OpenAPIParser.d.ts +52 -0
  80. package/lib/openapi/utils/services/OpenAPIParser.js +343 -0
  81. package/lib/openapi/utils/services/RedocNormalizedOptions.d.ts +100 -0
  82. package/lib/openapi/utils/services/RedocNormalizedOptions.js +170 -0
  83. package/lib/openapi/utils/types/index.d.ts +2 -0
  84. package/lib/openapi/utils/types/index.js +23 -0
  85. package/lib/openapi/utils/types/open-api.d.ts +305 -0
  86. package/lib/openapi/utils/types/open-api.js +8 -0
  87. package/lib/openapi/utils/types.d.ts +306 -0
  88. package/lib/openapi/utils/types.js +8 -0
  89. package/lib/openapi/utils/utils/JsonPointer.d.ts +51 -0
  90. package/lib/openapi/utils/utils/JsonPointer.js +95 -0
  91. package/lib/openapi/utils/utils/helpers.d.ts +43 -0
  92. package/lib/openapi/utils/utils/helpers.js +230 -0
  93. package/lib/openapi/utils/utils/index.d.ts +3 -0
  94. package/lib/openapi/utils/utils/index.js +25 -0
  95. package/lib/openapi/utils/utils/openapi.d.ts +40 -0
  96. package/lib/openapi/utils/utils/openapi.js +605 -0
  97. package/lib/options.d.ts +2 -0
  98. package/lib/options.js +69 -0
  99. package/lib/sidebars/index.d.ts +4 -0
  100. package/lib/sidebars/index.js +226 -0
  101. package/lib/sidebars/utils.d.ts +2 -0
  102. package/lib/sidebars/utils.js +30 -0
  103. package/lib/types.d.ts +135 -0
  104. package/lib/types.js +8 -0
  105. package/package.json +69 -0
  106. package/src/index.ts +945 -0
  107. package/src/markdown/__snapshots__/createSchema.test.ts.snap +1605 -0
  108. package/src/markdown/createArrayBracket.ts +35 -0
  109. package/src/markdown/createAuthentication.ts +201 -0
  110. package/src/markdown/createAuthorization.ts +13 -0
  111. package/src/markdown/createCallbackMethodEndpoint.ts +19 -0
  112. package/src/markdown/createCallbacks.ts +101 -0
  113. package/src/markdown/createContactInfo.ts +41 -0
  114. package/src/markdown/createDeprecationNotice.ts +31 -0
  115. package/src/markdown/createDescription.ts +12 -0
  116. package/src/markdown/createDetails.ts +16 -0
  117. package/src/markdown/createDetailsSummary.ts +16 -0
  118. package/src/markdown/createDownload.ts +15 -0
  119. package/src/markdown/createHeading.ts +23 -0
  120. package/src/markdown/createLicense.ts +34 -0
  121. package/src/markdown/createLogo.ts +21 -0
  122. package/src/markdown/createMethodEndpoint.ts +19 -0
  123. package/src/markdown/createParamsDetails.ts +22 -0
  124. package/src/markdown/createRequestBodyDetails.ts +24 -0
  125. package/src/markdown/createRequestHeader.ts +24 -0
  126. package/src/markdown/createRequestSchema.ts +32 -0
  127. package/src/markdown/createResponseSchema.ts +32 -0
  128. package/src/markdown/createSchema.test.ts +1075 -0
  129. package/src/markdown/createSchema.ts +862 -0
  130. package/src/markdown/createStatusCodes.ts +63 -0
  131. package/src/markdown/createTermsOfService.ts +30 -0
  132. package/src/markdown/createVendorExtensions.ts +22 -0
  133. package/src/markdown/createVersionBadge.ts +22 -0
  134. package/src/markdown/index.ts +144 -0
  135. package/src/markdown/schema.test.ts +208 -0
  136. package/src/markdown/schema.ts +185 -0
  137. package/src/markdown/utils.ts +89 -0
  138. package/src/openapi/__fixtures__/examples/openapi.yaml +49 -0
  139. package/src/openapi/createRequestExample.ts +235 -0
  140. package/src/openapi/createResponseExample.ts +238 -0
  141. package/src/openapi/index.ts +8 -0
  142. package/src/openapi/openapi.test.ts +40 -0
  143. package/src/openapi/openapi.ts +697 -0
  144. package/src/openapi/types.ts +455 -0
  145. package/src/openapi/utils/loadAndResolveSpec.ts +171 -0
  146. package/src/openapi/utils/services/OpenAPIParser.ts +434 -0
  147. package/src/openapi/utils/services/RedocNormalizedOptions.ts +330 -0
  148. package/src/openapi/utils/types/index.ts +10 -0
  149. package/src/openapi/utils/types/open-api.ts +303 -0
  150. package/src/openapi/utils/types.ts +303 -0
  151. package/src/openapi/utils/utils/JsonPointer.ts +99 -0
  152. package/src/openapi/utils/utils/helpers.ts +239 -0
  153. package/src/openapi/utils/utils/index.ts +11 -0
  154. package/src/openapi/utils/utils/openapi.ts +771 -0
  155. package/src/openapi-to-postmanv2.d.ts +10 -0
  156. package/src/options.ts +77 -0
  157. package/src/plugin-content-docs-types.d.ts +42 -0
  158. package/src/plugin-openapi.d.ts +87 -0
  159. package/src/postman-collection.d.ts +10 -0
  160. package/src/sidebars/index.ts +322 -0
  161. package/src/sidebars/utils.ts +29 -0
  162. package/src/types.ts +176 -0
  163. package/tsconfig.json +7 -0
@@ -0,0 +1,697 @@
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 path from "path";
9
+
10
+ import { Globby, GlobExcludeDefault, posixPath } from "@docusaurus/utils";
11
+ import chalk from "chalk";
12
+ import fs from "fs-extra";
13
+ import cloneDeep from "lodash/cloneDeep";
14
+ import kebabCase from "lodash/kebabCase";
15
+ import unionBy from "lodash/unionBy";
16
+ import uniq from "lodash/uniq";
17
+ import Converter from "openapi-to-postmanv2";
18
+ import Collection from "postman-collection";
19
+ import sdk from "postman-collection";
20
+
21
+ import { sampleRequestFromSchema } from "./createRequestExample";
22
+ import { OpenApiObject, TagGroupObject, TagObject } from "./types";
23
+ import { isURL } from "../index";
24
+ import {
25
+ ApiMetadata,
26
+ APIOptions,
27
+ ApiPageMetadata,
28
+ InfoPageMetadata,
29
+ SchemaPageMetadata,
30
+ SidebarOptions,
31
+ TagPageMetadata,
32
+ } from "../types";
33
+ import { sampleResponseFromSchema } from "./createResponseExample";
34
+ import { loadAndResolveSpec } from "./utils/loadAndResolveSpec";
35
+
36
+ /**
37
+ * Convenience function for converting raw JSON to a Postman Collection object.
38
+ */
39
+ function jsonToCollection(data: OpenApiObject): Promise<Collection> {
40
+ return new Promise((resolve, reject) => {
41
+ let schemaPack = new Converter.SchemaPack(
42
+ { type: "json", data },
43
+ { schemaFaker: false }
44
+ );
45
+ schemaPack.computedOptions.schemaFaker = false;
46
+ schemaPack.convert((_err: any, conversionResult: any) => {
47
+ if (!conversionResult.result) {
48
+ return reject(conversionResult.reason);
49
+ }
50
+ return resolve(new sdk.Collection(conversionResult.output[0].data));
51
+ });
52
+ });
53
+ }
54
+
55
+ /**
56
+ * Creates a Postman Collection object from an OpenAPI definition.
57
+ */
58
+ async function createPostmanCollection(
59
+ openapiData: OpenApiObject
60
+ ): Promise<Collection> {
61
+ // Create copy of openapiData
62
+ const data = cloneDeep(openapiData) as OpenApiObject;
63
+
64
+ // Including `servers` breaks postman, so delete all of them.
65
+ delete data.servers;
66
+ for (let pathItemObject of Object.values(data.paths)) {
67
+ delete pathItemObject.servers;
68
+ delete pathItemObject.get?.servers;
69
+ delete pathItemObject.put?.servers;
70
+ delete pathItemObject.post?.servers;
71
+ delete pathItemObject.delete?.servers;
72
+ delete pathItemObject.options?.servers;
73
+ delete pathItemObject.head?.servers;
74
+ delete pathItemObject.patch?.servers;
75
+ delete pathItemObject.trace?.servers;
76
+ }
77
+
78
+ return await jsonToCollection(data);
79
+ }
80
+
81
+ type PartialPage<T> = Omit<T, "permalink" | "source" | "sourceDirName">;
82
+
83
+ function createItems(
84
+ openapiData: OpenApiObject,
85
+ options: APIOptions,
86
+ sidebarOptions: SidebarOptions
87
+ ): ApiMetadata[] {
88
+ // TODO: Find a better way to handle this
89
+ let items: PartialPage<ApiMetadata>[] = [];
90
+ const infoIdSpaces = openapiData.info.title.replace(" ", "-").toLowerCase();
91
+ const infoId = kebabCase(infoIdSpaces);
92
+
93
+ if (openapiData.info.description || openapiData.info.title) {
94
+ // Only create an info page if we have a description.
95
+ const infoDescription = openapiData.info?.description;
96
+ let splitDescription: any;
97
+ if (infoDescription) {
98
+ splitDescription = infoDescription.match(/[^\r\n]+/g);
99
+ }
100
+ const infoPage: PartialPage<InfoPageMetadata> = {
101
+ type: "info",
102
+ id: infoId,
103
+ unversionedId: infoId,
104
+ title: openapiData.info.title
105
+ ? openapiData.info.title.replace(/((?:^|[^\\])(?:\\{2})*)"/g, "$1'")
106
+ : "",
107
+ description: openapiData.info.description
108
+ ? openapiData.info.description.replace(
109
+ /((?:^|[^\\])(?:\\{2})*)"/g,
110
+ "$1'"
111
+ )
112
+ : "",
113
+ frontMatter: {
114
+ description: splitDescription
115
+ ? splitDescription[0]
116
+ .replace(/((?:^|[^\\])(?:\\{2})*)"/g, "$1'")
117
+ .replace(/\s+$/, "")
118
+ : "",
119
+ },
120
+ securitySchemes: openapiData.components?.securitySchemes,
121
+ info: {
122
+ ...openapiData.info,
123
+ tags: openapiData.tags,
124
+ title: openapiData.info.title ?? "Introduction",
125
+ logo: openapiData.info["x-logo"]! as any,
126
+ darkLogo: openapiData.info["x-dark-logo"]! as any,
127
+ },
128
+ };
129
+ items.push(infoPage);
130
+ }
131
+
132
+ for (let [path, pathObject] of Object.entries(openapiData.paths)) {
133
+ const { $ref, description, parameters, servers, summary, ...rest } =
134
+ pathObject;
135
+ for (let [method, operationObject] of Object.entries({ ...rest })) {
136
+ const title =
137
+ operationObject.summary ??
138
+ operationObject.operationId ??
139
+ "Missing summary";
140
+ if (operationObject.description === undefined) {
141
+ operationObject.description =
142
+ operationObject.summary ?? operationObject.operationId ?? "";
143
+ }
144
+
145
+ const baseId = operationObject.operationId
146
+ ? kebabCase(operationObject.operationId)
147
+ : kebabCase(operationObject.summary);
148
+
149
+ const extensions = [];
150
+ const commonExtensions = ["x-codeSamples"];
151
+
152
+ for (const [key, value] of Object.entries(operationObject)) {
153
+ if (key.startsWith("x-") && !commonExtensions.includes(key)) {
154
+ extensions.push({ key, value });
155
+ }
156
+ }
157
+
158
+ const servers =
159
+ operationObject.servers ?? pathObject.servers ?? openapiData.servers;
160
+
161
+ const security = operationObject.security ?? openapiData.security;
162
+
163
+ // Add security schemes so we know how to handle security.
164
+ const securitySchemes = openapiData.components?.securitySchemes;
165
+
166
+ // Make sure schemes are lowercase. See: https://github.com/cloud-annotations/docusaurus-plugin-openapi/issues/79
167
+ if (securitySchemes) {
168
+ for (let securityScheme of Object.values(securitySchemes)) {
169
+ if (securityScheme.type === "http") {
170
+ securityScheme.scheme = securityScheme.scheme.toLowerCase();
171
+ }
172
+ }
173
+ }
174
+
175
+ let jsonRequestBodyExample;
176
+ const content = operationObject.requestBody?.content;
177
+ let body;
178
+ for (let key in content) {
179
+ if (
180
+ key.toLowerCase() === "application/json" ||
181
+ key.toLowerCase() === "application/json; charset=utf-8"
182
+ ) {
183
+ body = content[key];
184
+ break;
185
+ }
186
+ }
187
+
188
+ if (body?.schema) {
189
+ jsonRequestBodyExample = sampleRequestFromSchema(body.schema);
190
+ }
191
+
192
+ // Handle vendor JSON media types
193
+ const bodyContent = operationObject.requestBody?.content;
194
+
195
+ if (bodyContent && Object.keys(bodyContent).length > 0) {
196
+ const firstBodyContentKey = Object.keys(bodyContent)[0];
197
+ if (firstBodyContentKey.endsWith("+json")) {
198
+ const firstBody = bodyContent[firstBodyContentKey];
199
+ if (firstBody?.schema) {
200
+ jsonRequestBodyExample = sampleRequestFromSchema(firstBody.schema);
201
+ }
202
+ }
203
+ }
204
+
205
+ // TODO: Don't include summary temporarilly
206
+ const { summary, ...defaults } = operationObject;
207
+
208
+ // Merge common parameters with operation parameters
209
+ // Operation params take precendence over common params
210
+ if (parameters !== undefined) {
211
+ if (operationObject.parameters !== undefined) {
212
+ defaults.parameters = unionBy(
213
+ operationObject.parameters,
214
+ parameters,
215
+ "name"
216
+ );
217
+ } else {
218
+ defaults.parameters = parameters;
219
+ }
220
+ }
221
+
222
+ const opDescription = operationObject.description;
223
+ let splitDescription: any;
224
+ if (opDescription) {
225
+ splitDescription = opDescription.match(/[^\r\n]+/g);
226
+ }
227
+
228
+ const apiPage: PartialPage<ApiPageMetadata> = {
229
+ type: "api",
230
+ id: baseId,
231
+ infoId: infoId ?? "",
232
+ unversionedId: baseId,
233
+ title: title ? title.replace(/((?:^|[^\\])(?:\\{2})*)"/g, "$1'") : "",
234
+ description: operationObject.description
235
+ ? operationObject.description.replace(
236
+ /((?:^|[^\\])(?:\\{2})*)"/g,
237
+ "$1'"
238
+ )
239
+ : "",
240
+ frontMatter: {
241
+ description: splitDescription
242
+ ? splitDescription[0]
243
+ .replace(/((?:^|[^\\])(?:\\{2})*)"/g, "$1'")
244
+ .replace(/\s+$/, "")
245
+ : "",
246
+ ...(options?.proxy && { proxy: options.proxy }),
247
+ ...(options?.hideSendButton && {
248
+ hide_send_button: options.hideSendButton,
249
+ }),
250
+ ...(options?.showExtensions && {
251
+ show_extensions: options.showExtensions,
252
+ }),
253
+ },
254
+ api: {
255
+ ...defaults,
256
+ ...(extensions.length > 0 && { extensions: extensions }),
257
+ tags: operationObject.tags,
258
+ method,
259
+ path,
260
+ servers,
261
+ security,
262
+ securitySchemes,
263
+ jsonRequestBodyExample,
264
+ info: openapiData.info,
265
+ },
266
+ };
267
+
268
+ items.push(apiPage);
269
+ }
270
+ }
271
+
272
+ // Gather x-webhooks endpoints
273
+ for (let [path, pathObject] of Object.entries(
274
+ openapiData["x-webhooks"] ?? openapiData["webhooks"] ?? {}
275
+ )) {
276
+ path = "webhook";
277
+ const { $ref, description, parameters, servers, summary, ...rest } =
278
+ pathObject;
279
+ for (let [method, operationObject] of Object.entries({ ...rest })) {
280
+ method = "event";
281
+ const title =
282
+ operationObject.summary ??
283
+ operationObject.operationId ??
284
+ "Missing summary";
285
+ if (operationObject.description === undefined) {
286
+ operationObject.description =
287
+ operationObject.summary ?? operationObject.operationId ?? "";
288
+ }
289
+
290
+ const baseId = operationObject.operationId
291
+ ? kebabCase(operationObject.operationId)
292
+ : kebabCase(operationObject.summary);
293
+
294
+ const extensions = [];
295
+ const commonExtensions = ["x-codeSamples"];
296
+
297
+ for (const [key, value] of Object.entries(operationObject)) {
298
+ if (key.startsWith("x-") && !commonExtensions.includes(key)) {
299
+ extensions.push({ key, value });
300
+ }
301
+ }
302
+
303
+ const servers =
304
+ operationObject.servers ?? pathObject.servers ?? openapiData.servers;
305
+
306
+ const security = operationObject.security ?? openapiData.security;
307
+
308
+ // Add security schemes so we know how to handle security.
309
+ const securitySchemes = openapiData.components?.securitySchemes;
310
+
311
+ // Make sure schemes are lowercase. See: https://github.com/cloud-annotations/docusaurus-plugin-openapi/issues/79
312
+ if (securitySchemes) {
313
+ for (let securityScheme of Object.values(securitySchemes)) {
314
+ if (securityScheme.type === "http") {
315
+ securityScheme.scheme = securityScheme.scheme.toLowerCase();
316
+ }
317
+ }
318
+ }
319
+
320
+ let jsonRequestBodyExample;
321
+ const content = operationObject.requestBody?.content;
322
+ let body;
323
+ for (let key in content) {
324
+ if (
325
+ key.toLowerCase() === "application/json" ||
326
+ key.toLowerCase() === "application/json; charset=utf-8"
327
+ ) {
328
+ body = content[key];
329
+ break;
330
+ }
331
+ }
332
+
333
+ if (body?.schema) {
334
+ jsonRequestBodyExample = sampleRequestFromSchema(body.schema);
335
+ }
336
+
337
+ // Handle vendor JSON media types
338
+ const bodyContent = operationObject.requestBody?.content;
339
+ if (bodyContent) {
340
+ const firstBodyContentKey = Object.keys(bodyContent)[0];
341
+ if (firstBodyContentKey.endsWith("+json")) {
342
+ const firstBody = bodyContent[firstBodyContentKey];
343
+ if (firstBody?.schema) {
344
+ jsonRequestBodyExample = sampleRequestFromSchema(firstBody.schema);
345
+ }
346
+ }
347
+ }
348
+
349
+ // TODO: Don't include summary temporarilly
350
+ const { summary, ...defaults } = operationObject;
351
+
352
+ // Merge common parameters with operation parameters
353
+ // Operation params take precendence over common params
354
+ if (parameters !== undefined) {
355
+ if (operationObject.parameters !== undefined) {
356
+ defaults.parameters = unionBy(
357
+ operationObject.parameters,
358
+ parameters,
359
+ "name"
360
+ );
361
+ } else {
362
+ defaults.parameters = parameters;
363
+ }
364
+ }
365
+
366
+ const opDescription = operationObject.description;
367
+ let splitDescription: any;
368
+ if (opDescription) {
369
+ splitDescription = opDescription.match(/[^\r\n]+/g);
370
+ }
371
+
372
+ const apiPage: PartialPage<ApiPageMetadata> = {
373
+ type: "api",
374
+ id: baseId,
375
+ infoId: infoId ?? "",
376
+ unversionedId: baseId,
377
+ title: title ? title.replace(/((?:^|[^\\])(?:\\{2})*)"/g, "$1'") : "",
378
+ description: operationObject.description
379
+ ? operationObject.description.replace(
380
+ /((?:^|[^\\])(?:\\{2})*)"/g,
381
+ "$1'"
382
+ )
383
+ : "",
384
+ frontMatter: {
385
+ description: splitDescription
386
+ ? splitDescription[0]
387
+ .replace(/((?:^|[^\\])(?:\\{2})*)"/g, "$1'")
388
+ .replace(/\s+$/, "")
389
+ : "",
390
+ ...(options?.proxy && { proxy: options.proxy }),
391
+ ...(options?.hideSendButton && {
392
+ hide_send_button: options.hideSendButton,
393
+ }),
394
+ ...(options?.showExtensions && {
395
+ show_extensions: options.showExtensions,
396
+ }),
397
+ },
398
+ api: {
399
+ ...defaults,
400
+ ...(extensions.length > 0 && { extensions: extensions }),
401
+ tags: operationObject.tags,
402
+ method,
403
+ path,
404
+ servers,
405
+ security,
406
+ securitySchemes,
407
+ jsonRequestBodyExample,
408
+ info: openapiData.info,
409
+ },
410
+ };
411
+ items.push(apiPage);
412
+ }
413
+ }
414
+
415
+ if (
416
+ options?.showSchemas === true ||
417
+ Object.entries(openapiData?.components?.schemas ?? {})
418
+ .flatMap(([_, s]) => s["x-tags"])
419
+ .filter((item) => !!item).length > 0
420
+ ) {
421
+ // Gather schemas
422
+ for (let [schema, schemaObject] of Object.entries(
423
+ openapiData?.components?.schemas ?? {}
424
+ )) {
425
+ if (options?.showSchemas === true || schemaObject["x-tags"]) {
426
+ const baseIdSpaces =
427
+ schemaObject?.title?.replace(" ", "-").toLowerCase() ?? "";
428
+ const baseId = kebabCase(baseIdSpaces);
429
+
430
+ const schemaDescription = schemaObject.description;
431
+ let splitDescription: any;
432
+ if (schemaDescription) {
433
+ splitDescription = schemaDescription.match(/[^\r\n]+/g);
434
+ }
435
+
436
+ const schemaPage: PartialPage<SchemaPageMetadata> = {
437
+ type: "schema",
438
+ id: baseId,
439
+ infoId: infoId ?? "",
440
+ unversionedId: baseId,
441
+ title: schemaObject.title
442
+ ? schemaObject.title.replace(/((?:^|[^\\])(?:\\{2})*)"/g, "$1'")
443
+ : schema,
444
+ description: schemaObject.description
445
+ ? schemaObject.description.replace(
446
+ /((?:^|[^\\])(?:\\{2})*)"/g,
447
+ "$1'"
448
+ )
449
+ : "",
450
+ frontMatter: {
451
+ description: splitDescription
452
+ ? splitDescription[0]
453
+ .replace(/((?:^|[^\\])(?:\\{2})*)"/g, "$1'")
454
+ .replace(/\s+$/, "")
455
+ : "",
456
+ sample: JSON.stringify(sampleResponseFromSchema(schemaObject)),
457
+ },
458
+ schema: schemaObject,
459
+ };
460
+
461
+ items.push(schemaPage);
462
+ }
463
+ }
464
+ }
465
+
466
+ if (sidebarOptions?.categoryLinkSource === "tag") {
467
+ // Get global tags
468
+ const tags: TagObject[] = openapiData.tags ?? [];
469
+
470
+ // Get operation tags
471
+ const apiItems = items.filter((item) => {
472
+ return item.type === "api";
473
+ }) as ApiPageMetadata[];
474
+ const operationTags = uniq(
475
+ apiItems
476
+ .flatMap((item) => item.api.tags)
477
+ .filter((item): item is string => !!item)
478
+ );
479
+
480
+ // eslint-disable-next-line array-callback-return
481
+ tags
482
+ .filter((tag) => operationTags.includes(tag.name!)) // include only tags referenced by operation
483
+ // eslint-disable-next-line array-callback-return
484
+ .map((tag) => {
485
+ const description = getTagDisplayName(
486
+ tag.name!,
487
+ openapiData.tags ?? []
488
+ );
489
+ const tagId = kebabCase(tag.name);
490
+ const splitDescription = description.match(/[^\r\n]+/g);
491
+ const tagPage: PartialPage<TagPageMetadata> = {
492
+ type: "tag",
493
+ id: tagId,
494
+ unversionedId: tagId,
495
+ title: description ?? "",
496
+ description: description ?? "",
497
+ frontMatter: {
498
+ description: splitDescription
499
+ ? splitDescription[0]
500
+ .replace(/((?:^|[^\\])(?:\\{2})*)"/g, "$1'")
501
+ .replace(/\s+$/, "")
502
+ : "",
503
+ },
504
+ tag: {
505
+ ...tag,
506
+ },
507
+ };
508
+ items.push(tagPage);
509
+ });
510
+ }
511
+
512
+ return items as ApiMetadata[];
513
+ }
514
+
515
+ /**
516
+ * Attach Postman Request objects to the corresponding ApiItems.
517
+ */
518
+ function bindCollectionToApiItems(
519
+ items: ApiMetadata[],
520
+ postmanCollection: sdk.Collection
521
+ ) {
522
+ postmanCollection.forEachItem((item: any) => {
523
+ const method = item.request.method.toLowerCase();
524
+ const path = item.request.url
525
+ .getPath({ unresolved: true }) // unresolved returns "/:variableName" instead of "/<type>"
526
+ .replace(/(?<![a-z0-9-_]+):([a-z0-9-_]+)/gi, "{$1}"); // replace "/:variableName" with "/{variableName}"
527
+ const apiItem = items.find((item) => {
528
+ if (
529
+ item.type === "info" ||
530
+ item.type === "tag" ||
531
+ item.type === "schema"
532
+ ) {
533
+ return false;
534
+ }
535
+ return item.api.path === path && item.api.method === method;
536
+ });
537
+
538
+ if (apiItem?.type === "api") {
539
+ apiItem.api.postman = item.request;
540
+ }
541
+ });
542
+ }
543
+
544
+ interface OpenApiFiles {
545
+ source: string;
546
+ sourceDirName: string;
547
+ data: OpenApiObject;
548
+ }
549
+
550
+ export async function readOpenapiFiles(
551
+ openapiPath: string
552
+ ): Promise<OpenApiFiles[]> {
553
+ if (!isURL(openapiPath)) {
554
+ const stat = await fs.lstat(openapiPath);
555
+ if (stat.isDirectory()) {
556
+ // TODO: Add config for inlcude/ignore
557
+ const allFiles = await Globby(["**/*.{json,yaml,yml}"], {
558
+ cwd: openapiPath,
559
+ ignore: GlobExcludeDefault,
560
+ deep: 1,
561
+ });
562
+ const sources = allFiles.filter((x) => !x.includes("_category_")); // todo: regex exclude?
563
+ return Promise.all(
564
+ sources.map(async (source) => {
565
+ // TODO: make a function for this
566
+ const fullPath = posixPath(path.join(openapiPath, source));
567
+ const data = (await loadAndResolveSpec(
568
+ fullPath
569
+ )) as unknown as OpenApiObject;
570
+ return {
571
+ source: fullPath, // This will be aliased in process.
572
+ sourceDirName: path.dirname(source),
573
+ data,
574
+ };
575
+ })
576
+ );
577
+ }
578
+ }
579
+ const data = (await loadAndResolveSpec(
580
+ openapiPath
581
+ )) as unknown as OpenApiObject;
582
+ return [
583
+ {
584
+ source: openapiPath, // This will be aliased in process.
585
+ sourceDirName: ".",
586
+ data,
587
+ },
588
+ ];
589
+ }
590
+
591
+ export async function processOpenapiFiles(
592
+ files: OpenApiFiles[],
593
+ options: APIOptions,
594
+ sidebarOptions: SidebarOptions
595
+ ): Promise<[ApiMetadata[], TagObject[][], TagGroupObject[]]> {
596
+ const promises = files.map(async (file) => {
597
+ if (file.data !== undefined) {
598
+ const processedFile = await processOpenapiFile(
599
+ file.data,
600
+ options,
601
+ sidebarOptions
602
+ );
603
+ const itemsObjectsArray = processedFile[0].map((item) => ({
604
+ ...item,
605
+ }));
606
+ const tags = processedFile[1];
607
+ const tagGroups = processedFile[2];
608
+ return [itemsObjectsArray, tags, tagGroups];
609
+ }
610
+ console.warn(
611
+ chalk.yellow(
612
+ `WARNING: the following OpenAPI spec returned undefined: ${file.source}`
613
+ )
614
+ );
615
+ return [];
616
+ });
617
+ const metadata = await Promise.all(promises);
618
+ const items = metadata
619
+ .map(function (x) {
620
+ return x[0];
621
+ })
622
+ .flat()
623
+ .filter(function (x) {
624
+ // Remove undefined items due to transient parsing errors
625
+ return x !== undefined;
626
+ });
627
+
628
+ const tags = metadata
629
+ .map(function (x) {
630
+ return x[1];
631
+ })
632
+ .filter(function (x) {
633
+ // Remove undefined tags due to transient parsing errors
634
+ return x !== undefined;
635
+ });
636
+
637
+ const tagGroups = metadata
638
+ .map(function (x) {
639
+ return x[2];
640
+ })
641
+ .flat()
642
+ .filter(function (x) {
643
+ // Remove undefined tags due to transient parsing errors
644
+ return x !== undefined;
645
+ });
646
+
647
+ return [
648
+ items as ApiMetadata[],
649
+ tags as TagObject[][],
650
+ tagGroups as TagGroupObject[],
651
+ ];
652
+ }
653
+
654
+ export async function processOpenapiFile(
655
+ openapiData: OpenApiObject,
656
+ options: APIOptions,
657
+ sidebarOptions: SidebarOptions
658
+ ): Promise<[ApiMetadata[], TagObject[], TagGroupObject[]]> {
659
+ const postmanCollection = await createPostmanCollection(openapiData);
660
+ const items = createItems(openapiData, options, sidebarOptions);
661
+
662
+ bindCollectionToApiItems(items, postmanCollection);
663
+
664
+ let tags: TagObject[] = [];
665
+ if (openapiData.tags !== undefined) {
666
+ tags = openapiData.tags;
667
+ }
668
+
669
+ let tagGroups: TagGroupObject[] = [];
670
+ if (openapiData["x-tagGroups"] !== undefined) {
671
+ tagGroups = openapiData["x-tagGroups"];
672
+ }
673
+
674
+ return [items, tags, tagGroups];
675
+ }
676
+
677
+ // order for picking items as a display name of tags
678
+ const tagDisplayNameProperties = ["x-displayName", "name"] as const;
679
+
680
+ export function getTagDisplayName(tagName: string, tags: TagObject[]): string {
681
+ // find the very own tagObject
682
+ const tagObject = tags.find((tagObject) => tagObject.name === tagName) ?? {
683
+ // if none found, just fake one
684
+ name: tagName,
685
+ };
686
+
687
+ // return the first found and filled value from the property list
688
+ for (const property of tagDisplayNameProperties) {
689
+ const displayName = tagObject[property];
690
+ if (typeof displayName === "string") {
691
+ return displayName;
692
+ }
693
+ }
694
+
695
+ // always default to the tagName
696
+ return tagName;
697
+ }