docusaurus-plugin-openapi-docs 1.0.5 → 1.0.6

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/README.md CHANGED
@@ -81,7 +81,7 @@ Here is an example of properly configuring your `docusaurus.config.js` file for
81
81
  'docusaurus-plugin-openapi-docs',
82
82
  {
83
83
  id: "apiDocs",
84
- docPluginId: "classic",
84
+ docsPluginId: "classic",
85
85
  config: {
86
86
  petstore: { // Note: petstore key is treated as the <id> and can be used to specify an API doc instance when using CLI commands
87
87
  specPath: "examples/petstore.yaml", // Path to designated spec file
@@ -108,10 +108,10 @@ Here is an example of properly configuring your `docusaurus.config.js` file for
108
108
 
109
109
  The `docusaurus-plugin-openapi-docs` plugin can be configured with the following options:
110
110
 
111
- | Name | Type | Default | Description |
112
- | ------------- | -------- | ------- | ---------------------------------------------------------------------------------------------------------------------------------------------------- |
113
- | `id` | `string` | `null` | A unique document id. |
114
- | `docPluginId` | `string` | `null` | The ID associated with the `plugin-content-docs` or `preset` instance used to render the OpenAPI docs (e.g. "your-plugin-id", "classic", "default"). |
111
+ | Name | Type | Default | Description |
112
+ | -------------- | -------- | ------- | ---------------------------------------------------------------------------------------------------------------------------------------------------- |
113
+ | `id` | `string` | `null` | A unique document id. |
114
+ | `docsPluginId` | `string` | `null` | The ID associated with the `plugin-content-docs` or `preset` instance used to render the OpenAPI docs (e.g. "your-plugin-id", "classic", "default"). |
115
115
 
116
116
  ### config
117
117
 
package/lib/index.js CHANGED
@@ -54,13 +54,13 @@ function getDocsData(dataArray, filter) {
54
54
  }
55
55
  exports.getDocsData = getDocsData;
56
56
  function pluginOpenAPIDocs(context, options) {
57
- const { config, docPluginId } = options;
57
+ const { config, docsPluginId } = options;
58
58
  const { siteDir, siteConfig } = context;
59
59
  // Get routeBasePath and path from plugin-content-docs or preset
60
60
  const presets = siteConfig.presets;
61
61
  const plugins = siteConfig.plugins;
62
62
  const presetsPlugins = presets.concat(plugins);
63
- const docData = getDocsData(presetsPlugins, docPluginId);
63
+ const docData = getDocsData(presetsPlugins, docsPluginId);
64
64
  const docRouteBasePath = docData ? docData.routeBasePath : undefined;
65
65
  const docPath = docData ? (docData.path ? docData.path : "docs") : undefined;
66
66
  async function generateApiDocs(options) {
@@ -69,7 +69,7 @@ function pluginOpenAPIDocs(context, options) {
69
69
  ? specPath
70
70
  : path_1.default.resolve(siteDir, specPath);
71
71
  try {
72
- const openapiFiles = await (0, openapi_1.readOpenapiFiles)(contentPath, {});
72
+ const openapiFiles = await (0, openapi_1.readOpenapiFiles)(contentPath, options);
73
73
  const [loadedApi, tags] = await (0, openapi_1.processOpenapiFiles)(openapiFiles, sidebarOptions);
74
74
  if (!fs_1.default.existsSync(outputDir)) {
75
75
  try {
@@ -1,11 +1,11 @@
1
- import { ApiMetadata, SidebarOptions } from "../types";
1
+ import { ApiMetadata, APIOptions, SidebarOptions } from "../types";
2
2
  import { OpenApiObjectWithRef, TagObject } from "./types";
3
3
  interface OpenApiFiles {
4
4
  source: string;
5
5
  sourceDirName: string;
6
6
  data: OpenApiObjectWithRef;
7
7
  }
8
- export declare function readOpenapiFiles(openapiPath: string, _options: {}): Promise<OpenApiFiles[]>;
8
+ export declare function readOpenapiFiles(openapiPath: string, options: APIOptions): Promise<OpenApiFiles[]>;
9
9
  export declare function processOpenapiFiles(files: OpenApiFiles[], sidebarOptions: SidebarOptions): Promise<[ApiMetadata[], TagObject[]]>;
10
10
  export declare function processOpenapiFile(openapiDataWithRefs: OpenApiObjectWithRef, sidebarOptions: SidebarOptions): Promise<[ApiMetadata[], TagObject[]]>;
11
11
  export declare function getTagDisplayName(tagName: string, tags: TagObject[]): string;
@@ -188,7 +188,10 @@ function bindCollectionToApiItems(items, postmanCollection) {
188
188
  }
189
189
  });
190
190
  }
191
- async function readOpenapiFiles(openapiPath, _options) {
191
+ async function readOpenapiFiles(openapiPath, options) {
192
+ // TODO: determine if this should be an API option
193
+ // Forces the json-schema-ref-parser
194
+ const parseJsonRefs = true;
192
195
  if (!(0, index_1.isURL)(openapiPath)) {
193
196
  const stat = await fs_extra_1.default.lstat(openapiPath);
194
197
  if (stat.isDirectory()) {
@@ -203,7 +206,7 @@ async function readOpenapiFiles(openapiPath, _options) {
203
206
  return Promise.all(sources.map(async (source) => {
204
207
  // TODO: make a function for this
205
208
  const fullPath = path_1.default.join(openapiPath, source);
206
- const data = (await (0, loadAndBundleSpec_1.loadAndBundleSpec)(fullPath));
209
+ const data = (await (0, loadAndBundleSpec_1.loadAndBundleSpec)(fullPath, parseJsonRefs));
207
210
  return {
208
211
  source: fullPath,
209
212
  sourceDirName: path_1.default.dirname(source),
@@ -212,7 +215,7 @@ async function readOpenapiFiles(openapiPath, _options) {
212
215
  }));
213
216
  }
214
217
  }
215
- const data = (await (0, loadAndBundleSpec_1.loadAndBundleSpec)(openapiPath));
218
+ const data = (await (0, loadAndBundleSpec_1.loadAndBundleSpec)(openapiPath, parseJsonRefs));
216
219
  return [
217
220
  {
218
221
  source: openapiPath,
@@ -15,7 +15,7 @@ const _1 = require(".");
15
15
  describe("openapi", () => {
16
16
  describe("readOpenapiFiles", () => {
17
17
  it("readOpenapiFiles", async () => {
18
- const results = await (0, _1.readOpenapiFiles)(path_1.default.join(__dirname, "__fixtures__/examples"), {});
18
+ const results = await (0, _1.readOpenapiFiles)(path_1.default.join(__dirname, "__fixtures__/examples"), { specPath: "./", outputDir: "./" });
19
19
  const categoryMeta = results.find((x) => x.source.endsWith("_category_.json"));
20
20
  expect(categoryMeta).toBeFalsy();
21
21
  // console.log(results);
@@ -1,3 +1,3 @@
1
1
  import { OpenAPISpec } from "./types";
2
- export declare function loadAndBundleSpec(specUrlOrObject: object | string): Promise<OpenAPISpec>;
2
+ export declare function loadAndBundleSpec(specUrlOrObject: object | string, parseJsonRefs: boolean | undefined): Promise<OpenAPISpec>;
3
3
  export declare function convertSwagger2OpenAPI(spec: any): Promise<OpenAPISpec>;
@@ -5,12 +5,39 @@
5
5
  * This source code is licensed under the MIT license found in the
6
6
  * LICENSE file in the root directory of this source tree.
7
7
  * ========================================================================== */
8
+ var __importDefault = (this && this.__importDefault) || function (mod) {
9
+ return (mod && mod.__esModule) ? mod : { "default": mod };
10
+ };
8
11
  Object.defineProperty(exports, "__esModule", { value: true });
9
12
  exports.convertSwagger2OpenAPI = exports.loadAndBundleSpec = void 0;
13
+ // @ts-nocheck
14
+ const json_schema_ref_parser_1 = __importDefault(require("@apidevtools/json-schema-ref-parser"));
10
15
  const bundle_1 = require("@redocly/openapi-core/lib/bundle");
11
16
  const config_1 = require("@redocly/openapi-core/lib/config/config");
17
+ const chalk_1 = __importDefault(require("chalk"));
12
18
  const swagger2openapi_1 = require("swagger2openapi");
13
- async function loadAndBundleSpec(specUrlOrObject) {
19
+ async function resolveJsonRefs(specUrlOrObject) {
20
+ var _a, _b;
21
+ try {
22
+ let schema = await json_schema_ref_parser_1.default.dereference(specUrlOrObject, {
23
+ continueOnError: true,
24
+ resolve: {
25
+ http: {
26
+ timeout: 15000, // 15 sec timeout
27
+ },
28
+ },
29
+ dereference: {
30
+ circular: "ignore",
31
+ },
32
+ });
33
+ return schema;
34
+ }
35
+ catch (err) {
36
+ console.error(chalk_1.default.yellow((_b = (_a = err.errors[0]) === null || _a === void 0 ? void 0 : _a.message) !== null && _b !== void 0 ? _b : err));
37
+ return;
38
+ }
39
+ }
40
+ async function loadAndBundleSpec(specUrlOrObject, parseJsonRefs) {
14
41
  const config = new config_1.Config({});
15
42
  const bundleOpts = {
16
43
  config,
@@ -28,6 +55,14 @@ async function loadAndBundleSpec(specUrlOrObject) {
28
55
  // Force dereference ?
29
56
  // bundleOpts["dereference"] = true;
30
57
  const { bundle: { parsed }, } = await (0, bundle_1.bundle)(bundleOpts);
58
+ if (parseJsonRefs) {
59
+ const resolved = resolveJsonRefs(parsed);
60
+ return typeof resolved === Object
61
+ ? resolved.swagger !== undefined
62
+ ? convertSwagger2OpenAPI(resolved)
63
+ : resolved
64
+ : parsed;
65
+ }
31
66
  return parsed.swagger !== undefined ? convertSwagger2OpenAPI(parsed) : parsed;
32
67
  }
33
68
  exports.loadAndBundleSpec = loadAndBundleSpec;
package/lib/options.js CHANGED
@@ -17,7 +17,7 @@ const sidebarOptions = utils_validation_1.Joi.object({
17
17
  });
18
18
  exports.OptionsSchema = utils_validation_1.Joi.object({
19
19
  id: utils_validation_1.Joi.string().required(),
20
- docPluginId: utils_validation_1.Joi.string().required(),
20
+ docsPluginId: utils_validation_1.Joi.string().required(),
21
21
  config: utils_validation_1.Joi.object()
22
22
  .pattern(/^/, utils_validation_1.Joi.object({
23
23
  specPath: utils_validation_1.Joi.string().required(),
@@ -19,7 +19,7 @@ function isInfoItem(item) {
19
19
  return item.type === "info";
20
20
  }
21
21
  function groupByTags(items, sidebarOptions, options, tags, docPath) {
22
- const { outputDir } = options;
22
+ const { outputDir, label } = options;
23
23
  const { sidebarCollapsed, sidebarCollapsible, customProps, categoryLinkSource, } = sidebarOptions;
24
24
  const apiItems = items.filter(isApiItem);
25
25
  const infoItems = items.filter(isInfoItem);
@@ -95,7 +95,9 @@ function groupByTags(items, sidebarOptions, options, tags, docPath) {
95
95
  linkConfig = {
96
96
  type: "generated-index",
97
97
  title: tag,
98
- slug: "/category/" + (0, lodash_1.kebabCase)(tag),
98
+ slug: label
99
+ ? "/category/" + (0, lodash_1.kebabCase)(label) + "/" + (0, lodash_1.kebabCase)(tag)
100
+ : "/category/" + (0, lodash_1.kebabCase)(tag),
99
101
  };
100
102
  }
101
103
  return {
package/lib/types.d.ts CHANGED
@@ -3,7 +3,7 @@ import { InfoObject, OperationObject, SecuritySchemeObject, TagObject } from "./
3
3
  export type { PropSidebarItemCategory, SidebarItemLink, PropSidebar, PropSidebarItem, } from "@docusaurus/plugin-content-docs-types";
4
4
  export interface PluginOptions {
5
5
  id?: string;
6
- docPluginId: string;
6
+ docsPluginId: string;
7
7
  config: {
8
8
  [key: string]: APIOptions;
9
9
  };
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.5",
4
+ "version": "1.0.6",
5
5
  "license": "MIT",
6
6
  "keywords": [
7
7
  "openapi",
@@ -36,6 +36,7 @@
36
36
  "utility-types": "^3.10.0"
37
37
  },
38
38
  "dependencies": {
39
+ "@apidevtools/json-schema-ref-parser": "^9.0.9",
39
40
  "@docusaurus/mdx-loader": "2.0.0-beta.21",
40
41
  "@docusaurus/plugin-content-docs": "2.0.0-beta.21",
41
42
  "@docusaurus/utils": "2.0.0-beta.21",
@@ -62,5 +63,5 @@
62
63
  "engines": {
63
64
  "node": ">=14"
64
65
  },
65
- "gitHead": "fe58997d349cdab4f113c4a7449cd650882dcab6"
66
+ "gitHead": "ff7622e334e96e7f39b0daf5ef2cb17bfa832834"
66
67
  }
package/src/index.ts CHANGED
@@ -62,14 +62,14 @@ export default function pluginOpenAPIDocs(
62
62
  context: LoadContext,
63
63
  options: PluginOptions
64
64
  ): Plugin<LoadedContent> {
65
- const { config, docPluginId } = options;
65
+ const { config, docsPluginId } = options;
66
66
  const { siteDir, siteConfig } = context;
67
67
 
68
68
  // Get routeBasePath and path from plugin-content-docs or preset
69
69
  const presets: any = siteConfig.presets;
70
70
  const plugins: any = siteConfig.plugins;
71
71
  const presetsPlugins = presets.concat(plugins);
72
- const docData: any = getDocsData(presetsPlugins, docPluginId);
72
+ const docData: any = getDocsData(presetsPlugins, docsPluginId);
73
73
  const docRouteBasePath = docData ? docData.routeBasePath : undefined;
74
74
  const docPath = docData ? (docData.path ? docData.path : "docs") : undefined;
75
75
 
@@ -81,7 +81,7 @@ export default function pluginOpenAPIDocs(
81
81
  : path.resolve(siteDir, specPath);
82
82
 
83
83
  try {
84
- const openapiFiles = await readOpenapiFiles(contentPath, {});
84
+ const openapiFiles = await readOpenapiFiles(contentPath, options);
85
85
  const [loadedApi, tags] = await processOpenapiFiles(
86
86
  openapiFiles,
87
87
  sidebarOptions!
@@ -16,7 +16,7 @@ describe("openapi", () => {
16
16
  it("readOpenapiFiles", async () => {
17
17
  const results = await readOpenapiFiles(
18
18
  path.join(__dirname, "__fixtures__/examples"),
19
- {}
19
+ { specPath: "./", outputDir: "./" }
20
20
  );
21
21
  const categoryMeta = results.find((x) =>
22
22
  x.source.endsWith("_category_.json")
@@ -19,6 +19,7 @@ import { kebabCase } from "lodash";
19
19
  import { isURL } from "../index";
20
20
  import {
21
21
  ApiMetadata,
22
+ APIOptions,
22
23
  ApiPageMetadata,
23
24
  InfoPageMetadata,
24
25
  SidebarOptions,
@@ -247,8 +248,12 @@ interface OpenApiFiles {
247
248
 
248
249
  export async function readOpenapiFiles(
249
250
  openapiPath: string,
250
- _options: {}
251
+ options: APIOptions
251
252
  ): Promise<OpenApiFiles[]> {
253
+ // TODO: determine if this should be an API option
254
+ // Forces the json-schema-ref-parser
255
+ const parseJsonRefs = true;
256
+
252
257
  if (!isURL(openapiPath)) {
253
258
  const stat = await fs.lstat(openapiPath);
254
259
  if (stat.isDirectory()) {
@@ -270,7 +275,8 @@ export async function readOpenapiFiles(
270
275
  // TODO: make a function for this
271
276
  const fullPath = path.join(openapiPath, source);
272
277
  const data = (await loadAndBundleSpec(
273
- fullPath
278
+ fullPath,
279
+ parseJsonRefs
274
280
  )) as OpenApiObjectWithRef;
275
281
  return {
276
282
  source: fullPath, // This will be aliased in process.
@@ -281,7 +287,10 @@ export async function readOpenapiFiles(
281
287
  );
282
288
  }
283
289
  }
284
- const data = (await loadAndBundleSpec(openapiPath)) as OpenApiObjectWithRef;
290
+ const data = (await loadAndBundleSpec(
291
+ openapiPath,
292
+ parseJsonRefs
293
+ )) as OpenApiObjectWithRef;
285
294
  return [
286
295
  {
287
296
  source: openapiPath, // This will be aliased in process.
@@ -7,16 +7,39 @@
7
7
 
8
8
  // @ts-nocheck
9
9
 
10
+ import $RefParser from "@apidevtools/json-schema-ref-parser";
10
11
  import type { Source, Document } from "@redocly/openapi-core";
11
12
  import { bundle } from "@redocly/openapi-core/lib/bundle";
12
13
  import type { ResolvedConfig } from "@redocly/openapi-core/lib/config";
13
14
  import { Config } from "@redocly/openapi-core/lib/config/config";
15
+ import chalk from "chalk";
14
16
  import { convertObj } from "swagger2openapi";
15
17
 
16
18
  import { OpenAPISpec } from "./types";
17
19
 
20
+ async function resolveJsonRefs(specUrlOrObject: object | string) {
21
+ try {
22
+ let schema = await $RefParser.dereference(specUrlOrObject, {
23
+ continueOnError: true,
24
+ resolve: {
25
+ http: {
26
+ timeout: 15000, // 15 sec timeout
27
+ },
28
+ },
29
+ dereference: {
30
+ circular: "ignore",
31
+ },
32
+ });
33
+ return schema;
34
+ } catch (err) {
35
+ console.error(chalk.yellow(err.errors[0]?.message ?? err));
36
+ return;
37
+ }
38
+ }
39
+
18
40
  export async function loadAndBundleSpec(
19
- specUrlOrObject: object | string
41
+ specUrlOrObject: object | string,
42
+ parseJsonRefs: boolean | undefined
20
43
  ): Promise<OpenAPISpec> {
21
44
  const config = new Config({} as ResolvedConfig);
22
45
  const bundleOpts = {
@@ -39,6 +62,14 @@ export async function loadAndBundleSpec(
39
62
  const {
40
63
  bundle: { parsed },
41
64
  } = await bundle(bundleOpts);
65
+ if (parseJsonRefs) {
66
+ const resolved = resolveJsonRefs(parsed);
67
+ return typeof resolved === Object
68
+ ? resolved.swagger !== undefined
69
+ ? convertSwagger2OpenAPI(resolved)
70
+ : resolved
71
+ : parsed;
72
+ }
42
73
  return parsed.swagger !== undefined ? convertSwagger2OpenAPI(parsed) : parsed;
43
74
  }
44
75
 
package/src/options.ts CHANGED
@@ -17,7 +17,7 @@ const sidebarOptions = Joi.object({
17
17
 
18
18
  export const OptionsSchema = Joi.object({
19
19
  id: Joi.string().required(),
20
- docPluginId: Joi.string().required(),
20
+ docsPluginId: Joi.string().required(),
21
21
  config: Joi.object()
22
22
  .pattern(
23
23
  /^/,
@@ -38,7 +38,7 @@ function groupByTags(
38
38
  tags: TagObject[],
39
39
  docPath: string
40
40
  ): ProcessedSidebar {
41
- const { outputDir } = options;
41
+ const { outputDir, label } = options;
42
42
  const {
43
43
  sidebarCollapsed,
44
44
  sidebarCollapsible,
@@ -135,7 +135,9 @@ function groupByTags(
135
135
  linkConfig = {
136
136
  type: "generated-index" as "generated-index",
137
137
  title: tag,
138
- slug: "/category/" + kebabCase(tag),
138
+ slug: label
139
+ ? "/category/" + kebabCase(label) + "/" + kebabCase(tag)
140
+ : "/category/" + kebabCase(tag),
139
141
  } as SidebarItemCategoryLinkConfig;
140
142
  }
141
143
 
package/src/types.ts CHANGED
@@ -22,7 +22,7 @@ export type {
22
22
  } from "@docusaurus/plugin-content-docs-types";
23
23
  export interface PluginOptions {
24
24
  id?: string;
25
- docPluginId: string;
25
+ docsPluginId: string;
26
26
  config: {
27
27
  [key: string]: APIOptions;
28
28
  };