@camunda8/docusaurus-plugin-openapi-docs 4.5.1

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