Socket
Socket
Sign inDemoInstall

docusaurus-plugin-openapi

Package Overview
Dependencies
917
Maintainers
1
Versions
38
Alerts
File Explorer

Advanced tools

Install Socket

Detect and block malicious and high-risk dependencies

Install

Comparing version 0.6.2 to 0.6.3

lib/docs/docs.d.ts

95

lib/index.js

@@ -16,2 +16,4 @@ "use strict";

const chalk_1 = __importDefault(require("chalk"));
const fs_extra_1 = __importDefault(require("fs-extra"));
const docs_1 = require("./docs/docs");
const markdown_1 = require("./markdown");

@@ -23,3 +25,3 @@ const openapi_1 = require("./openapi");

var _a;
const { baseUrl, generatedFilesDir } = context;
const { baseUrl, generatedFilesDir, siteDir } = context;
const pluginId = (_a = options.id) !== null && _a !== void 0 ? _a : utils_1.DEFAULT_PLUGIN_ID;

@@ -41,2 +43,23 @@ const pluginDataDirRoot = path_1.default.join(generatedFilesDir, "docusaurus-plugin-openapi");

const { routeBasePath } = options;
async function toMetadata(
/** E.g. "api/plugins/myDoc.mdx" */
relativeSource) {
const source = path_1.default.join(contentPath, relativeSource);
const content = await fs_extra_1.default.readFile(source, "utf-8");
return {
type: "mdx",
...(await (0, docs_1.processDocMetadata)({
docFile: {
contentPath: contentPath,
filePath: source,
source: relativeSource,
content: content,
},
relativeSource: relativeSource,
context: context,
options: options,
env: process.env.NODE_ENV,
})),
};
}
try {

@@ -49,2 +72,12 @@ const openapiFiles = await (0, openapi_1.readOpenapiFiles)(contentPath, {});

});
const pagesFiles = [];
if (!contentPath.endsWith(".json") &&
!contentPath.endsWith(".yaml") &&
!contentPath.endsWith(".yml")) {
pagesFiles.push(...(await (0, utils_1.Globby)(["**/*.{md,mdx}"], {
cwd: contentPath,
// ignore: options.exclude, // TODO
})));
}
loadedApi.push(...(await Promise.all(pagesFiles.map(toMetadata))));
return { loadedApi };

@@ -69,14 +102,31 @@ }

const pageId = `site-${routeBasePath}-${item.id}`;
await createData(`${(0, utils_1.docuHash)(pageId)}.json`, JSON.stringify(item, null, 2));
// TODO: "-content" should be inside hash to prevent name too long errors.
const markdown = await createData(`${(0, utils_1.docuHash)(pageId)}-content.mdx`, item.type === "api" ? (0, markdown_1.createApiPageMD)(item) : (0, markdown_1.createInfoPageMD)(item));
return {
path: item.permalink,
component: apiItemComponent,
exact: true,
modules: {
content: markdown,
},
sidebar: sidebarName,
};
if (item.type === "api" || item.type === "info") {
await createData(`${(0, utils_1.docuHash)(pageId)}.json`, JSON.stringify(item, null, 2));
// TODO: "-content" should be inside hash to prevent name too long errors.
const markdown = await createData(`${(0, utils_1.docuHash)(pageId)}-content.mdx`, item.type === "api" ? (0, markdown_1.createApiPageMD)(item) : (0, markdown_1.createInfoPageMD)(item));
return {
path: item.permalink,
component: apiItemComponent,
exact: true,
modules: {
content: markdown,
},
sidebar: sidebarName,
};
}
else {
await createData(
// Note that this created data path must be in sync with
// metadataPath provided to mdx-loader.
`${(0, utils_1.docuHash)(item.source)}.json`, JSON.stringify(item, null, 2));
return {
path: item.permalink,
component: "@theme/MarkdownItem",
exact: true,
modules: {
content: item.source,
},
sidebar: sidebarName,
};
}
});

@@ -88,2 +138,3 @@ // Important: the layout component should not end with /,

const basePath = apiBaseRoute === "/" ? "" : apiBaseRoute;
// Generates a new root route using the first api item.
async function rootRoute() {

@@ -103,5 +154,7 @@ const item = loadedApi[0];

}
// Whether we already have a document whose permalink falls on the root route.
const hasRootRoute = (await Promise.all(promises)).find((d) => (0, utils_1.normalizeUrl)([d.path, "/"]) === (0, utils_1.normalizeUrl)([basePath, "/"]));
const routes = (await Promise.all([
...promises,
rootRoute(),
...(hasRootRoute ? [] : [rootRoute()]),
]));

@@ -136,3 +189,3 @@ const apiBaseMetadataPath = await createData(`${(0, utils_1.docuHash)(`api-metadata-prop`)}.json`, JSON.stringify({

test: /(\.mdx?)$/,
include: [dataDir].map(utils_1.addTrailingPathSeparator),
include: [dataDir, contentPath].map(utils_1.addTrailingPathSeparator),
use: [

@@ -147,4 +200,14 @@ getJSLoader({ isServer }),

beforeDefaultRemarkPlugins,
// Docusaurus 2.2.0 has a regression that requires this option to be set.
markdownConfig: {},
metadataPath: (mdxPath) => {
return mdxPath.replace(/(-content\.mdx?)$/, ".json");
if (mdxPath.startsWith(dataDir)) {
// The MDX file already lives in `dataDir`: this is an OpenAPI MDX
return mdxPath.replace(/(-content\.mdx?)$/, ".json");
}
else {
// Standard resolution
const aliasedSource = (0, utils_1.aliasedSitePath)(mdxPath, siteDir);
return path_1.default.join(dataDir, `${(0, utils_1.docuHash)(aliasedSource)}.json`);
}
},

@@ -151,0 +214,0 @@ },

@@ -151,3 +151,3 @@ "use strict";

const apiItem = items.find((item) => {
if (item.type === "info") {
if (item.type === "info" || item.type === "mdx") {
return false;

@@ -184,2 +184,6 @@ }

});
// Explicitly look for _spec_ files, which are excluded by default since they start with _
allFiles.push(...(await (0, utils_1.Globby)(["**/_spec_.{json,yaml,yml}"], {
cwd: openapiPath,
})));
const sources = allFiles.filter((x) => !x.includes("_category_")); // todo: regex exclude?

@@ -186,0 +190,0 @@ return Promise.all(sources.map(async (source) => {

5

lib/sidebars/index.d.ts
import type { DeepPartial } from "utility-types";
import type { InfoPageMetadata, PropSidebar } from "../types";
import type { InfoPageMetadata, MdxPageMetadata, PropSidebar } from "../types";
import { ApiPageMetadata } from "../types";

@@ -14,4 +14,5 @@ interface Options {

};
declare type Item = InfoItem | ApiItem;
declare type MdxItem = Pick<MdxPageMetadata, keys | "frontMatter">;
declare type Item = InfoItem | ApiItem | MdxItem;
export declare function generateSidebar(items: Item[], options: Options): Promise<PropSidebar>;
export {};

@@ -21,2 +21,3 @@ "use strict";

const CategoryMetadataFilenameBase = "_category_";
const BottomSidebarPosition = 999999;
function isApiItem(item) {

@@ -28,2 +29,5 @@ return item.type === "api";

}
function isMdxItem(item) {
return item.type === "mdx";
}
const Terminator = "."; // a file or folder can never be "."

@@ -39,3 +43,3 @@ const BreadcrumbSeparator = "/";

async function generateSidebar(items, options) {
var _a, _b, _c, _d, _e;
var _a, _b, _c, _d, _e, _f;
const sourceGroups = (0, lodash_1.groupBy)(items, (item) => item.source);

@@ -56,13 +60,23 @@ let sidebar = [];

if (crumb === Terminator) {
const title = (_b = (_a = items.filter(isApiItem)[0]) === null || _a === void 0 ? void 0 : _a.api.info) === null || _b === void 0 ? void 0 : _b.title;
const fileName = path_1.default.basename(source, path_1.default.extname(source));
// Title could be an empty string so `??` won't work here.
const label = !title ? fileName : title;
visiting.push({
type: "category",
label,
collapsible: options.sidebarCollapsible,
collapsed: options.sidebarCollapsed,
items: groupByTags(items, options),
});
if (isMdxItem(items[0])) {
visiting.push(...groupByTags(items, options));
}
else if (["_spec_.json", "_spec_.yml", "_spec_.yaml"].includes(path_1.default.basename(items[0].source))) {
// Don't create a category for this spec file
visiting.push(...groupByTags(items, options));
}
else {
const title = (_b = (_a = items.filter(isApiItem)[0]) === null || _a === void 0 ? void 0 : _a.api.info) === null || _b === void 0 ? void 0 : _b.title;
const fileName = path_1.default.basename(source, path_1.default.extname(source));
// Title could be an empty string so `??` won't work here.
const label = !title ? fileName : title;
visiting.push({
type: "category",
label,
position: BottomSidebarPosition,
collapsible: options.sidebarCollapsible,
collapsed: options.sidebarCollapsed,
items: groupByTags(items, options),
});
}
visiting = sidebar; // reset

@@ -90,6 +104,6 @@ break;

customProps: meta === null || meta === void 0 ? void 0 : meta.customProps,
position: meta === null || meta === void 0 ? void 0 : meta.position,
position: (_d = meta === null || meta === void 0 ? void 0 : meta.position) !== null && _d !== void 0 ? _d : BottomSidebarPosition,
label,
collapsible: (_d = meta === null || meta === void 0 ? void 0 : meta.collapsible) !== null && _d !== void 0 ? _d : options.sidebarCollapsible,
collapsed: (_e = meta === null || meta === void 0 ? void 0 : meta.collapsed) !== null && _e !== void 0 ? _e : options.sidebarCollapsed,
collapsible: (_e = meta === null || meta === void 0 ? void 0 : meta.collapsible) !== null && _e !== void 0 ? _e : options.sidebarCollapsible,
collapsed: (_f = meta === null || meta === void 0 ? void 0 : meta.collapsed) !== null && _f !== void 0 ? _f : options.sidebarCollapsed,
items: [],

@@ -115,2 +129,3 @@ };

}
sidebar = recursiveSidebarSort(sidebar);
return sidebar;

@@ -120,11 +135,34 @@ }

/**
* Sort the sidebar recursively based on `position`.
* @param sidebar
* @returns
*/
function recursiveSidebarSort(sidebar) {
// Use lodash sortBy to ensure sorting stability
sidebar = (0, lodash_1.sortBy)(sidebar, (item) => item.position);
for (const item of sidebar) {
if (item.type === "category") {
item.items = recursiveSidebarSort(item.items);
}
}
return sidebar;
}
/**
* Takes a flat list of pages and groups them into categories based on there tags.
*/
function groupByTags(items, options) {
const intros = items.filter(isInfoItem).map((item) => {
const intros = items
.filter((m) => isInfoItem(m) || isMdxItem(m))
.map((item) => {
var _a, _b;
const fileName = path_1.default.basename(item.source, path_1.default.extname(item.source));
const label = !item.title ? fileName : item.title;
return {
type: "link",
label: item.title,
label: label,
href: item.permalink,
docId: item.id,
position: isMdxItem(item)
? (_b = (_a = item.frontMatter) === null || _a === void 0 ? void 0 : _a.sidebar_position) !== null && _b !== void 0 ? _b : BottomSidebarPosition
: BottomSidebarPosition,
};

@@ -146,2 +184,3 @@ });

}, item.api.method),
position: BottomSidebarPosition,
};

@@ -159,2 +198,3 @@ }

.map(createLink),
position: BottomSidebarPosition,
};

@@ -172,2 +212,3 @@ })

.map(createLink),
position: BottomSidebarPosition,
},

@@ -174,0 +215,0 @@ ];

@@ -21,3 +21,3 @@ import type { MDXOptions } from "@docusaurus/mdx-loader";

}
export declare type ApiMetadata = ApiPageMetadata | InfoPageMetadata;
export declare type ApiMetadata = ApiPageMetadata | InfoPageMetadata | MdxPageMetadata;
export interface ApiMetadataBase {

@@ -56,2 +56,5 @@ sidebar?: string;

}
export interface MdxPageMetadata extends ApiMetadataBase {
type: "mdx";
}
export declare type ApiInfo = InfoObject;

@@ -58,0 +61,0 @@ export interface ApiNavLink {

{
"name": "docusaurus-plugin-openapi",
"description": "OpenAPI plugin for Docusaurus.",
"version": "0.6.2",
"version": "0.6.3",
"license": "MIT",

@@ -59,3 +59,3 @@ "publishConfig": {

},
"gitHead": "30ba78a7390da009efc8db4321e532f23b95d380"
"gitHead": "e37b579105a2e6dd34fdb87b5973172e32763e75"
}

@@ -22,10 +22,14 @@ /* ============================================================================

posixPath,
aliasedSitePath,
Globby,
} from "@docusaurus/utils";
import chalk from "chalk";
import fs from "fs-extra";
import { Configuration } from "webpack";
import { DocEnv, processDocMetadata } from "./docs/docs";
import { createApiPageMD, createInfoPageMD } from "./markdown";
import { readOpenapiFiles, processOpenapiFiles } from "./openapi";
import { generateSidebar } from "./sidebars";
import type { PluginOptions, LoadedContent } from "./types";
import type { PluginOptions, LoadedContent, MdxPageMetadata } from "./types";
import { isURL } from "./util";

@@ -37,3 +41,3 @@

): Plugin<LoadedContent> {
const { baseUrl, generatedFilesDir } = context;
const { baseUrl, generatedFilesDir, siteDir } = context;

@@ -67,2 +71,26 @@ const pluginId = options.id ?? DEFAULT_PLUGIN_ID;

async function toMetadata(
/** E.g. "api/plugins/myDoc.mdx" */
relativeSource: string
): Promise<MdxPageMetadata> {
const source = path.join(contentPath, relativeSource);
const content = await fs.readFile(source, "utf-8");
return {
type: "mdx",
...(await processDocMetadata({
docFile: {
contentPath: contentPath,
filePath: source,
source: relativeSource,
content: content,
},
relativeSource: relativeSource,
context: context,
options: options,
env: process.env.NODE_ENV as DocEnv,
})),
};
}
try {

@@ -75,2 +103,19 @@ const openapiFiles = await readOpenapiFiles(contentPath, {});

});
const pagesFiles: string[] = [];
if (
!contentPath.endsWith(".json") &&
!contentPath.endsWith(".yaml") &&
!contentPath.endsWith(".yml")
) {
pagesFiles.push(
...(await Globby(["**/*.{md,mdx}"], {
cwd: contentPath,
// ignore: options.exclude, // TODO
}))
);
}
loadedApi.push(...(await Promise.all(pagesFiles.map(toMetadata))));
return { loadedApi };

@@ -105,21 +150,40 @@ } catch (e) {

await createData(
`${docuHash(pageId)}.json`,
JSON.stringify(item, null, 2)
);
if (item.type === "api" || item.type === "info") {
await createData(
`${docuHash(pageId)}.json`,
JSON.stringify(item, null, 2)
);
// TODO: "-content" should be inside hash to prevent name too long errors.
const markdown = await createData(
`${docuHash(pageId)}-content.mdx`,
item.type === "api" ? createApiPageMD(item) : createInfoPageMD(item)
);
return {
path: item.permalink,
component: apiItemComponent,
exact: true,
modules: {
content: markdown,
},
sidebar: sidebarName,
};
// TODO: "-content" should be inside hash to prevent name too long errors.
const markdown = await createData(
`${docuHash(pageId)}-content.mdx`,
item.type === "api" ? createApiPageMD(item) : createInfoPageMD(item)
);
return {
path: item.permalink,
component: apiItemComponent,
exact: true,
modules: {
content: markdown,
},
sidebar: sidebarName,
};
} else {
await createData(
// Note that this created data path must be in sync with
// metadataPath provided to mdx-loader.
`${docuHash(item.source)}.json`,
JSON.stringify(item, null, 2)
);
return {
path: item.permalink,
component: "@theme/MarkdownItem", // "@theme/DocItem"
exact: true,
modules: {
content: item.source,
},
sidebar: sidebarName,
};
}
});

@@ -133,2 +197,3 @@

// Generates a new root route using the first api item.
async function rootRoute() {

@@ -150,5 +215,10 @@ const item = loadedApi[0];

// Whether we already have a document whose permalink falls on the root route.
const hasRootRoute = (await Promise.all(promises)).find(
(d) => normalizeUrl([d.path, "/"]) === normalizeUrl([basePath, "/"])
);
const routes = (await Promise.all([
...promises,
rootRoute(),
...(hasRootRoute ? [] : [rootRoute()]),
])) as RouteConfig[];

@@ -204,3 +274,3 @@

test: /(\.mdx?)$/,
include: [dataDir].map(addTrailingPathSeparator),
include: [dataDir, contentPath].map(addTrailingPathSeparator),
use: [

@@ -215,4 +285,16 @@ getJSLoader({ isServer }),

beforeDefaultRemarkPlugins,
// Docusaurus 2.2.0 has a regression that requires this option to be set.
markdownConfig: {},
metadataPath: (mdxPath: string) => {
return mdxPath.replace(/(-content\.mdx?)$/, ".json");
if (mdxPath.startsWith(dataDir)) {
// The MDX file already lives in `dataDir`: this is an OpenAPI MDX
return mdxPath.replace(/(-content\.mdx?)$/, ".json");
} else {
// Standard resolution
const aliasedSource = aliasedSitePath(mdxPath, siteDir);
return path.join(
dataDir,
`${docuHash(aliasedSource)}.json`
);
}
},

@@ -219,0 +301,0 @@ },

@@ -193,3 +193,3 @@ /* ============================================================================

const apiItem = items.find((item) => {
if (item.type === "info") {
if (item.type === "info" || item.type === "mdx") {
return false;

@@ -244,2 +244,10 @@ }

});
// Explicitly look for _spec_ files, which are excluded by default since they start with _
allFiles.push(
...(await Globby(["**/_spec_.{json,yaml,yml}"], {
cwd: openapiPath,
}))
);
const sources = allFiles.filter((x) => !x.includes("_category_")); // todo: regex exclude?

@@ -246,0 +254,0 @@ return Promise.all(

@@ -16,2 +16,3 @@ /* ============================================================================

customProps?: Record<string, unknown>;
position?: number;
};

@@ -123,1 +124,13 @@

}
declare module "@theme/MarkdownItem" {
export interface Props {
readonly content: {
readonly metadata: Metadata;
(): JSX.Element;
};
}
const MarkdownItem: (props: Props) => JSX.Element;
export default MarkdownItem;
}

@@ -16,3 +16,3 @@ /* ============================================================================

import Yaml from "js-yaml";
import { groupBy, uniq } from "lodash";
import { groupBy, sortBy, uniq } from "lodash";
import type { DeepPartial } from "utility-types";

@@ -22,3 +22,5 @@

InfoPageMetadata,
MdxPageMetadata,
PropSidebar,
PropSidebarItem,
PropSidebarItemCategory,

@@ -40,7 +42,10 @@ } from "../types";

};
type MdxItem = Pick<MdxPageMetadata, keys | "frontMatter">;
type Item = InfoItem | ApiItem;
type Item = InfoItem | ApiItem | MdxItem;
const CategoryMetadataFilenameBase = "_category_";
const BottomSidebarPosition = 999999;
function isApiItem(item: Item): item is ApiItem {

@@ -54,2 +59,6 @@ return item.type === "api";

function isMdxItem(item: Item): item is MdxItem {
return item.type === "mdx";
}
const Terminator = "."; // a file or folder can never be "."

@@ -88,13 +97,26 @@ const BreadcrumbSeparator = "/";

if (crumb === Terminator) {
const title = items.filter(isApiItem)[0]?.api.info?.title;
const fileName = path.basename(source, path.extname(source));
// Title could be an empty string so `??` won't work here.
const label = !title ? fileName : title;
visiting.push({
type: "category" as const,
label,
collapsible: options.sidebarCollapsible,
collapsed: options.sidebarCollapsed,
items: groupByTags(items, options),
});
if (isMdxItem(items[0])) {
visiting.push(...groupByTags(items, options));
} else if (
["_spec_.json", "_spec_.yml", "_spec_.yaml"].includes(
path.basename(items[0].source)
)
) {
// Don't create a category for this spec file
visiting.push(...groupByTags(items, options));
} else {
const title = items.filter(isApiItem)[0]?.api.info?.title;
const fileName = path.basename(source, path.extname(source));
// Title could be an empty string so `??` won't work here.
const label = !title ? fileName : title;
visiting.push({
type: "category" as const,
label,
position: BottomSidebarPosition,
collapsible: options.sidebarCollapsible,
collapsed: options.sidebarCollapsed,
items: groupByTags(items, options),
});
}
visiting = sidebar; // reset

@@ -126,3 +148,3 @@ break;

customProps: meta?.customProps,
position: meta?.position,
position: meta?.position ?? BottomSidebarPosition,
label,

@@ -156,2 +178,4 @@ collapsible: meta?.collapsible ?? options.sidebarCollapsible,

sidebar = recursiveSidebarSort(sidebar);
return sidebar;

@@ -161,14 +185,39 @@ }

/**
* Sort the sidebar recursively based on `position`.
* @param sidebar
* @returns
*/
function recursiveSidebarSort(sidebar: PropSidebar | PropSidebarItem[]) {
// Use lodash sortBy to ensure sorting stability
sidebar = sortBy(sidebar, (item) => item.position);
for (const item of sidebar) {
if (item.type === "category") {
item.items = recursiveSidebarSort(item.items);
}
}
return sidebar;
}
/**
* Takes a flat list of pages and groups them into categories based on there tags.
*/
function groupByTags(items: Item[], options: Options): PropSidebar {
const intros = items.filter(isInfoItem).map((item) => {
return {
type: "link" as const,
label: item.title,
href: item.permalink,
docId: item.id,
};
});
const intros = items
.filter((m) => isInfoItem(m) || isMdxItem(m))
.map((item) => {
const fileName = path.basename(item.source, path.extname(item.source));
const label = !item.title ? fileName : item.title;
return {
type: "link" as const,
label: label,
href: item.permalink,
docId: item.id,
position: isMdxItem(item)
? (item.frontMatter?.sidebar_position as number) ??
BottomSidebarPosition
: BottomSidebarPosition,
};
});
const apiItems = items.filter(isApiItem);

@@ -195,2 +244,3 @@

),
position: BottomSidebarPosition,
};

@@ -209,2 +259,3 @@ }

.map(createLink),
position: BottomSidebarPosition,
};

@@ -223,2 +274,3 @@ })

.map(createLink),
position: BottomSidebarPosition,
},

@@ -225,0 +277,0 @@ ];

@@ -44,3 +44,3 @@ /* ============================================================================

export type ApiMetadata = ApiPageMetadata | InfoPageMetadata;
export type ApiMetadata = ApiPageMetadata | InfoPageMetadata | MdxPageMetadata;

@@ -85,2 +85,6 @@ export interface ApiMetadataBase {

export interface MdxPageMetadata extends ApiMetadataBase {
type: "mdx";
}
export type ApiInfo = InfoObject;

@@ -87,0 +91,0 @@

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc