docusaurus-plugin-openapi-docs 1.0.2 → 1.0.3

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/types.d.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  import type Request from "@paloaltonetworks/postman-collection";
2
- import { InfoObject, OperationObject, SecuritySchemeObject } from "./openapi/types";
2
+ import { InfoObject, OperationObject, SecuritySchemeObject, TagObject } from "./openapi/types";
3
3
  export type { PropSidebarItemCategory, SidebarItemLink, PropSidebar, PropSidebarItem, } from "@docusaurus/plugin-content-docs-types";
4
4
  export interface PluginOptions {
5
5
  id?: string;
@@ -16,13 +16,15 @@ export interface APIOptions {
16
16
  export interface LoadedContent {
17
17
  loadedApi: ApiMetadata[];
18
18
  }
19
- export declare type ApiMetadata = ApiPageMetadata | InfoPageMetadata;
19
+ export declare type ApiMetadata = ApiPageMetadata | InfoPageMetadata | TagPageMetadata;
20
20
  export interface ApiMetadataBase {
21
21
  sidebar?: string;
22
22
  previous?: ApiNavLink;
23
23
  next?: ApiNavLink;
24
24
  id: string;
25
25
  unversionedId: string;
26
+ infoId?: string;
27
+ infoPath?: string;
26
28
  title: string;
27
29
  description: string;
28
30
  source: string;
@@ -52,6 +54,14 @@ export interface InfoPageMetadata extends ApiMetadataBase {
52
54
  type: "info";
53
55
  info: ApiInfo;
54
56
  markdown?: string;
57
+ securitySchemes?: {
58
+ [key: string]: SecuritySchemeObject;
59
+ };
60
+ }
61
+ export interface TagPageMetadata extends ApiMetadataBase {
62
+ type: "tag";
63
+ tag: TagObject;
64
+ markdown?: string;
55
65
  }
56
66
  export declare type ApiInfo = InfoObject;
57
67
  export interface ApiNavLink {
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": "1.0.2",
4
+ "version": "1.0.3",
5
5
  "license": "MIT",
6
6
  "keywords": [
7
7
  "openapi",
@@ -28,20 +28,21 @@
28
28
  "watch": "tsc --watch"
29
29
  },
30
30
  "devDependencies": {
31
- "@docusaurus/module-type-aliases": "2.0.0-beta.18",
32
- "@docusaurus/types": "2.0.0-beta.18",
31
+ "@docusaurus/module-type-aliases": "2.0.0-beta.21",
32
+ "@docusaurus/types": "2.0.0-beta.21",
33
33
  "@types/fs-extra": "^9.0.13",
34
34
  "@types/json-schema": "^7.0.9",
35
35
  "@types/lodash": "^4.14.176",
36
36
  "utility-types": "^3.10.0"
37
37
  },
38
38
  "dependencies": {
39
- "@docusaurus/mdx-loader": "2.0.0-beta.18",
40
- "@docusaurus/plugin-content-docs": "2.0.0-beta.18",
41
- "@docusaurus/utils": "2.0.0-beta.18",
42
- "@docusaurus/utils-validation": "2.0.0-beta.18",
39
+ "@docusaurus/mdx-loader": "2.0.0-beta.21",
40
+ "@docusaurus/plugin-content-docs": "2.0.0-beta.21",
41
+ "@docusaurus/utils": "2.0.0-beta.21",
42
+ "@docusaurus/utils-validation": "2.0.0-beta.21",
43
43
  "@paloaltonetworks/openapi-to-postmanv2": "3.1.0-hotfix.1",
44
44
  "@paloaltonetworks/postman-collection": "^4.1.0",
45
+ "@redocly/openapi-core": "^1.0.0-beta.100",
45
46
  "@types/js-yaml": "^4.0.5",
46
47
  "@types/mustache": "^4.1.2",
47
48
  "chalk": "^4.1.2",
@@ -52,6 +53,7 @@
52
53
  "json-schema-merge-allof": "^0.8.1",
53
54
  "lodash": "^4.17.20",
54
55
  "mustache": "^4.2.0",
56
+ "swagger2openapi": "^7.0.8",
55
57
  "webpack": "^5.61.0"
56
58
  },
57
59
  "peerDependencies": {
@@ -60,5 +62,5 @@
60
62
  "engines": {
61
63
  "node": ">=14"
62
64
  },
63
- "gitHead": "526f2d10bc187bc7095ac70d5b1453b9b92c114f"
65
+ "gitHead": "618a438ec42148f525fb92cb11d291ce50d2ad06"
64
66
  }
package/src/index.ts CHANGED
@@ -13,11 +13,15 @@ import { Globby } from "@docusaurus/utils";
13
13
  import chalk from "chalk";
14
14
  import { render } from "mustache";
15
15
 
16
- import { createApiPageMD, createInfoPageMD } from "./markdown";
16
+ import { createApiPageMD, createInfoPageMD, createTagPageMD } from "./markdown";
17
17
  import { readOpenapiFiles, processOpenapiFiles } from "./openapi";
18
18
  import generateSidebarSlice from "./sidebars";
19
19
  import type { PluginOptions, LoadedContent, APIOptions } from "./types";
20
20
 
21
+ export function isURL(str: string): boolean {
22
+ return /^(https?:)\/\//m.test(str);
23
+ }
24
+
21
25
  export default function pluginOpenAPI(
22
26
  context: LoadContext,
23
27
  options: PluginOptions
@@ -28,11 +32,16 @@ export default function pluginOpenAPI(
28
32
  async function generateApiDocs(options: APIOptions) {
29
33
  let { specPath, outputDir, template, sidebarOptions } = options;
30
34
 
31
- const contentPath = path.resolve(siteDir, specPath);
35
+ const contentPath = isURL(specPath)
36
+ ? specPath
37
+ : path.resolve(siteDir, specPath);
32
38
 
33
39
  try {
34
40
  const openapiFiles = await readOpenapiFiles(contentPath, {});
35
- const [loadedApi, tags] = await processOpenapiFiles(openapiFiles);
41
+ const [loadedApi, tags] = await processOpenapiFiles(
42
+ openapiFiles,
43
+ sidebarOptions!
44
+ );
36
45
  if (!fs.existsSync(outputDir)) {
37
46
  try {
38
47
  fs.mkdirSync(outputDir, { recursive: true });
@@ -100,6 +109,9 @@ api: {{{json}}}
100
109
  {{#api.method}}
101
110
  sidebar_class_name: "{{{api.method}}} api-method"
102
111
  {{/api.method}}
112
+ {{#infoPath}}
113
+ info_path: {{{infoPath}}}
114
+ {{/infoPath}}
103
115
  ---
104
116
 
105
117
  {{{markdown}}}
@@ -119,19 +131,45 @@ hide_title: true
119
131
  import DocCardList from '@theme/DocCardList';
120
132
  import {useCurrentSidebarCategory} from '@docusaurus/theme-common';
121
133
 
134
+ <DocCardList items={useCurrentSidebarCategory().items}/>
135
+ \`\`\`
136
+ `;
137
+
138
+ const tagMdTemplate = template
139
+ ? fs.readFileSync(template).toString()
140
+ : `---
141
+ id: {{{id}}}
142
+ title: {{{description}}}
143
+ description: {{{description}}}
144
+ ---
145
+
146
+ {{{markdown}}}
147
+
148
+ \`\`\`mdx-code-block
149
+ import DocCardList from '@theme/DocCardList';
150
+ import {useCurrentSidebarCategory} from '@docusaurus/theme-common';
151
+
122
152
  <DocCardList items={useCurrentSidebarCategory().items}/>
123
153
  \`\`\`
124
154
  `;
125
155
 
126
156
  loadedApi.map(async (item) => {
127
157
  const markdown =
128
- item.type === "api" ? createApiPageMD(item) : createInfoPageMD(item);
158
+ item.type === "api"
159
+ ? createApiPageMD(item)
160
+ : item.type === "info"
161
+ ? createInfoPageMD(item)
162
+ : createTagPageMD(item);
129
163
  item.markdown = markdown;
130
164
  if (item.type === "api") {
131
165
  item.json = JSON.stringify(item.api);
166
+ if (item.infoId) item.infoPath = `${outputDir}/${item.infoId}`;
132
167
  }
168
+
133
169
  const view = render(mdTemplate, item);
134
170
  const utils = render(infoMdTemplate, item);
171
+ // eslint-disable-next-line testing-library/render-result-naming-convention
172
+ const tagUtils = render(tagMdTemplate, item);
135
173
 
136
174
  if (item.type === "api") {
137
175
  if (!fs.existsSync(`${outputDir}/${item.id}.api.mdx`)) {
@@ -179,8 +217,31 @@ import {useCurrentSidebarCategory} from '@docusaurus/theme-common';
179
217
  }
180
218
  }
181
219
  }
220
+
221
+ if (item.type === "tag") {
222
+ if (!fs.existsSync(`${outputDir}/${item.id}.tag.mdx`)) {
223
+ try {
224
+ fs.writeFileSync(
225
+ `${outputDir}/${item.id}.tag.mdx`,
226
+ tagUtils,
227
+ "utf8"
228
+ );
229
+ console.log(
230
+ chalk.green(
231
+ `Successfully created "${outputDir}/${item.id}.tag.mdx"`
232
+ )
233
+ );
234
+ } catch (err) {
235
+ console.error(
236
+ chalk.red(`Failed to write "${outputDir}/${item.id}.tag.mdx"`),
237
+ chalk.yellow(err)
238
+ );
239
+ }
240
+ }
241
+ }
182
242
  return;
183
243
  });
244
+
184
245
  return;
185
246
  } catch (e) {
186
247
  console.error(chalk.red(`Loading of api failed for "${contentPath}"`));
@@ -191,7 +252,7 @@ import {useCurrentSidebarCategory} from '@docusaurus/theme-common';
191
252
  async function cleanApiDocs(options: APIOptions) {
192
253
  const { outputDir } = options;
193
254
  const apiDir = path.join(siteDir, outputDir);
194
- const apiMdxFiles = await Globby(["*.api.mdx", "*.info.mdx"], {
255
+ const apiMdxFiles = await Globby(["*.api.mdx", "*.info.mdx", "*.tag.mdx"], {
195
256
  cwd: path.resolve(apiDir),
196
257
  });
197
258
  const sidebarFile = await Globby(["sidebar.js"], {
@@ -0,0 +1,160 @@
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 { OAuthFlowObject, SecuritySchemeObject } from "../openapi/types";
9
+ import { createDescription } from "./createDescription";
10
+ import { create, guard } from "./utils";
11
+
12
+ export function createAuthentication(securitySchemes: SecuritySchemeObject) {
13
+ if (!securitySchemes || !Object.keys(securitySchemes).length) return "";
14
+
15
+ const createAuthenticationTable = (securityScheme: any) => {
16
+ const { bearerFormat, flows, name, scheme, type } = securityScheme;
17
+
18
+ const createSecuritySchemeTypeRow = () =>
19
+ create("tr", {
20
+ children: [
21
+ create("th", { children: "Security Scheme Type:" }),
22
+ create("td", { children: type }),
23
+ ],
24
+ });
25
+
26
+ const createOAuthFlowRows = () => {
27
+ const flowRows = Object.entries(flows).map(([flowType, flowObj]) => {
28
+ const { authorizationUrl, tokenUrl, refreshUrl, scopes } =
29
+ flowObj as OAuthFlowObject;
30
+
31
+ return create("tr", {
32
+ children: [
33
+ create("th", { children: `${flowType} OAuth Flow:` }),
34
+ create("td", {
35
+ children: [
36
+ guard(tokenUrl, () =>
37
+ create("p", { children: `Token URL: ${tokenUrl}` })
38
+ ),
39
+ guard(authorizationUrl, () =>
40
+ create("p", {
41
+ children: `Authorization URL: ${authorizationUrl}`,
42
+ })
43
+ ),
44
+ guard(refreshUrl, () =>
45
+ create("p", { children: `Refresh URL: ${refreshUrl}` })
46
+ ),
47
+ create("span", { children: "Scopes:" }),
48
+ create("ul", {
49
+ children: Object.entries(scopes).map(([scope, description]) =>
50
+ create("li", { children: `${scope}: ${description}` })
51
+ ),
52
+ }),
53
+ ],
54
+ }),
55
+ ],
56
+ });
57
+ });
58
+
59
+ return flowRows.join("");
60
+ };
61
+
62
+ switch (type) {
63
+ case "apiKey":
64
+ return create("div", {
65
+ children: [
66
+ create("table", {
67
+ children: create("tbody", {
68
+ children: [
69
+ createSecuritySchemeTypeRow(),
70
+ create("tr", {
71
+ children: [
72
+ create("th", { children: "Header parameter name:" }),
73
+ create("td", { children: name }),
74
+ ],
75
+ }),
76
+ ],
77
+ }),
78
+ }),
79
+ ],
80
+ });
81
+ case "http":
82
+ return create("div", {
83
+ children: [
84
+ create("table", {
85
+ children: create("tbody", {
86
+ children: [
87
+ createSecuritySchemeTypeRow(),
88
+ create("tr", {
89
+ children: [
90
+ create("th", { children: "HTTP Authorization Scheme:" }),
91
+ create("td", { children: scheme }),
92
+ ],
93
+ }),
94
+ create("tr", {
95
+ children: [
96
+ create("th", { children: "Bearer format:" }),
97
+ create("td", { children: bearerFormat }),
98
+ ],
99
+ }),
100
+ ],
101
+ }),
102
+ }),
103
+ ],
104
+ });
105
+ case "oauth2":
106
+ return create("div", {
107
+ children: [
108
+ create("table", {
109
+ children: create("tbody", {
110
+ children: [
111
+ createSecuritySchemeTypeRow(),
112
+ createOAuthFlowRows(),
113
+ ],
114
+ }),
115
+ }),
116
+ ],
117
+ });
118
+ default:
119
+ return "";
120
+ }
121
+ };
122
+
123
+ const formatTabLabel = (str: string) => {
124
+ const formattedLabel = str
125
+ .replace(/(_|-)/g, " ")
126
+ .trim()
127
+ .replace(/\w\S*/g, (str) => str.charAt(0).toUpperCase() + str.substr(1))
128
+ .replace(/([a-z])([A-Z])/g, "$1 $2")
129
+ .replace(/([A-Z])([A-Z][a-z])/g, "$1 $2");
130
+
131
+ const isOAuth = formattedLabel.toLowerCase().includes("oauth2");
132
+ const isApiKey = formattedLabel.toLowerCase().includes("api");
133
+
134
+ return isOAuth ? "OAuth 2.0" : isApiKey ? "API Key" : formattedLabel;
135
+ };
136
+
137
+ return create("div", {
138
+ children: [
139
+ create("h2", {
140
+ children: "Authentication",
141
+ id: "authentication",
142
+ style: { marginBottom: "1rem" },
143
+ }),
144
+ create("Tabs", {
145
+ children: Object.entries(securitySchemes).map(
146
+ ([schemeType, schemeObj]) =>
147
+ create("TabItem", {
148
+ label: formatTabLabel(schemeType),
149
+ value: `${schemeType}`,
150
+ children: [
151
+ createDescription(schemeObj.description),
152
+ createAuthenticationTable(schemeObj),
153
+ ],
154
+ })
155
+ ),
156
+ }),
157
+ ],
158
+ style: { marginBottom: "2rem" },
159
+ });
160
+ }
@@ -7,8 +7,13 @@
7
7
 
8
8
  import { escape } from "lodash";
9
9
 
10
- import { ContactObject, LicenseObject } from "../openapi/types";
11
- import { ApiPageMetadata, InfoPageMetadata } from "../types";
10
+ import {
11
+ ContactObject,
12
+ LicenseObject,
13
+ SecuritySchemeObject,
14
+ } from "../openapi/types";
15
+ import { ApiPageMetadata, InfoPageMetadata, TagPageMetadata } from "../types";
16
+ import { createAuthentication } from "./createAuthentication";
12
17
  import { createContactInfo } from "./createContactInfo";
13
18
  import { createDeprecationNotice } from "./createDeprecationNotice";
14
19
  import { createDescription } from "./createDescription";
@@ -50,13 +55,21 @@ export function createApiPageMD({
50
55
 
51
56
  export function createInfoPageMD({
52
57
  info: { title, version, description, contact, license, termsOfService },
58
+ securitySchemes,
53
59
  }: InfoPageMetadata) {
54
60
  return render([
61
+ `import Tabs from "@theme/Tabs";\n`,
62
+ `import TabItem from "@theme/TabItem";\n`,
55
63
  createVersionBadge(version),
56
64
  `# ${escape(title)}\n\n`,
57
65
  createDescription(description),
66
+ createAuthentication(securitySchemes as unknown as SecuritySchemeObject),
58
67
  createContactInfo(contact as ContactObject),
59
68
  createTermsOfService(termsOfService),
60
69
  createLicense(license as LicenseObject),
61
70
  ]);
62
71
  }
72
+
73
+ export function createTagPageMD({ tag: { description } }: TagPageMetadata) {
74
+ return render([createDescription(description)]);
75
+ }
@@ -5,6 +5,8 @@
5
5
  * LICENSE file in the root directory of this source tree.
6
6
  * ========================================================================== */
7
7
 
8
+ import chalk from "chalk";
9
+
8
10
  import { SchemaObject } from "./types";
9
11
 
10
12
  interface OASTypeToTypeMap {
@@ -48,71 +50,78 @@ const primitives: Primitives = {
48
50
  };
49
51
 
50
52
  export const sampleFromSchema = (schema: SchemaObject = {}): any => {
51
- let { type, example, allOf, properties, items } = schema;
53
+ try {
54
+ let { type, example, allOf, properties, items } = schema;
52
55
 
53
- if (example !== undefined) {
54
- return example;
55
- }
56
+ if (example !== undefined) {
57
+ return example;
58
+ }
56
59
 
57
- if (allOf) {
58
- // TODO: We are just assuming it will always be an object for now
59
- let obj: SchemaObject = {
60
- type: "object",
61
- properties: {},
62
- required: [], // NOTE: We shouldn't need to worry about required
63
- };
64
- for (let item of allOf) {
65
- if (item.properties) {
66
- obj.properties = {
67
- ...obj.properties,
68
- ...item.properties,
69
- };
60
+ if (allOf) {
61
+ // TODO: We are just assuming it will always be an object for now
62
+ let obj: SchemaObject = {
63
+ type: "object",
64
+ properties: {},
65
+ required: [], // NOTE: We shouldn't need to worry about required
66
+ };
67
+ for (let item of allOf) {
68
+ if (item.properties) {
69
+ obj.properties = {
70
+ ...obj.properties,
71
+ ...item.properties,
72
+ };
73
+ }
70
74
  }
75
+ return sampleFromSchema(obj);
71
76
  }
72
- return sampleFromSchema(obj);
73
- }
74
77
 
75
- if (!type) {
76
- if (properties) {
77
- type = "object";
78
- } else if (items) {
79
- type = "array";
80
- } else {
81
- return;
78
+ if (!type) {
79
+ if (properties) {
80
+ type = "object";
81
+ } else if (items) {
82
+ type = "array";
83
+ } else {
84
+ return;
85
+ }
82
86
  }
83
- }
84
87
 
85
- if (type === "object") {
86
- let obj: any = {};
87
- for (let [name, prop] of Object.entries(properties ?? {})) {
88
- if (prop.deprecated) {
89
- continue;
88
+ if (type === "object") {
89
+ let obj: any = {};
90
+ for (let [name, prop] of Object.entries(properties ?? {})) {
91
+ if (prop.deprecated) {
92
+ continue;
93
+ }
94
+ obj[name] = sampleFromSchema(prop);
90
95
  }
91
- obj[name] = sampleFromSchema(prop);
96
+ return obj;
92
97
  }
93
- return obj;
94
- }
95
98
 
96
- if (type === "array") {
97
- if (Array.isArray(items?.anyOf)) {
98
- return items?.anyOf.map((item) => sampleFromSchema(item));
99
- }
99
+ if (type === "array") {
100
+ if (Array.isArray(items?.anyOf)) {
101
+ return items?.anyOf.map((item) => sampleFromSchema(item));
102
+ }
100
103
 
101
- if (Array.isArray(items?.oneOf)) {
102
- return items?.oneOf.map((item) => sampleFromSchema(item));
103
- }
104
+ if (Array.isArray(items?.oneOf)) {
105
+ return items?.oneOf.map((item) => sampleFromSchema(item));
106
+ }
104
107
 
105
- return [sampleFromSchema(items)];
106
- }
108
+ return [sampleFromSchema(items)];
109
+ }
107
110
 
108
- if (schema.enum) {
109
- if (schema.default) {
110
- return schema.default;
111
+ if (schema.enum) {
112
+ if (schema.default) {
113
+ return schema.default;
114
+ }
115
+ return normalizeArray(schema.enum)[0];
111
116
  }
112
- return normalizeArray(schema.enum)[0];
113
- }
114
117
 
115
- return primitive(schema);
118
+ return primitive(schema);
119
+ } catch (err) {
120
+ console.error(
121
+ chalk.yellow("WARNING: failed to create example from schema object:", err)
122
+ );
123
+ return;
124
+ }
116
125
  };
117
126
 
118
127
  function primitive(schema: SchemaObject = {}) {
@@ -26,12 +26,6 @@ describe("openapi", () => {
26
26
  const yaml = results.find((x) => x.source.endsWith("openapi.yaml"));
27
27
  expect(yaml).toBeTruthy();
28
28
  expect(yaml?.sourceDirName).toBe(".");
29
- const froyo = results.find((x) => x.source.endsWith("froyo.yaml"));
30
- expect(froyo).toBeTruthy();
31
- expect(froyo?.sourceDirName).toBe("yogurtstore");
32
- const nested = results.find((x) => x.source.endsWith("nested.yaml"));
33
- expect(nested).toBeTruthy();
34
- expect(nested?.sourceDirName).toBe("yogurtstore/nested");
35
29
  });
36
30
  });
37
31
  });