diff --git a/.gitignore b/.gitignore index 0337ddad4..3cefdd244 100644 --- a/.gitignore +++ b/.gitignore @@ -132,3 +132,6 @@ dist # TernJS port file .tern-port + +# demo API MDX files +demo/api/**/*.mdx diff --git a/cypress/integration/test.spec.ts b/cypress/integration/test.spec.ts index 114a1d54f..b1264f6db 100644 --- a/cypress/integration/test.spec.ts +++ b/cypress/integration/test.spec.ts @@ -8,41 +8,12 @@ describe("test", () => { it("loads Petstore page", () => { cy.viewport("macbook-15"); - cy.visit("/petstore"); + cy.visit("/api/petstore"); navTo( - [/^pet$/i, /add a new pet to the store/i], + [/^petstore$/i, /add a new pet to the store/i], /add a new pet to the store/i ); }); - - it("loads Cloud Object Storage page", () => { - cy.viewport("macbook-15"); - cy.visit("/cos"); - navTo([], /generating an iam token/i); - }); - - it("loads Multi-spec page", () => { - cy.viewport("macbook-15"); - cy.visit("/multi-spec"); - navTo( - [ - /foods/i, - /burger store/i, - /burger example/i, - /^api$/i, - /list all burgers/i, - ], - /list all burgers/i - ); - }); - - it("loads a page with authentication", () => { - cy.visit("/cos/list-buckets"); - cy.findByRole("button", { name: /authorize/i }).should("exist"); - - cy.visit("/cos/create-a-bucket"); - cy.findByRole("button", { name: /authorize/i }).should("exist"); - }); }); /** diff --git a/demo/api/cos/_category_.json b/demo/api/cos/_category_.json new file mode 100644 index 000000000..3d5565287 --- /dev/null +++ b/demo/api/cos/_category_.json @@ -0,0 +1,4 @@ +{ + "label": "IBM Cloud Object Storage", + "collapsed": true +} diff --git a/demo/api/first.md b/demo/api/first.md new file mode 100644 index 000000000..9fdb76b08 --- /dev/null +++ b/demo/api/first.md @@ -0,0 +1,9 @@ +--- +id: first +sidebar_label: First Doc +sidebar_position: 1 +--- + +## This doc comes first + +This doc supercedes all other docs in the sidebar. diff --git a/demo/api/food/_category_.json b/demo/api/food/_category_.json new file mode 100644 index 000000000..246c5fb7f --- /dev/null +++ b/demo/api/food/_category_.json @@ -0,0 +1,4 @@ +{ + "label": "Food", + "collapsed": true +} diff --git a/demo/api/food/burgers/_category_.json b/demo/api/food/burgers/_category_.json new file mode 100644 index 000000000..fe672ab25 --- /dev/null +++ b/demo/api/food/burgers/_category_.json @@ -0,0 +1,4 @@ +{ + "label": "Burgers", + "collapsed": true +} diff --git a/demo/api/food/yogurtstore/_category_.json b/demo/api/food/yogurtstore/_category_.json new file mode 100644 index 000000000..c7b0c8889 --- /dev/null +++ b/demo/api/food/yogurtstore/_category_.json @@ -0,0 +1,4 @@ +{ + "label": "Yogurt Store", + "collapsed": true +} diff --git a/demo/api/openapi-issue/_category_.json b/demo/api/openapi-issue/_category_.json new file mode 100644 index 000000000..cca366250 --- /dev/null +++ b/demo/api/openapi-issue/_category_.json @@ -0,0 +1,4 @@ +{ + "label": "OpenAPI Issue", + "collapsed": true +} diff --git a/demo/api/petstore/_category_.json b/demo/api/petstore/_category_.json new file mode 100644 index 000000000..616b0f229 --- /dev/null +++ b/demo/api/petstore/_category_.json @@ -0,0 +1,4 @@ +{ + "label": "Petstore", + "collapsed": true +} diff --git a/demo/api/petstore/first.md b/demo/api/petstore/first.md new file mode 100644 index 000000000..5e17ba2fc --- /dev/null +++ b/demo/api/petstore/first.md @@ -0,0 +1,9 @@ +--- +id: before-petstore +sidebar_label: Before Petstore API +sidebar_position: 1 +--- + +## This is only a test! + +This is how you would "front load" a non-API doc. diff --git a/demo/api/second.md b/demo/api/second.md new file mode 100644 index 000000000..045a2814b --- /dev/null +++ b/demo/api/second.md @@ -0,0 +1,9 @@ +--- +id: second +sidebar_label: Second Doc +sidebar_position: 2 +--- + +## This doc comes second + +This doc comes after the first doc. diff --git a/demo/apiSidebars.js b/demo/apiSidebars.js new file mode 100644 index 000000000..a39e5339d --- /dev/null +++ b/demo/apiSidebars.js @@ -0,0 +1,19 @@ +/** + * Creating a sidebar enables you to: + - create an ordered group of docs + - render a sidebar for each doc of that group + - provide next/previous navigation + + The sidebars can be generated from the filesystem, or explicitly defined here. + + Create as many sidebars as you want. + */ + +/** @type {import('@docusaurus/plugin-content-docs').SidebarsConfig} */ +const sidebars = { + // By default, Docusaurus generates a sidebar from the api folder structure + // openApiSidebar: require("./api/sidebar").sidebar, + openApiSidebar: [{ type: "autogenerated", dirName: "." }], +}; + +module.exports = sidebars; diff --git a/demo/docusaurus.config.js b/demo/docusaurus.config.js index e24c91562..f6d87a601 100644 --- a/demo/docusaurus.config.js +++ b/demo/docusaurus.config.js @@ -8,73 +8,103 @@ const darkCodeTheme = require("prism-react-renderer/themes/dracula"); const config = { title: "Docusaurus OpenAPI", tagline: "OpenAPI plugin for generating API reference docs in Docusaurus v2.", - url: "https://docusaurus-openapi.netlify.app", + url: "https://docusaurus-openapi.tryingpan.dev", baseUrl: "/", onBrokenLinks: "warn", onBrokenMarkdownLinks: "warn", favicon: "img/favicon.ico", - organizationName: "cloud-annotations", // Usually your GitHub org/user name. + organizationName: "PaloAltoNetworks", // Usually your GitHub org/user name. projectName: "docusaurus-openapi", // Usually your repo name. presets: [ [ - "@paloaltonetworks/docusaurus-preset-openapi", - /** @type {import('@paloaltonetworks/docusaurus-preset-openapi').Options} */ - ({ - api: { - path: "examples/petstore.yaml", - routeBasePath: "petstore", - beforeApiDocs: [ - "examples/test.md", - "examples/test2.mdx", - "examples/test3.md", - ], - }, + "@docusaurus/preset-classic", + { docs: { sidebarPath: require.resolve("./sidebars.js"), // Please change this to your repo. editUrl: - "https://github.com/cloud-annotations/docusaurus-openapi/edit/main/demo/", + "https://github.com/facebook/docusaurus/edit/master/website/", + }, + blog: { + showReadingTime: true, + // Please change this to your repo. + editUrl: + "https://github.com/facebook/docusaurus/edit/master/website/blog/", }, - blog: false, theme: { customCss: require.resolve("./src/css/custom.css"), }, - proxy: { - "/proxy": { - target: "http://localhost:8091", - pathRewrite: { "^/proxy": "" }, - }, - }, - }), + }, ], ], plugins: [ + [ + "@paloaltonetworks/docusaurus-plugin-openapi", + { + id: "petstore", + path: "examples/petstore.yaml", + outputDir: "api/petstore", + }, + ], [ "@paloaltonetworks/docusaurus-plugin-openapi", { id: "cos", path: "examples/openapi-cos.json", - routeBasePath: "cos", - beforeApiDocs: ["examples/test.md", "examples/test2.mdx"], + outputDir: "api/cos", + }, + ], + [ + "@paloaltonetworks/docusaurus-plugin-openapi", + { + id: "openapi-issue", + path: "examples/openapi-issue-21.json", + outputDir: "api/openapi-issue", + }, + ], + [ + "@paloaltonetworks/docusaurus-plugin-openapi", + { + id: "burgers", + path: "examples/food/burgers/openapi.yaml", + outputDir: "api/food/burgers", }, ], [ "@paloaltonetworks/docusaurus-plugin-openapi", { - id: "multi-spec", - path: "examples", - routeBasePath: "multi-spec", - showExecuteButton: false, - showManualAuthentication: false, + id: "yogurt", + path: "examples/food/yogurtstore/openapi.yaml", + outputDir: "api/food/yogurtstore", + }, + ], + // [require.resolve("./plugins/webpackOptimizer"), {}], + [ + "@docusaurus/plugin-content-docs", + { + id: "openapi", + path: "api", + breadcrumbs: true, + routeBasePath: "api", + include: ["**/*.md", "**/*.mdx"], + sidebarPath: "apiSidebars.js", + docLayoutComponent: "@theme/DocPage", // This solves the providers issue and drop the need for ApiPage component + docItemComponent: "@theme/ApiItem", // Will probably need to clean this up/refactor to get it fully updated + remarkPlugins: [], + rehypePlugins: [], + beforeDefaultRemarkPlugins: [], + beforeDefaultRehypePlugins: [], + showLastUpdateAuthor: true, // We can now add stuff like this :) + showLastUpdateTime: true, }, ], - [require.resolve("./plugins/webpackOptimizer"), {}], ], + themes: ["@paloaltonetworks/docusaurus-theme-openapi"], themeConfig: - /** @type {import('@paloaltonetworks/docusaurus-preset-openapi').ThemeConfig} */ + /** @type {import('@docusaurus/preset-classic').ThemeConfig} */ ({ colorMode: { disableSwitch: false, @@ -97,13 +127,16 @@ const config = { label: "Examples", position: "left", items: [ - { to: "/petstore/test", label: "Petstore" }, - { to: "/cos/test", label: "Cloud Object Storage" }, - { to: "/multi-spec", label: "Multi-spec" }, + { to: "/api/petstore/", label: "Petstore" }, + { + to: "/api/cos/create-a-bucket", + label: "Cloud Object Storage", + }, + { to: "api/food/burgers/", label: "Food" }, ], }, { - href: "https://github.com/cloud-annotations/docusaurus-openapi", + href: "https://github.com/PaloAltoNetworks/docusaurus-openapi", position: "right", className: "header-github-link", "aria-label": "GitHub repository", @@ -113,21 +146,18 @@ const config = { footer: { style: "dark", logo: { - alt: "Deploys by Netlify", - src: "https://www.netlify.com/img/global/badges/netlify-color-accent.svg", + alt: "Deploys by Firebase", + src: "https://firebase.google.com/downloads/brand-guidelines/SVG/logo-built_knockout.svg", width: 160, height: 51, - href: "https://www.netlify.com", + href: "https://firebase.google.com", }, - copyright: `Copyright © ${new Date().getFullYear()} Cloud Annotations. Built with Docusaurus.`, + copyright: `Copyright © ${new Date().getFullYear()} Palo Alto Networks. Built with Docusaurus.`, }, prism: { theme: lightCodeTheme, darkTheme: darkCodeTheme, }, - api: { - authPersistance: "localStorage", - }, }), }; diff --git a/demo/examples/petstore.yaml b/demo/examples/petstore.yaml index a0c3af6cf..e96578d48 100644 --- a/demo/examples/petstore.yaml +++ b/demo/examples/petstore.yaml @@ -11,26 +11,26 @@ info: [http://swagger.io](http://swagger.io) or on [irc.freenode.net, #swagger](http://swagger.io/irc/). For this sample, you can use the api key `special-key` to test the authorization filters. - # Introduction + ## Introduction This API is documented in **OpenAPI format** and is based on [Petstore sample](http://petstore.swagger.io/) provided by [swagger.io](http://swagger.io) team. It was **extended** to illustrate features of [generator-openapi-repo](https://github.com/Rebilly/generator-openapi-repo) tool and [ReDoc](https://github.com/Redocly/redoc) documentation. In addition to standard OpenAPI syntax we use a few [vendor extensions](https://github.com/Redocly/redoc/blob/master/docs/redoc-vendor-extensions.md). - # OpenAPI Specification + ## OpenAPI Specification This API is documented in **OpenAPI format** and is based on [Petstore sample](http://petstore.swagger.io/) provided by [swagger.io](http://swagger.io) team. It was **extended** to illustrate features of [generator-openapi-repo](https://github.com/Rebilly/generator-openapi-repo) tool and [ReDoc](https://github.com/Redocly/redoc) documentation. In addition to standard OpenAPI syntax we use a few [vendor extensions](https://github.com/Redocly/redoc/blob/master/docs/redoc-vendor-extensions.md). - # Cross-Origin Resource Sharing + ## Cross-Origin Resource Sharing This API features Cross-Origin Resource Sharing (CORS) implemented in compliance with [W3C spec](https://www.w3.org/TR/cors/). And that allows cross-domain communication from the browser. All responses have a wildcard same-origin which makes them completely public and accessible to everyone, including any code on any site. - # Authentication + ## Authentication Petstore offers two forms of authentication: - API Key diff --git a/demo/src/css/custom.css b/demo/src/css/custom.css index b444106f4..ac48d7184 100644 --- a/demo/src/css/custom.css +++ b/demo/src/css/custom.css @@ -27,7 +27,7 @@ --openapi-code-red: var(--ifm-color-danger) !important; --openapi-code-orange: var(--ifm-color-warning) !important; /* Helps avoid #aaa bug in monaco-editor */ - --openapi-code-dim-light: var(--ifm-color-secondary-lightest) !important; + /* --openapi-code-dim-light: var(--ifm-color-secondary-lightest) !important; */ } /* For readability concerns, you should choose a lighter palette in dark mode. */ diff --git a/package.json b/package.json index 2722924f1..ba0d5376c 100644 --- a/package.json +++ b/package.json @@ -26,7 +26,8 @@ "release:changelog": "scripts/changelog.ts", "release:version": "scripts/version.ts", "release:publish": "scripts/publish.ts", - "clean": "rm -rf node_modules build demo/.docusaurus demo/build demo/node_modules && find packages -name node_modules -type d -maxdepth 2 -exec rm -rf {} + && find packages -name dist -type d -maxdepth 2 -exec rm -rf {} + && find packages -name lib -type d -maxdepth 2 -exec rm -rf {} + && find packages -name lib-next -type d -maxdepth 2 -exec rm -rf {} +" + "clean": "rm -rf node_modules build demo/.docusaurus demo/build demo/node_modules && find packages -name node_modules -type d -maxdepth 2 -exec rm -rf {} + && find packages -name dist -type d -maxdepth 2 -exec rm -rf {} + && find packages -name lib -type d -maxdepth 2 -exec rm -rf {} + && find packages -name lib-next -type d -maxdepth 2 -exec rm -rf {} +", + "cleanApiDocs": "find ./demo/api -name '*.mdx' -type f | xargs rm" }, "devDependencies": { "@babel/cli": "^7.16.0", diff --git a/packages/docusaurus-plugin-openapi/package.json b/packages/docusaurus-plugin-openapi/package.json index 64898faeb..782df715d 100644 --- a/packages/docusaurus-plugin-openapi/package.json +++ b/packages/docusaurus-plugin-openapi/package.json @@ -24,7 +24,6 @@ "@docusaurus/module-type-aliases": "2.0.0-beta.17", "@docusaurus/types": "2.0.0-beta.17", "@types/fs-extra": "^9.0.13", - "@types/js-yaml": "^4.0.5", "@types/json-schema": "^7.0.9", "@types/lodash": "^4.14.176", "@types/postman-collection": "^3.5.7", @@ -37,6 +36,8 @@ "@docusaurus/utils-validation": "2.0.0-beta.17", "@paloaltonetworks/openapi-to-postmanv2": "3.1.0-hotfix.1", "@paloaltonetworks/postman-collection": "^4.1.0", + "@types/js-yaml": "^4.0.5", + "@types/mustache": "^4.1.2", "chalk": "^4.1.2", "clsx": "^1.1.1", "fs-extra": "^9.0.1", @@ -44,7 +45,7 @@ "json-refs": "^3.0.15", "json-schema-merge-allof": "^0.8.1", "lodash": "^4.17.20", - "remark-admonitions": "^1.2.1", + "mustache": "^4.2.0", "webpack": "^5.61.0" }, "peerDependencies": { diff --git a/packages/docusaurus-plugin-openapi/src/docs/docFrontmatter.ts b/packages/docusaurus-plugin-openapi/src/docs/docFrontmatter.ts deleted file mode 100644 index b519c2907..000000000 --- a/packages/docusaurus-plugin-openapi/src/docs/docFrontmatter.ts +++ /dev/null @@ -1,49 +0,0 @@ -/* ============================================================================ - * Copyright (c) Cloud Annotations - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - * ========================================================================== */ - -import { - JoiFrontMatter as Joi, // Custom instance for front matter - URISchema, - FrontMatterTagsSchema, - FrontMatterTOCHeadingLevels, - validateFrontMatter, -} from "@docusaurus/utils-validation"; - -import type { DocFrontMatter } from "./types"; - -// NOTE: we don't add any default value on purpose here -// We don't want default values to magically appear in doc metadata and props -// While the user did not provide those values explicitly -// We use default values in code instead -const DocFrontMatterSchema = Joi.object({ - id: Joi.string(), - title: Joi.string().allow(""), // see https://github.com/facebook/docusaurus/issues/4591#issuecomment-822372398 - hide_title: Joi.boolean(), - hide_table_of_contents: Joi.boolean(), - keywords: Joi.array().items(Joi.string().required()), - image: URISchema, - description: Joi.string().allow(""), // see https://github.com/facebook/docusaurus/issues/4591#issuecomment-822372398 - slug: Joi.string(), - sidebar_label: Joi.string(), - sidebar_position: Joi.number(), - sidebar_class_name: Joi.string(), - sidebar_custom_props: Joi.object().unknown(), - displayed_sidebar: Joi.string().allow(null), - tags: FrontMatterTagsSchema, - pagination_label: Joi.string(), - custom_edit_url: URISchema.allow("", null), - parse_number_prefixes: Joi.boolean(), - pagination_next: Joi.string().allow(null), - pagination_prev: Joi.string().allow(null), - ...FrontMatterTOCHeadingLevels, -}).unknown(); - -export function validateDocFrontMatter( - frontMatter: Record -): DocFrontMatter { - return validateFrontMatter(frontMatter, DocFrontMatterSchema); -} diff --git a/packages/docusaurus-plugin-openapi/src/docs/docs.ts b/packages/docusaurus-plugin-openapi/src/docs/docs.ts deleted file mode 100644 index bdf380d8c..000000000 --- a/packages/docusaurus-plugin-openapi/src/docs/docs.ts +++ /dev/null @@ -1,183 +0,0 @@ -/* ============================================================================ - * Copyright (c) Cloud Annotations - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - * ========================================================================== */ - -import path from "path"; - -import type { - CategoryIndexMatcher, - CategoryIndexMatcherParam, -} from "@docusaurus/plugin-content-docs"; -import { - aliasedSitePath, - parseMarkdownString, - normalizeUrl, -} from "@docusaurus/utils"; -import fs from "fs-extra"; - -import { DocPageMetadata } from "../types"; -import { validateDocFrontMatter } from "./docFrontmatter"; -import { DocObject, DocMetadataBase } from "./types"; - -interface DocFiles { - source: string; - sourceDirName: string; - data: DocObject; -} - -// By convention, Docusaurus considers some docs are "indexes": -// - index.md -// - readme.md -// - /.md -// -// This function is the default implementation of this convention -// -// Those index docs produce a different behavior -// - Slugs do not end with a weird "/index" suffix -// - Auto-generated sidebar categories link to them as intro -export const isCategoryIndex: CategoryIndexMatcher = ({ - fileName, - directories, -}): boolean => { - const eligibleDocIndexNames = [ - "index", - "readme", - directories[0]?.toLowerCase(), - ]; - return eligibleDocIndexNames.includes(fileName.toLowerCase()); -}; - -/** - * `guides/sidebar/autogenerated.md` -> - * `'autogenerated', '.md', ['sidebar', 'guides']` - */ -export function toCategoryIndexMatcherParam({ - source, - sourceDirName, -}: Pick< - DocMetadataBase, - "source" | "sourceDirName" ->): CategoryIndexMatcherParam { - // source + sourceDirName are always posix-style - return { - fileName: path.posix.parse(source).name, - extension: path.posix.parse(source).ext, - directories: sourceDirName.split(path.posix.sep).reverse(), - }; -} - -export async function readDocFiles( - beforeApiDocs: Array, - _options: {} -): Promise { - const sources = beforeApiDocs.map(async (source) => { - const fullPath = path.join(source); - const data = (await fs.readFile(fullPath, "utf-8")) as DocObject; - return { - source: fullPath, // This will be aliased in process. - sourceDirName: path.dirname(source), - data, - }; - }); - return Promise.all(sources); -} - -export async function processDocFiles( - files: DocFiles[], - options: { - baseUrl: string; - routeBasePath: string; - siteDir: string; - } -): Promise { - const promises = files.map(async (file) => { - const { - frontMatter: unsafeFrontMatter, - contentTitle, - excerpt, - } = parseMarkdownString(file.data as string); - const frontMatter = validateDocFrontMatter(unsafeFrontMatter); - const slug = "/" + frontMatter.id; - const permalink = - options.baseUrl + options.routeBasePath + "/" + frontMatter.id; - return { - type: "doc" as "doc", // TODO: fix this - id: frontMatter.id ?? "", - unversionedId: frontMatter.id ?? "", - title: frontMatter.title ?? contentTitle ?? frontMatter.id ?? "", - description: frontMatter.description ?? excerpt ?? "", - slug: slug, - frontMatter: frontMatter, - permalink: permalink, - ...file, - source: aliasedSitePath(file.source, options.siteDir), - sourceDirName: file.sourceDirName, - } as DocPageMetadata; - }); - const metadata = await Promise.all(promises); - const items = metadata.flat(); - - let seen: { [key: string]: number } = {}; - for (let i = 0; i < items.length; i++) { - const baseId = items[i].id; - let count = seen[baseId]; - - let id; - if (count) { - id = `${baseId}-${count}`; - seen[baseId] = count + 1; - } else { - id = baseId; - seen[baseId] = 1; - } - - items[i].id = id; - items[i].unversionedId = id; - items[i].slug = "/" + id; - } - - for (let i = 0; i < items.length; i++) { - const current = items[i]; - const prev = items[i - 1]; - const next = items[i + 1]; - - current.permalink = normalizeUrl([ - options.baseUrl, - options.routeBasePath, - current.id, - ]); - - if (prev) { - current.previous = { - title: - (prev.frontMatter.pagination_label as string) ?? - (prev.frontMatter.sidebar_label as string) ?? - prev.title, - permalink: normalizeUrl([ - options.baseUrl, - options.routeBasePath, - prev.id, - ]), - }; - } - - if (next) { - current.next = { - title: - (next.frontMatter.pagination_label as string) ?? - (next.frontMatter.sidebar_label as string) ?? - next.title, - permalink: normalizeUrl([ - options.baseUrl, - options.routeBasePath, - next.id, - ]), - }; - } - } - - return items; -} diff --git a/packages/docusaurus-plugin-openapi/src/docs/index.ts b/packages/docusaurus-plugin-openapi/src/docs/index.ts deleted file mode 100644 index 520b8668e..000000000 --- a/packages/docusaurus-plugin-openapi/src/docs/index.ts +++ /dev/null @@ -1,8 +0,0 @@ -/* ============================================================================ - * Copyright (c) Cloud Annotations - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - * ========================================================================== */ - -export { readDocFiles } from "./docs"; diff --git a/packages/docusaurus-plugin-openapi/src/docs/types.ts b/packages/docusaurus-plugin-openapi/src/docs/types.ts deleted file mode 100644 index 3502f15e5..000000000 --- a/packages/docusaurus-plugin-openapi/src/docs/types.ts +++ /dev/null @@ -1,74 +0,0 @@ -/* ============================================================================ - * Copyright (c) Cloud Annotations - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - * ========================================================================== */ - -import type { VersionBanner } from "@docusaurus/plugin-content-docs"; -import type { Tag, FrontMatterTag } from "@docusaurus/utils"; -import type { ContentPaths } from "@docusaurus/utils/lib/markdownLinks"; - -export interface DocObject {} - -export type DocFrontMatter = { - // Front matter uses snake case - id?: string; - title?: string; - tags?: FrontMatterTag[]; - hide_title?: boolean; - hide_table_of_contents?: boolean; - keywords?: string[]; - image?: string; - description?: string; - slug?: string; - sidebar_label?: string; - sidebar_position?: number; - sidebar_class_name?: string; - sidebar_custom_props?: Record; - displayed_sidebar?: string | null; - pagination_label?: string; - custom_edit_url?: string | null; - parse_number_prefixes?: boolean; - toc_min_heading_level?: number; - toc_max_heading_level?: number; - pagination_next?: string | null; - pagination_prev?: string | null; -}; - -export type LastUpdateData = { - lastUpdatedAt?: number; - formattedLastUpdatedAt?: string; - lastUpdatedBy?: string; -}; - -export type DocMetadataBase = LastUpdateData & { - id: string; // TODO legacy versioned id => try to remove - unversionedId: string; // TODO new unversioned id => try to rename to "id" - version: string; - title: string; - description: string; - source: string; // @site aliased posix source => "@site/docs/folder/subFolder/subSubFolder/myDoc.md" - sourceDirName: string; // posix path relative to the versioned docs folder (can be ".") => "folder/subFolder/subSubFolder" - slug: string; - permalink: string; - sidebarPosition?: number; - editUrl?: string | null; - tags: Tag[]; - frontMatter: DocFrontMatter & Record; -}; - -export type VersionMetadata = ContentPaths & { - versionName: string; // 1.0.0 - versionLabel: string; // Version 1.0.0 - versionPath: string; // /baseUrl/docs/1.0.0 - tagsPath: string; - versionEditUrl?: string | undefined; - versionEditUrlLocalized?: string | undefined; - versionBanner: VersionBanner | null; - versionBadge: boolean; - versionClassName: string; - isLast: boolean; - sidebarFilePath: string | false | undefined; // versioned_sidebars/1.0.0.json - routePriority: number | undefined; // -1 for the latest docs -}; diff --git a/packages/docusaurus-plugin-openapi/src/index.ts b/packages/docusaurus-plugin-openapi/src/index.ts index f2168a7bd..d3f82b413 100644 --- a/packages/docusaurus-plugin-openapi/src/index.ts +++ b/packages/docusaurus-plugin-openapi/src/index.ts @@ -5,269 +5,135 @@ * LICENSE file in the root directory of this source tree. * ========================================================================== */ +import fs from "fs"; import path from "path"; -import type { - LoadContext, - Plugin, - RouteConfig, - ConfigureWebpackUtils, -} from "@docusaurus/types"; -import { - DEFAULT_PLUGIN_ID, - normalizeUrl, - docuHash, - addTrailingPathSeparator, - posixPath, -} from "@docusaurus/utils"; +import type { LoadContext, Plugin } from "@docusaurus/types"; import chalk from "chalk"; -import { Configuration } from "webpack"; +import { render } from "mustache"; -import { readDocFiles } from "./docs"; -import { processDocFiles } from "./docs/docs"; import { createApiPageMD, createInfoPageMD } from "./markdown"; import { readOpenapiFiles, processOpenapiFiles } from "./openapi"; -import { generateSidebar } from "./sidebars"; import type { PluginOptions, LoadedContent } from "./types"; export default function pluginOpenAPI( context: LoadContext, options: PluginOptions ): Plugin { - const { baseUrl, generatedFilesDir } = context; - const pluginId = options.id ?? DEFAULT_PLUGIN_ID; - const beforeApiDocs = options.beforeApiDocs; - - const pluginDataDirRoot = path.join( - generatedFilesDir, - "docusaurus-plugin-openapi" - ); - - const dataDir = path.join(pluginDataDirRoot, pluginId); - - const aliasedSource = (source: string) => - `~api/${posixPath(path.relative(pluginDataDirRoot, source))}`; - const contentPath = path.resolve(context.siteDir, options.path); - const docPaths = beforeApiDocs.map((docPath) => { - return path.resolve(context.siteDir, docPath); - }); - - return { - name: "docusaurus-plugin-openapi", - - getPathsToWatch() { - return [contentPath].concat(docPaths); - }, - - async loadContent() { - const { routeBasePath } = options; - const docFiles = await readDocFiles(beforeApiDocs, {}); - const loadedDocs = await processDocFiles(docFiles, { - baseUrl, - routeBasePath, - siteDir: context.siteDir, - }); - try { - const openapiFiles = await readOpenapiFiles(contentPath, {}); - const loadedApi = await processOpenapiFiles(loadedDocs, openapiFiles, { - baseUrl, - routeBasePath, - siteDir: context.siteDir, - }); - // Set item.next on last doc item - if (loadedDocs.length > 0) { - loadedDocs[loadedDocs.length - 1].next = { - title: loadedApi[0].title, - permalink: loadedApi[0].permalink, - }; - } - - return { loadedApi, loadedDocs }; - } catch (e) { - console.error(chalk.red(`Loading of api failed for "${contentPath}"`)); - throw e; - } - }, - - async contentLoaded({ content, actions }) { - const { loadedApi, loadedDocs } = content; + // Generate md/mdx before loadContent() life cycle method + async function beforeLoadContent() { + try { + const openapiFiles = await readOpenapiFiles(contentPath, {}); + const loadedApi = await processOpenapiFiles(openapiFiles); const { - routeBasePath, - apiLayoutComponent, - apiItemComponent, - sidebarCollapsed, - sidebarCollapsible, showExecuteButton, showManualAuthentication, + outputDir, + template, } = options; - const { addRoute, createData } = actions; - const sidebarName = `openapi-sidebar-${pluginId}`; - - const docSidebar = await generateSidebar(loadedDocs as [], { - contentPath, - sidebarCollapsible, - sidebarCollapsed, - }); - - const sidebar = await generateSidebar(loadedApi, { - contentPath, - sidebarCollapsible, - sidebarCollapsed, - }); - - const docPromises = loadedDocs.map(async (item) => { - const pageId = `site-${routeBasePath}-${item.id}`; - - await createData( - `${docuHash(pageId)}.json`, - JSON.stringify(item, null, 2) - ); - - const markdown = await createData( - `${docuHash(pageId)}-content.mdx`, - item.data - ); - - return { - path: item.permalink, - component: apiItemComponent, - exact: true, - modules: { - content: markdown, - }, - sidebar: sidebarName, - }; - }); - - const promises = loadedApi.map(async (item) => { - const pageId = `site-${routeBasePath}-${item.id}`; - + // TODO: Address race condition leading to "Module not found" + // TODO: Determine if mdx cleanup should be separate yarn script + // + // const mdFiles = await Globby(["*.mdx"], { + // cwd: path.resolve(outputDir), + // }); + // mdFiles.map((mdx) => + // fs.unlink(`${outputDir}/${mdx}`, (err) => { + // if (err) { + // console.error( + // chalk.red(`Cleanup failed for "${outputDir}/${mdx}"`) + // ); + // } else { + // console.log( + // chalk.green(`Cleanup succeeded for "${outputDir}/${mdx}"`) + // ); + // } + // }) + // ); + + const mdTemplate = template + ? fs.readFileSync(template).toString() + : `--- +id: {{{id}}} +sidebar_label: {{{title}}} +hide_title: true +{{#api}} +hide_table_of_contents: true +{{/api}} +{{#api.json}} +api: {{{json}}} +{{/api.json}} +{{#api.method}} +sidebar_class_name: "{{{api.method}}} api-method" +{{/api.method}} +--- + +{{{markdown}}} + `; + + loadedApi.map(async (item) => { // Statically set custom plugin options - item["showExecuteButton"] = showExecuteButton; - item["showManualAuthentication"] = showManualAuthentication; - - 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, - }; - }); - - // Important: the layout component should not end with /, - // as it conflicts with the home doc - // Workaround fix for https://github.com/facebook/docusaurus/issues/2917 - const apiBaseRoute = normalizeUrl([baseUrl, routeBasePath]); - const basePath = apiBaseRoute === "/" ? "" : apiBaseRoute; - - async function rootRoute() { - const item = loadedApi[0]; - const pageId = `site-${routeBasePath}-${item.id}`; - - return { - path: basePath, - component: apiItemComponent, - exact: true, - modules: { - // TODO: "-content" should be inside hash to prevent name too long errors. - content: path.join(dataDir, `${docuHash(pageId)}-content.mdx`), - }, - sidebar: sidebarName, - }; - } - - const routes = (await Promise.all([ - ...promises, - ...docPromises, - rootRoute(), - ])) as RouteConfig[]; - - const apiBaseMetadataPath = await createData( - `${docuHash(`api-metadata-prop`)}.json`, - JSON.stringify( - { - apiSidebars: { - [sidebarName]: docSidebar.concat(sidebar), - }, - }, - null, - 2 - ) - ); + item.showExecuteButton = showExecuteButton; + item.showManualAuthentication = showManualAuthentication; + const markdown = + item.type === "api" ? createApiPageMD(item) : createInfoPageMD(item); + item.markdown = markdown; + if (item.type === "api") { + item.json = JSON.stringify(item.api); + } + const view = render(mdTemplate, item); + + if (item.type === "api") { + if (!fs.existsSync(`${outputDir}/${item.id}.mdx`)) { + try { + fs.writeFileSync(`${outputDir}/${item.id}.mdx`, view, "utf8"); + console.log( + chalk.green( + `Successfully created "${outputDir}/${item.id}.mdx"` + ) + ); + } catch { + console.error( + chalk.red(`Failed to write "${outputDir}/${item.id}.mdx"`) + ); + } + } + } - addRoute({ - path: basePath, - exact: false, // allow matching /api/* as well - component: apiLayoutComponent, // main api component (ApiPage) - routes, // subroute for each api - modules: { - apiMetadata: aliasedSource(apiBaseMetadataPath), - }, + // TODO: determine if we actually want/need this + if (item.type === "info") { + if (!fs.existsSync(`${outputDir}/index.mdx`)) { + try { + fs.writeFileSync(`${outputDir}/index.mdx`, view, "utf8"); + console.log( + chalk.green(`Successfully created "${outputDir}/index.mdx"`) + ); + } catch { + console.error( + chalk.red(`Failed to write "${outputDir}/index.mdx"`) + ); + } + } + } + return; }); + return loadedApi; + } catch (e) { + console.error(chalk.red(`Loading of api failed for "${contentPath}"`)); + throw e; + } + } - return; - }, + beforeLoadContent(); - configureWebpack( - _config: Configuration, - isServer: boolean, - { getJSLoader }: ConfigureWebpackUtils - ) { - const { - rehypePlugins, - remarkPlugins, - beforeDefaultRehypePlugins, - beforeDefaultRemarkPlugins, - } = options; + return { + name: "docusaurus-plugin-openapi", - return { - resolve: { - alias: { - "~api": pluginDataDirRoot, - }, - }, - module: { - rules: [ - { - test: /(\.mdx?)$/, - include: [dataDir].map(addTrailingPathSeparator), - use: [ - getJSLoader({ isServer }), - { - loader: require.resolve("@docusaurus/mdx-loader"), - options: { - remarkPlugins, - rehypePlugins, - beforeDefaultRehypePlugins, - beforeDefaultRemarkPlugins, - metadataPath: (mdxPath: string) => { - return mdxPath.replace(/(-content\.mdx?)$/, ".json"); - }, - }, - }, - ].filter(Boolean), - }, - ], - }, - }; + getPathsToWatch() { + return [contentPath]; }, }; } - -export { validateOptions } from "./options"; diff --git a/packages/docusaurus-plugin-openapi/src/openapi/openapi.ts b/packages/docusaurus-plugin-openapi/src/openapi/openapi.ts index d79971dd9..0680cca0e 100644 --- a/packages/docusaurus-plugin-openapi/src/openapi/openapi.ts +++ b/packages/docusaurus-plugin-openapi/src/openapi/openapi.ts @@ -7,12 +7,7 @@ import path from "path"; -import { - aliasedSitePath, - Globby, - GlobExcludeDefault, - normalizeUrl, -} from "@docusaurus/utils"; +import { Globby, GlobExcludeDefault } from "@docusaurus/utils"; import Converter from "@paloaltonetworks/openapi-to-postmanv2"; // @ts-ignore import sdk, { Collection } from "@paloaltonetworks/postman-collection"; @@ -22,12 +17,7 @@ import yaml from "js-yaml"; import JsonRefs from "json-refs"; import { kebabCase } from "lodash"; -import { - ApiMetadata, - ApiPageMetadata, - InfoPageMetadata, - DocPageMetadata, -} from "../types"; +import { ApiMetadata, ApiPageMetadata, InfoPageMetadata } from "../types"; import { sampleFromSchema } from "./createExample"; import { OpenApiObject, OpenApiObjectWithRef, TagObject } from "./types"; @@ -255,83 +245,16 @@ export async function readOpenapiFiles( } export async function processOpenapiFiles( - beforeApiItems: DocPageMetadata[], - files: OpenApiFiles[], - options: { - baseUrl: string; - routeBasePath: string; - siteDir: string; - } + files: OpenApiFiles[] ): Promise { const promises = files.map(async (file) => { const items = await processOpenapiFile(file.data); return items.map((item) => ({ ...item, - source: aliasedSitePath(file.source, options.siteDir), - sourceDirName: file.sourceDirName, })); }); const metadata = await Promise.all(promises); const items = metadata.flat(); - - let seen: { [key: string]: number } = {}; - for (let i = 0; i < items.length; i++) { - const baseId = items[i].id; - let count = seen[baseId]; - - let id; - if (count) { - id = `${baseId}-${count}`; - seen[baseId] = count + 1; - } else { - id = baseId; - seen[baseId] = 1; - } - - items[i].id = id; - items[i].unversionedId = id; - items[i].slug = "/" + id; - } - - for (let i = 0; i < items.length; i++) { - const current = items[i]; - let prev; - if (i === 0) { - // Set item.prev to last doc item - prev = beforeApiItems[beforeApiItems.length - 1]; - } else { - prev = items[i - 1]; - } - const next = items[i + 1]; - - current.permalink = normalizeUrl([ - options.baseUrl, - options.routeBasePath, - current.id, - ]); - - if (prev) { - current.previous = { - title: prev.title, - permalink: normalizeUrl([ - options.baseUrl, - options.routeBasePath, - prev.id, - ]), - }; - } - - if (next) { - current.next = { - title: next.title, - permalink: normalizeUrl([ - options.baseUrl, - options.routeBasePath, - next.id, - ]), - }; - } - } return items; } diff --git a/packages/docusaurus-plugin-openapi/src/options.ts b/packages/docusaurus-plugin-openapi/src/options.ts index 6320f86d1..3e47a50a3 100644 --- a/packages/docusaurus-plugin-openapi/src/options.ts +++ b/packages/docusaurus-plugin-openapi/src/options.ts @@ -5,101 +5,22 @@ * LICENSE file in the root directory of this source tree. * ========================================================================== */ -import type { - OptionValidationContext, - ValidationResult, -} from "@docusaurus/types"; -import { - Joi, - RemarkPluginsSchema, - RehypePluginsSchema, - AdmonitionsSchema, -} from "@docusaurus/utils-validation"; -import chalk from "chalk"; -import admonitions from "remark-admonitions"; +import { Joi } from "@docusaurus/utils-validation"; import type { PluginOptions } from "./types"; -export const DEFAULT_OPTIONS: Omit = { +export const DEFAULT_OPTIONS: PluginOptions = { path: "openapi.json", // Path to data on filesystem, relative to site dir. - routeBasePath: "api", // URL Route. - apiLayoutComponent: "@theme/ApiPage", - apiItemComponent: "@theme/ApiItem", - remarkPlugins: [], - rehypePlugins: [], - beforeDefaultRemarkPlugins: [], - beforeDefaultRehypePlugins: [], - admonitions: {}, - sidebarCollapsible: true, - sidebarCollapsed: true, showExecuteButton: true, showManualAuthentication: true, - beforeApiDocs: [], + outputDir: "api", }; export const OptionsSchema = Joi.object({ path: Joi.string().default(DEFAULT_OPTIONS.path), - routeBasePath: Joi.string() - // '' not allowed, see https://github.com/facebook/docusaurus/issues/3374 - // .allow('') "" - .default(DEFAULT_OPTIONS.routeBasePath), - sidebarCollapsible: Joi.boolean().default(DEFAULT_OPTIONS.sidebarCollapsible), - sidebarCollapsed: Joi.boolean().default(DEFAULT_OPTIONS.sidebarCollapsed), - apiLayoutComponent: Joi.string().default(DEFAULT_OPTIONS.apiLayoutComponent), - apiItemComponent: Joi.string().default(DEFAULT_OPTIONS.apiItemComponent), - remarkPlugins: RemarkPluginsSchema.default(DEFAULT_OPTIONS.remarkPlugins), - rehypePlugins: RehypePluginsSchema.default(DEFAULT_OPTIONS.rehypePlugins), - beforeDefaultRemarkPlugins: RemarkPluginsSchema.default( - DEFAULT_OPTIONS.beforeDefaultRemarkPlugins - ), - beforeDefaultRehypePlugins: RehypePluginsSchema.default( - DEFAULT_OPTIONS.beforeDefaultRehypePlugins - ), - admonitions: Joi.alternatives() - .try(AdmonitionsSchema, Joi.boolean().invalid(true)) - .default(DEFAULT_OPTIONS.admonitions), showExecuteButton: Joi.boolean().default(DEFAULT_OPTIONS.showExecuteButton), showManualAuthentication: Joi.boolean().default( DEFAULT_OPTIONS.showManualAuthentication ), - beforeApiDocs: Joi.array().default(DEFAULT_OPTIONS.beforeApiDocs), + outputDir: Joi.string().default(DEFAULT_OPTIONS.outputDir), }); - -export function validateOptions({ - validate, - options: userOptions, -}: OptionValidationContext): ValidationResult { - let options = userOptions; - - if (options.sidebarCollapsible === false) { - // When sidebarCollapsible=false and sidebarCollapsed=undefined, we don't want to have the inconsistency warning - // We let options.sidebarCollapsible become the default value for options.sidebarCollapsed - if (typeof options.sidebarCollapsed === "undefined") { - options = { - ...options, - sidebarCollapsed: false, - }; - } - if (options.sidebarCollapsed) { - console.warn( - chalk.yellow( - "The docs plugin config is inconsistent. It does not make sense to use sidebarCollapsible=false and sidebarCollapsed=true at the same time. sidebarCollapsed=false will be ignored." - ) - ); - options = { - ...options, - sidebarCollapsed: false, - }; - } - } - - const normalizedOptions = validate(OptionsSchema, options); - - if (normalizedOptions.admonitions) { - normalizedOptions.remarkPlugins = normalizedOptions.remarkPlugins.concat([ - [admonitions, normalizedOptions.admonitions], - ]); - } - - return normalizedOptions; -} diff --git a/packages/docusaurus-plugin-openapi/src/plugin-content-docs-types.d.ts b/packages/docusaurus-plugin-openapi/src/plugin-content-docs-types.d.ts new file mode 100644 index 000000000..0fceb8b4b --- /dev/null +++ b/packages/docusaurus-plugin-openapi/src/plugin-content-docs-types.d.ts @@ -0,0 +1,42 @@ +/* ============================================================================ + * Copyright (c) Cloud Annotations + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * ========================================================================== */ + +declare module "@docusaurus/plugin-content-docs-types" { + // Makes all properties visible when hovering over the type + type Expand> = { [P in keyof T]: T[P] }; + + export type SidebarItemBase = { + className?: string; + customProps?: Record; + }; + + export type SidebarItemLink = SidebarItemBase & { + type: "link"; + href: string; + label: string; + docId: string; + }; + + type SidebarItemCategoryBase = SidebarItemBase & { + type: "category"; + label: string; + collapsed: boolean; + collapsible: boolean; + }; + + export type PropSidebarItemCategory = Expand< + SidebarItemCategoryBase & { + items: PropSidebarItem[]; + } + >; + + export type PropSidebarItem = SidebarItemLink | PropSidebarItemCategory; + export type PropSidebar = PropSidebarItem[]; + export type PropSidebars = { + [sidebarId: string]: PropSidebar; + }; +} diff --git a/packages/docusaurus-plugin-openapi/src/plugin-openapi.d.ts b/packages/docusaurus-plugin-openapi/src/plugin-openapi.d.ts index 65690f48c..b59bd3b6d 100644 --- a/packages/docusaurus-plugin-openapi/src/plugin-openapi.d.ts +++ b/packages/docusaurus-plugin-openapi/src/plugin-openapi.d.ts @@ -5,43 +5,10 @@ * LICENSE file in the root directory of this source tree. * ========================================================================== */ -// TODO: figure out how to import this -declare module "@docusaurus/plugin-content-docs-types" { - // Makes all properties visible when hovering over the type - type Expand> = { [P in keyof T]: T[P] }; - - export type SidebarItemBase = { - className?: string; - customProps?: Record; - }; - - export type SidebarItemLink = SidebarItemBase & { - type: "link"; - href: string; - label: string; - docId: string; - }; - - type SidebarItemCategoryBase = SidebarItemBase & { - type: "category"; - label: string; - collapsed: boolean; - collapsible: boolean; - }; - - export type PropSidebarItemCategory = Expand< - SidebarItemCategoryBase & { - items: PropSidebarItem[]; - } - >; - - export type PropSidebarItem = SidebarItemLink | PropSidebarItemCategory; - export type PropSidebar = PropSidebarItem[]; - export type PropSidebars = { - [sidebarId: string]: PropSidebar; - }; -} +import type { FrontMatter as DocsFrontMatter } from "@docusaurus/types"; +import type { Props as DocsProps } from "@docusaurus/types"; +// TODO: figure out how to import this declare module "@paloaltonetworks/docusaurus-plugin-openapi" { import type { PropSidebars } from "@docusaurus/plugin-content-docs-types"; @@ -90,15 +57,9 @@ declare module "@theme/ApiItem" { readonly sidebar?: string; }; - export type FrontMatter = { - readonly id: string; - readonly title: string; - readonly image?: string; - readonly keywords?: readonly string[]; - readonly hide_table_of_contents?: boolean; - readonly toc_min_heading_level?: number; - readonly toc_max_heading_level?: number; - }; + export interface FrontMatter extends DocsFrontMatter { + readonly api?: object; + } export type Metadata = { readonly description?: string; @@ -112,7 +73,7 @@ declare module "@theme/ApiItem" { readonly type?: string; }; - export interface Props { + export interface Props extends DocsProps { readonly route: ApiRoute; readonly content: { readonly frontMatter: FrontMatter; diff --git a/packages/docusaurus-plugin-openapi/src/remark-admonitions.d.ts b/packages/docusaurus-plugin-openapi/src/remark-admonitions.d.ts deleted file mode 100644 index a98af0b01..000000000 --- a/packages/docusaurus-plugin-openapi/src/remark-admonitions.d.ts +++ /dev/null @@ -1,13 +0,0 @@ -/* ============================================================================ - * Copyright (c) Cloud Annotations - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - * ========================================================================== */ - -declare module "remark-admonitions" { - type Options = any; - - const plugin: (options?: Options) => void; - export = plugin; -} diff --git a/packages/docusaurus-plugin-openapi/src/sidebars/index.ts b/packages/docusaurus-plugin-openapi/src/sidebars/index.ts deleted file mode 100644 index 72c3916f1..000000000 --- a/packages/docusaurus-plugin-openapi/src/sidebars/index.ts +++ /dev/null @@ -1,291 +0,0 @@ -/* ============================================================================ - * Copyright (c) Cloud Annotations - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - * ========================================================================== */ - -import path from "path"; - -import type { PluginOptions } from "@docusaurus/plugin-content-docs"; -import { posixPath } from "@docusaurus/utils"; -import chalk from "chalk"; -import clsx from "clsx"; -import fs from "fs-extra"; -import Yaml from "js-yaml"; -import { groupBy, uniq } from "lodash"; -import type { DeepPartial } from "utility-types"; - -import type { - InfoPageMetadata, - PropSidebar, - PropSidebarItemCategory, -} from "../types"; -import { ApiPageMetadata, DocPageMetadata } from "../types"; -import { CategoryMetadataFile } from "./types"; -import { validateCategoryMetadataFile } from "./validation"; - -interface Options { - contentPath: string; - sidebarCollapsible: boolean; - sidebarCollapsed: boolean; -} - -// Statically define CategoryMetadataFilenameBase as -// './lib/sidebars/validation' is not defined by "exports" -const CategoryMetadataFilenameBase = "_category_"; - -type keys = "type" | "title" | "permalink" | "id" | "source" | "sourceDirName"; -// Gotta be a better way man -type docKeys = - | "type" - | "title" - | "permalink" - | "id" - | "source" - | "sourceDirName" - | "frontMatter"; - -type InfoItem = Pick; -type ApiItem = Pick & { - api: DeepPartial; -}; -type DocItem = Pick; - -type Item = InfoItem | ApiItem | DocItem; - -// If a path is provided, make it absolute -// use this before loadSidebars() -export function resolveSidebarPathOption( - siteDir: string, - sidebarPathOption: PluginOptions["sidebarPath"] -): PluginOptions["sidebarPath"] { - return sidebarPathOption - ? path.resolve(siteDir, sidebarPathOption) - : sidebarPathOption; -} - -function isApiItem(item: Item): item is ApiItem { - return item.type === "api"; -} - -function isInfoItem(item: Item): item is InfoItem { - return item.type === "info"; -} - -function isDocItem(item: Item): item is DocItem { - return item.type === "doc"; -} - -const Terminator = "."; // a file or folder can never be "." -const BreadcrumbSeparator = "/"; -function getBreadcrumbs(dir: string) { - if (dir === Terminator) { - // this isn't actually needed, but removing would result in an array: [".", "."] - return [Terminator]; - } - return [...dir.split(BreadcrumbSeparator).filter(Boolean), Terminator]; -} - -export async function generateSidebar( - items: Item[], - options: Options -): Promise { - const sourceGroups = groupBy(items, (item) => item.source); - let sidebar: PropSidebar = []; - let visiting = sidebar; - let docsSidebar = sidebar; - - if (items.length > 0 && items[0].type === "doc") { - for (const item of items as DocItem[]) { - const { sidebar_label } = item.frontMatter; - docsSidebar.push({ - type: "link" as const, - label: (sidebar_label as string) ?? item.id ?? item.title, - href: item.permalink, - docId: item.id, - }); - } - return docsSidebar; - } - - for (const items of Object.values(sourceGroups)) { - if (items.length === 0) { - // Since the groups are created based on the items, there should never be a length of zero. - console.warn(chalk.yellow(`Unnexpected empty group!`)); - continue; - } - - const { sourceDirName, source } = items[0]; - - const breadcrumbs = getBreadcrumbs(sourceDirName); - - let currentPath = []; - for (const crumb of breadcrumbs) { - // We hit a spec file, create the groups for it. - 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), - }); - visiting = sidebar; // reset - break; - } - - // Read category file to generate a label for the current path. - currentPath.push(crumb); - const categoryPath = path.join(options.contentPath, ...currentPath); - const meta = await readCategoryMetadataFile(categoryPath); - const label = meta?.label ?? crumb; - - // Check for existing categories for the current label. - const existingCategory = visiting - .filter((c): c is PropSidebarItemCategory => c.type === "category") - .find((c) => c.label === label); - - // If exists, skip creating a new one. - if (existingCategory) { - visiting = existingCategory.items; - continue; - } - - // Otherwise, create a new one. - const newCategory = { - type: "category" as const, - label, - collapsible: options.sidebarCollapsible, - collapsed: options.sidebarCollapsed, - items: [], - }; - visiting.push(newCategory); - visiting = newCategory.items; - } - } - - // The first group should always be a category, but check for type narrowing - if (sidebar.length === 1 && sidebar[0].type === "category") { - return sidebar[0].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 docs = items.filter(isDocItem).map((item) => { - const sidebarLabel = item.frontMatter.sidebar_label as string; - return { - type: "link" as const, - label: sidebarLabel ?? item.id ?? item.title, - href: item.permalink, - docId: item.id, - }; - }); - - const intros = items.filter(isInfoItem).map((item) => { - return { - type: "link" as const, - label: item.title, - href: item.permalink, - docId: item.id, - }; - }); - - const apiItems = items.filter(isApiItem); - - const tags = uniq( - apiItems - .flatMap((item) => item.api.tags) - .filter((item): item is string => !!item) - ); - - function createLink(item: ApiItem) { - return { - type: "link" as const, - label: item.title, - href: item.permalink, - docId: item.id, - className: clsx( - { - "menu__list-item--deprecated": item.api.deprecated, - "api-method": !!item.api.method, - }, - item.api.method - ), - }; - } - - const tagged = tags - .map((tag) => { - return { - type: "category" as const, - label: tag, - collapsible: options.sidebarCollapsible, - collapsed: options.sidebarCollapsed, - items: apiItems - .filter((item) => !!item.api.tags?.includes(tag)) - .map(createLink), - }; - }) - .filter((item) => item.items.length > 0); // Filter out any categories with no items. - - const untaggedItems = apiItems.filter( - (item) => item.api.tags === undefined || item.api.tags.length === 0 - ); - const untagged = - untaggedItems.length > 0 - ? [ - { - type: "category" as const, - label: "API", - collapsible: options.sidebarCollapsible, - collapsed: options.sidebarCollapsed, - items: untaggedItems.map(createLink), - }, - ] - : []; - - return [...docs, ...intros, ...tagged, ...untagged]; -} - -/** - * Taken from: https://github.com/facebook/docusaurus/blob/main/packages/docusaurus-plugin-content-docs/src/sidebars/generator.ts - */ -async function readCategoryMetadataFile( - categoryDirPath: string -): Promise { - async function tryReadFile(filePath: string): Promise { - const contentString = await fs.readFile(filePath, { encoding: "utf8" }); - const unsafeContent = Yaml.load(contentString); - try { - return validateCategoryMetadataFile(unsafeContent); - } catch (e) { - console.error( - chalk.red( - `The docs sidebar category metadata file path=${filePath} looks invalid!` - ) - ); - throw e; - } - } - // eslint-disable-next-line no-restricted-syntax - for (const ext of [".json", ".yml", ".yaml"]) { - // Simpler to use only posix paths for mocking file metadata in tests - const filePath = posixPath( - path.join(categoryDirPath, `${CategoryMetadataFilenameBase}${ext}`) - ); - if (await fs.pathExists(filePath)) { - return tryReadFile(filePath); - } - } - return null; -} diff --git a/packages/docusaurus-plugin-openapi/src/sidebars/sidebars.test.ts b/packages/docusaurus-plugin-openapi/src/sidebars/sidebars.test.ts deleted file mode 100644 index e10af9384..000000000 --- a/packages/docusaurus-plugin-openapi/src/sidebars/sidebars.test.ts +++ /dev/null @@ -1,416 +0,0 @@ -/* ============================================================================ - * Copyright (c) Cloud Annotations - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - * ========================================================================== */ - -import { generateSidebar } from "."; -import type { - PropSidebarItemCategory, - SidebarItemLink, - PropSidebarItem, -} from "../types"; - -// npx jest packages/docusaurus-plugin-openapi/src/sidebars/sidebars.test.ts --watch - -function isCategory(item: PropSidebarItem): item is PropSidebarItemCategory { - return item.type === "category"; -} -function isLink(item: PropSidebarItem): item is SidebarItemLink { - return item.type === "link"; -} - -describe("sidebars", () => { - const getOpts = () => ({ - contentPath: "", - sidebarCollapsible: true, - sidebarCollapsed: true, - }); - - const getIntro = (overrides = {}) => ({ - type: "info" as const, - id: "introduction", - unversionedId: "introduction", - title: "Introduction", - description: "Sample description.", - slug: "/introduction", - frontMatter: {}, - info: { - title: "YAML Example", - version: "1.0.0", - description: "Sample description.", - }, - source: "@site/examples/openapi.yaml", - sourceDirName: ".", - permalink: "/yaml/introduction", - next: { - title: "Hello World", - permalink: "/yaml/hello-world", - }, - ...overrides, - }); - - describe("Single Spec - YAML", () => { - it("base case - single spec with untagged routes should render flat with a default category", async () => { - const input = [ - getIntro(), - { - type: "api" as const, - id: "hello-world", - title: "Hello World", - api: { - tags: [], - }, - source: "@site/examples/openapi.yaml", - sourceDirName: ".", - permalink: "/yaml/hello-world", - }, - ]; - - const output = await generateSidebar(input, getOpts()); - // console.log(JSON.stringify(output, null, 2)); - - // intro.md - const info = output.find( - (x) => x.type === "link" && x.docId === "introduction" - ) as SidebarItemLink; - expect(info?.type).toBe("link"); - expect(info?.label).toBe("Introduction"); - expect(info?.href).toBe("/yaml/introduction"); - - const category = output.find(isCategory); - expect(category?.label).toBe("API"); - - const api = category?.items.find(isLink); - expect(api?.label).toBe("Hello World"); - expect(api?.docId).toBe("hello-world"); - }); - - it("single spec tags case - should render root level categories per tag", async () => { - const input = [ - getIntro(), - { - type: "api" as const, - id: "hello-world", - title: "Hello World", - api: { - tags: ["stuff"], - }, - source: "@site/examples/openapi.yaml", - sourceDirName: ".", - permalink: "/yaml/hello-world", - }, - ]; - - const output = await generateSidebar(input, getOpts()); - // console.log(JSON.stringify(output, null, 2)); - - // intro.md - const info = output.find( - (x) => x.type === "link" && x.docId === "introduction" - ) as SidebarItemLink; - expect(info?.type).toBe("link"); - expect(info?.label).toBe("Introduction"); - expect(info?.href).toBe("/yaml/introduction"); - - // swagger rendering - const api = output.find( - (x) => x.type === "category" - ) as PropSidebarItemCategory; - expect(api?.label).toBe("stuff"); - expect(api?.items).toBeInstanceOf(Array); - expect(api?.items).toHaveLength(1); - - const [helloWorld] = api?.items ?? []; - expect(helloWorld.type).toBe("link"); - expect(helloWorld.label).toBe("Hello World"); - }); - }); - describe("Multi Spec", () => { - it("should leverage the info.title if provided for spec name @ root category", async () => { - const input = [ - { - type: "api" as const, - id: "cats", - title: "Cats", - api: { - info: { title: "Cats" }, - tags: [], - }, - source: "@site/examples/cats.yaml", - sourceDirName: ".", - permalink: "/yaml/cats", - }, - { - type: "api" as const, - id: "dogs", - title: "Dogs", - api: { - info: { title: "Dogs" }, - tags: [], - }, - source: "@site/examples/dogs.yaml", - sourceDirName: ".", - permalink: "/yaml/dogs", - }, - ]; - - const output = (await generateSidebar( - input, - getOpts() - )) as PropSidebarItemCategory[]; - - // console.log(JSON.stringify(output, null, 2)); - expect(output).toHaveLength(2); - const [cats, dogs] = output; - expect(cats.type).toBe("category"); - expect(cats.items).toHaveLength(1); - - const [catApi] = (cats.items ?? []).filter(isCategory); - expect(catApi.type).toBe("category"); - const [catLink] = catApi?.items; - expect(catLink.type).toBe("link"); - expect(dogs.type).toBe("category"); - expect(dogs.items).toHaveLength(1); - expect(dogs.label).toBe("Dogs"); - }); - - it("empty title should render the filename.", async () => { - const input = [ - { - type: "api" as const, - id: "cats", - title: "Cats", - api: { - info: { title: "Cats" }, - tags: [], - }, - source: "@site/examples/cats.yaml", - sourceDirName: ".", - permalink: "/yaml/cats", - }, - { - type: "api" as const, - id: "dogs", - title: "List Dogs", - api: { - info: { title: "" }, - tags: [], - }, - source: "@site/examples/dogs.yaml", - sourceDirName: ".", - permalink: "/yaml/dogs", - }, - { - type: "api" as const, - id: "dogs-id", - title: "Dog By Id", - api: { - info: { title: "" }, - tags: [], - }, - source: "@site/examples/dogs.yaml", - sourceDirName: ".", - permalink: "/yaml/dogs-id", - }, - ]; - - const output = (await generateSidebar( - input, - getOpts() - )) as PropSidebarItemCategory[]; - - // console.log(JSON.stringify(output, null, 2)); - const [cats, dogsSpec] = output; - expect(cats.items).toHaveLength(1); - expect(dogsSpec.type).toBe("category"); - const [dogApi] = dogsSpec.items.filter(isCategory); - expect(dogApi?.items).toHaveLength(2); - expect(dogApi.label).toBe("API"); - const [dogsItem] = dogApi.items; - expect(dogsItem.label).toBe("List Dogs"); - }); - - it("multi spec, multi tag", async () => { - const input = [ - { - type: "api" as const, - id: "tails", - title: "List Tails", - api: { - info: { title: "Cats" }, - tags: ["Tails"], - }, - source: "@site/examples/cats.yaml", - sourceDirName: ".", - permalink: "/yaml/tails", - }, - { - type: "api" as const, - id: "tails-by-id", - title: "Tails By Id", - api: { - info: { title: "Cats" }, - tags: ["Tails"], - }, - source: "@site/examples/cats.yaml", - sourceDirName: ".", - permalink: "/yaml/tails-by-id", - }, - { - type: "api" as const, - id: "whiskers", - title: "List whiskers", - api: { - info: { title: "Cats" }, - tags: ["Whiskers"], - }, - source: "@site/examples/cats.yaml", - sourceDirName: ".", - permalink: "/yaml/whiskers", - }, - { - type: "api" as const, - id: "dogs", - title: "List Dogs", - api: { - info: { title: "Dogs" }, - tags: ["Doggos"], - }, - source: "@site/examples/dogs.yaml", - sourceDirName: ".", - permalink: "/yaml/dogs", - }, - { - type: "api" as const, - id: "dogs-id", - title: "Dogs By Id", - api: { - info: { title: "Dogs" }, - tags: ["Doggos"], - }, - source: "@site/examples/dogs.yaml", - sourceDirName: ".", - permalink: "/yaml/dogs-id", - }, - { - type: "api" as const, - id: "toys", - title: "Toys", - api: { - info: { title: "Dogs" }, - tags: ["Toys"], - }, - source: "@site/examples/dogs.yaml", - sourceDirName: ".", - permalink: "/yaml/toys", - }, - ]; - - const output = (await generateSidebar( - input, - getOpts() - )) as PropSidebarItemCategory[]; - - // console.log(JSON.stringify(output, null, 2)); - const [cats, dogs] = output; - expect(cats.type).toBe("category"); - expect(cats.items).toHaveLength(2); // extra api item category no longer included - const [tails, whiskers] = (cats.items || []).filter(isCategory); - expect(tails.type).toBe("category"); - expect(whiskers.type).toBe("category"); - expect(tails.items).toHaveLength(2); - expect(whiskers.items).toHaveLength(1); - expect(tails.items?.[0].type).toBe("link"); - expect(whiskers.items?.[0].type).toBe("link"); - expect(tails.items?.[0].label).toBe("List Tails"); - expect(whiskers.items?.[0].label).toBe("List whiskers"); - - expect(dogs.type).toBe("category"); - expect(dogs.items).toHaveLength(2); // extra api item category no longer included - expect(dogs.label).toBe("Dogs"); - const [doggos, toys] = (dogs.items || []) as PropSidebarItemCategory[]; - expect(doggos.type).toBe("category"); - expect(toys.type).toBe("category"); - expect(doggos.items).toHaveLength(2); - expect(toys.items).toHaveLength(1); - }); - - it("child folders", async () => { - const input = [ - { - type: "api" as const, - id: "cats", - title: "Cats", - api: { - info: { title: "Cat Store" }, - tags: ["Cats"], - }, - source: "@site/examples/animals/pets/cats.yaml", - sourceDirName: "animals/pets", - permalink: "/yaml/cats", - }, - { - type: "api" as const, - id: "burgers", - title: "Burgers", - api: { - info: { title: "Burger Store" }, - tags: ["Burgers"], - }, - source: "@site/examples/food/fast/burgers.yaml", - sourceDirName: "food/fast", - permalink: "/yaml/burgers", - }, - ]; - - const output = await generateSidebar(input, getOpts()); - expect(output).toBeTruthy(); - // console.log(JSON.stringify(output, null, 2)); - // console.log(output); - const [animals, foods] = output; - expect(animals.type).toBe("category"); - expect(foods.type).toBe("category"); - - /* - animals - pets - Cat Store - cats - Foods - Buger Store - Burger Example - burgers - */ - output.filter(isCategory).forEach((category) => { - // console.log(category.label); - expect(category.items[0].type).toBe("category"); - category.items.filter(isCategory).forEach((subCategory) => { - expect(subCategory.items[0].type).toBe("category"); - subCategory.items.filter(isCategory).forEach((groupCategory) => { - expect(groupCategory.items[0].type).toBe("category"); - groupCategory.items.forEach((linkItem) => { - expect(linkItem.type).toBe("category"); - }); - }); - }); - }); - }); - it("child folders with no paths", async () => { - const input = [ - getIntro({ - source: "@site/examples/foods/foods.yaml", - sourceDirName: "foods", - }), - getIntro({ - source: "@site/examples/animals/animals.yaml", - sourceDirName: "animals", - }), - ]; - const output = await generateSidebar(input, getOpts()); - expect(output).toBeTruthy(); - expect(output[0].type).toBe("category"); - }); - }); -}); diff --git a/packages/docusaurus-plugin-openapi/src/sidebars/types.ts b/packages/docusaurus-plugin-openapi/src/sidebars/types.ts deleted file mode 100644 index e39ad5a1b..000000000 --- a/packages/docusaurus-plugin-openapi/src/sidebars/types.ts +++ /dev/null @@ -1,268 +0,0 @@ -/* ============================================================================ - * Copyright (c) Cloud Annotations - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - * ========================================================================== */ - -import type { - NumberPrefixParser, - SidebarOptions, - CategoryIndexMatcher, -} from "@docusaurus/plugin-content-docs"; -import type { Slugger } from "@docusaurus/utils"; -import type { Optional, Required } from "utility-types"; - -import type { ApiMetadataBase } from "../types"; - -// Makes all properties visible when hovering over the type -type Expand> = { [P in keyof T]: T[P] }; - -export type SidebarItemBase = { - className?: string; - customProps?: Record; -}; - -export type SidebarItemDoc = SidebarItemBase & { - type: "doc" | "ref"; - label?: string; - id: string; -}; - -export type SidebarItemHtml = SidebarItemBase & { - type: "html"; - value: string; - defaultStyle?: boolean; -}; - -export type SidebarItemLink = SidebarItemBase & { - type: "link"; - href: string; - label: string; -}; - -export type SidebarItemAutogenerated = SidebarItemBase & { - type: "autogenerated"; - dirName: string; -}; - -type SidebarItemCategoryBase = SidebarItemBase & { - type: "category"; - label: string; - collapsed: boolean; - collapsible: boolean; -}; - -export type SidebarItemCategoryLinkDoc = { type: "doc"; id: string }; - -export type SidebarItemCategoryLinkGeneratedIndexConfig = { - type: "generated-index"; - slug?: string; - title?: string; - description?: string; - image?: string; - keywords?: string | readonly string[]; -}; -export type SidebarItemCategoryLinkGeneratedIndex = { - type: "generated-index"; - slug: string; - permalink: string; - title?: string; - description?: string; - image?: string; - keywords?: string | readonly string[]; -}; - -export type SidebarItemCategoryLinkConfig = - | SidebarItemCategoryLinkDoc - | SidebarItemCategoryLinkGeneratedIndexConfig; - -export type SidebarItemCategoryLink = - | SidebarItemCategoryLinkDoc - | SidebarItemCategoryLinkGeneratedIndex; - -// The user-given configuration in sidebars.js, before normalization -export type SidebarItemCategoryConfig = Expand< - Optional & { - items: SidebarCategoriesShorthand | SidebarItemConfig[]; - link?: SidebarItemCategoryLinkConfig; - } ->; - -export type SidebarCategoriesShorthand = { - [sidebarCategory: string]: SidebarCategoriesShorthand | SidebarItemConfig[]; -}; - -export type SidebarItemConfig = - | SidebarItemDoc - | SidebarItemHtml - | SidebarItemLink - | SidebarItemAutogenerated - | SidebarItemCategoryConfig - | string - | SidebarCategoriesShorthand; - -export type SidebarConfig = SidebarCategoriesShorthand | SidebarItemConfig[]; -export type SidebarsConfig = { - [sidebarId: string]: SidebarConfig; -}; - -// Normalized but still has 'autogenerated', which will be handled in processing -export type NormalizedSidebarItemCategory = Expand< - Optional & { - items: NormalizedSidebarItem[]; - link?: SidebarItemCategoryLinkConfig; - } ->; - -export type NormalizedSidebarItem = - | SidebarItemDoc - | SidebarItemHtml - | SidebarItemLink - | NormalizedSidebarItemCategory - | SidebarItemAutogenerated; - -export type NormalizedSidebar = NormalizedSidebarItem[]; -export type NormalizedSidebars = { - [sidebarId: string]: NormalizedSidebar; -}; - -export type ProcessedSidebarItemCategory = Expand< - Optional & { - items: ProcessedSidebarItem[]; - link?: SidebarItemCategoryLinkConfig; - } ->; -export type ProcessedSidebarItem = - | SidebarItemDoc - | SidebarItemHtml - | SidebarItemLink - | ProcessedSidebarItemCategory; -export type ProcessedSidebar = ProcessedSidebarItem[]; -export type ProcessedSidebars = { - [sidebarId: string]: ProcessedSidebar; -}; - -export type SidebarItemCategory = Expand< - SidebarItemCategoryBase & { - items: SidebarItem[]; - link?: SidebarItemCategoryLink; - } ->; - -export type SidebarItemCategoryWithLink = Required; - -export type SidebarItemCategoryWithGeneratedIndex = - SidebarItemCategoryWithLink & { link: SidebarItemCategoryLinkGeneratedIndex }; - -export type SidebarItem = - | SidebarItemDoc - | SidebarItemHtml - | SidebarItemLink - | SidebarItemCategory; - -// A sidebar item that is part of the previous/next ordered navigation -export type SidebarNavigationItem = - | SidebarItemDoc - | SidebarItemCategoryWithLink; - -export type Sidebar = SidebarItem[]; -export type SidebarItemType = SidebarItem["type"]; -export type Sidebars = { - [sidebarId: string]: Sidebar; -}; - -// Doc links have been resolved to URLs, ready to be passed to the theme -export type PropSidebarItemCategory = Expand< - SidebarItemCategoryBase & { - items: PropSidebarItem[]; - href?: string; - } ->; - -// we may want to use a union type in props instead of this generic link? -export type PropSidebarItemLink = SidebarItemLink & { - docId?: string; -}; - -export type PropSidebarItemHtml = SidebarItemHtml; - -export type PropSidebarItem = - | PropSidebarItemLink - | PropSidebarItemCategory - | PropSidebarItemHtml; -export type PropSidebar = PropSidebarItem[]; -export type PropSidebars = { - [sidebarId: string]: PropSidebar; -}; - -export type PropSidebarBreadcrumbsItem = - | PropSidebarItemLink - | PropSidebarItemCategory; - -export type PropVersionDoc = { - id: string; - title: string; - description?: string; - sidebar?: string; -}; -export type PropVersionDocs = { - [docId: string]: PropVersionDoc; -}; - -export type CategoryMetadataFile = { - label?: string; - position?: number; - collapsed?: boolean; - collapsible?: boolean; - className?: string; - link?: SidebarItemCategoryLinkConfig | null; - - // TODO should we allow "items" here? how would this work? would an - // "autogenerated" type be allowed? - // This mkdocs plugin do something like that: https://github.com/lukasgeiter/mkdocs-awesome-pages-plugin/ - // cf comment: https://github.com/facebook/docusaurus/issues/3464#issuecomment-784765199 -}; - -// Reduce API surface for options.sidebarItemsGenerator -// The user-provided generator fn should receive only a subset of metadata -// A change to any of these metadata can be considered as a breaking change -export type SidebarItemsGeneratorDoc = Pick< - ApiMetadataBase, - | "id" - | "unversionedId" - | "frontMatter" - | "source" - | "sourceDirName" - | "sidebarPosition" ->; - -export type SidebarItemsGeneratorArgs = { - item: SidebarItemAutogenerated; - docs: SidebarItemsGeneratorDoc[]; - numberPrefixParser: NumberPrefixParser; - isCategoryIndex: CategoryIndexMatcher; - categoriesMetadata: Record; - options: SidebarOptions; -}; -export type SidebarItemsGenerator = ( - generatorArgs: SidebarItemsGeneratorArgs -) => Promise; - -// Also inject the default generator to conveniently wrap/enhance/sort the -// default sidebar gen logic -// see https://github.com/facebook/docusaurus/issues/4640#issuecomment-822292320 -export type SidebarItemsGeneratorOptionArgs = { - defaultSidebarItemsGenerator: SidebarItemsGenerator; -} & SidebarItemsGeneratorArgs; -export type SidebarItemsGeneratorOption = ( - generatorArgs: SidebarItemsGeneratorOptionArgs -) => Promise; - -export type SidebarProcessorParams = { - sidebarItemsGenerator: SidebarItemsGeneratorOption; - numberPrefixParser: NumberPrefixParser; - docs: ApiMetadataBase[]; - categoryLabelSlugger: Slugger; - sidebarOptions: SidebarOptions; -}; diff --git a/packages/docusaurus-plugin-openapi/src/sidebars/validation.ts b/packages/docusaurus-plugin-openapi/src/sidebars/validation.ts deleted file mode 100644 index 4b1fd3208..000000000 --- a/packages/docusaurus-plugin-openapi/src/sidebars/validation.ts +++ /dev/null @@ -1,176 +0,0 @@ -/* ============================================================================ - * Copyright (c) Cloud Annotations - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - * ========================================================================== */ - -import { Joi, URISchema } from "@docusaurus/utils-validation"; - -import type { - SidebarItemConfig, - SidebarItemBase, - SidebarItemAutogenerated, - SidebarItemDoc, - SidebarItemHtml, - SidebarItemLink, - SidebarItemCategoryConfig, - SidebarItemCategoryLink, - SidebarItemCategoryLinkDoc, - SidebarItemCategoryLinkGeneratedIndex, - NormalizedSidebars, - NormalizedSidebarItem, - NormalizedSidebarItemCategory, - CategoryMetadataFile, -} from "./types"; - -// NOTE: we don't add any default values during validation on purpose! -// Config types are exposed to users for typechecking and we use the same type -// in normalization - -const sidebarItemBaseSchema = Joi.object({ - className: Joi.string(), - customProps: Joi.object().unknown(), -}); - -const sidebarItemAutogeneratedSchema = - sidebarItemBaseSchema.append({ - type: "autogenerated", - dirName: Joi.string() - .required() - .pattern(/^[^/](?:.*[^/])?$/) - .message( - '"dirName" must be a dir path relative to the docs folder root, and should not start or end with slash' - ), - }); - -const sidebarItemDocSchema = sidebarItemBaseSchema.append({ - type: Joi.string().valid("doc", "ref").required(), - id: Joi.string().required(), - label: Joi.string(), -}); - -const sidebarItemHtmlSchema = sidebarItemBaseSchema.append({ - type: "html", - value: Joi.string().required(), - defaultStyle: Joi.boolean(), -}); - -const sidebarItemLinkSchema = sidebarItemBaseSchema.append({ - type: "link", - href: URISchema.required(), - label: Joi.string() - .required() - .messages({ "any.unknown": '"label" must be a string' }), -}); - -const sidebarItemCategoryLinkSchema = Joi.object() - .allow(null) - .when(".type", { - switch: [ - { - is: "doc", - then: Joi.object({ - type: "doc", - id: Joi.string().required(), - }), - }, - { - is: "generated-index", - then: Joi.object({ - type: "generated-index", - slug: Joi.string().optional(), - // This one is not in the user config, only in the normalized version - // permalink: Joi.string().optional(), - title: Joi.string().optional(), - description: Joi.string().optional(), - image: Joi.string().optional(), - keywords: [Joi.string(), Joi.array().items(Joi.string())], - }), - }, - { - is: Joi.required(), - then: Joi.forbidden().messages({ - "any.unknown": 'Unknown sidebar category link type "{.type}".', - }), - }, - ], - }); - -const sidebarItemCategorySchema = - sidebarItemBaseSchema.append({ - type: "category", - label: Joi.string() - .required() - .messages({ "any.unknown": '"label" must be a string' }), - items: Joi.array() - .required() - .messages({ "any.unknown": '"items" must be an array' }), - // TODO: Joi doesn't allow mutual recursion. See https://github.com/sideway/joi/issues/2611 - // .items(Joi.link('#sidebarItemSchema')), - link: sidebarItemCategoryLinkSchema, - collapsed: Joi.boolean().messages({ - "any.unknown": '"collapsed" must be a boolean', - }), - collapsible: Joi.boolean().messages({ - "any.unknown": '"collapsible" must be a boolean', - }), - }); - -const sidebarItemSchema = Joi.object().when(".type", { - switch: [ - { is: "link", then: sidebarItemLinkSchema }, - { - is: Joi.string().valid("doc", "ref").required(), - then: sidebarItemDocSchema, - }, - { is: "html", then: sidebarItemHtmlSchema }, - { is: "autogenerated", then: sidebarItemAutogeneratedSchema }, - { is: "category", then: sidebarItemCategorySchema }, - { - is: Joi.any().required(), - then: Joi.forbidden().messages({ - "any.unknown": 'Unknown sidebar item type "{.type}".', - }), - }, - ], -}); -// .id('sidebarItemSchema'); - -function validateSidebarItem( - item: unknown -): asserts item is NormalizedSidebarItem { - // TODO: remove once with proper Joi support - // Because we can't use Joi to validate nested items (see above), we do it - // manually - Joi.assert(item, sidebarItemSchema); - - if ((item as NormalizedSidebarItemCategory).type === "category") { - (item as NormalizedSidebarItemCategory).items.forEach(validateSidebarItem); - } -} - -export function validateSidebars( - sidebars: Record -): asserts sidebars is NormalizedSidebars { - Object.values(sidebars as NormalizedSidebars).forEach((sidebar) => { - sidebar.forEach(validateSidebarItem); - }); -} - -const categoryMetadataFileSchema = Joi.object({ - label: Joi.string(), - position: Joi.number(), - collapsed: Joi.boolean(), - collapsible: Joi.boolean(), - className: Joi.string(), - link: sidebarItemCategoryLinkSchema, -}); - -export function validateCategoryMetadataFile( - unsafeContent: unknown -): CategoryMetadataFile { - return Joi.attempt(unsafeContent, categoryMetadataFileSchema); -} - -export {}; diff --git a/packages/docusaurus-plugin-openapi/src/types.ts b/packages/docusaurus-plugin-openapi/src/types.ts index 2ff4e01d8..6aee3d738 100644 --- a/packages/docusaurus-plugin-openapi/src/types.ts +++ b/packages/docusaurus-plugin-openapi/src/types.ts @@ -5,11 +5,9 @@ * LICENSE file in the root directory of this source tree. * ========================================================================== */ -import type { RemarkAndRehypePluginOptions } from "@docusaurus/mdx-loader"; // @ts-ignore import type { Request } from "@paloaltonetworks/postman-collection"; -import { DocObject } from "./docs/types"; import { InfoObject, OperationObject, @@ -22,23 +20,18 @@ export type { PropSidebar, PropSidebarItem, } from "@docusaurus/plugin-content-docs-types"; -export interface PluginOptions extends RemarkAndRehypePluginOptions { - id: string; +export interface PluginOptions { + id?: string; path: string; - routeBasePath: string; - apiLayoutComponent: string; - apiItemComponent: string; - admonitions: Record; - sidebarCollapsible: boolean; - sidebarCollapsed: boolean; showExecuteButton: boolean; showManualAuthentication: boolean; - beforeApiDocs: Array; + outputDir: string; + template?: string; } export interface LoadedContent { loadedApi: ApiMetadata[]; - loadedDocs: DocPageMetadata[]; + // loadedDocs: DocPageMetadata[]; TODO: cleanup } export type ApiMetadata = ApiPageMetadata | InfoPageMetadata; @@ -63,8 +56,10 @@ export interface ApiMetadataBase { } export interface ApiPageMetadata extends ApiMetadataBase { + json?: string; type: "api"; api: ApiItem; + markdown?: string; } export interface ApiItem extends OperationObject { @@ -81,11 +76,7 @@ export interface ApiItem extends OperationObject { export interface InfoPageMetadata extends ApiMetadataBase { type: "info"; info: ApiInfo; -} - -export interface DocPageMetadata extends ApiMetadataBase { - type: "doc"; - data: DocObject; + markdown?: string; } export type ApiInfo = InfoObject; diff --git a/packages/docusaurus-preset-openapi/src/preset-openapi.d.ts b/packages/docusaurus-preset-openapi/src/preset-openapi.d.ts index 819fd39b5..a6364997b 100644 --- a/packages/docusaurus-preset-openapi/src/preset-openapi.d.ts +++ b/packages/docusaurus-preset-openapi/src/preset-openapi.d.ts @@ -15,5 +15,6 @@ export type Options = { proxy?: false | import("@paloaltonetworks/docusaurus-plugin-proxy").Options; } & ClassicOptions; -export type ThemeConfig = import("docusaurus-theme-openapi").ThemeConfig & - ClassicThemeConfig; +export type ThemeConfig = + import("@paloaltonetworks/docusaurus-theme-openapi").ThemeConfig & + ClassicThemeConfig; diff --git a/packages/docusaurus-theme-openapi/src/theme-openapi.d.ts b/packages/docusaurus-theme-openapi/src/theme-openapi.d.ts index 3511c5b03..83b5c597a 100644 --- a/packages/docusaurus-theme-openapi/src/theme-openapi.d.ts +++ b/packages/docusaurus-theme-openapi/src/theme-openapi.d.ts @@ -7,6 +7,42 @@ /// +declare module "@docusaurus/plugin-content-docs-types" { + // Makes all properties visible when hovering over the type + type Expand> = { [P in keyof T]: T[P] }; + + export type SidebarItemBase = { + className?: string; + customProps?: Record; + }; + + export type SidebarItemLink = SidebarItemBase & { + type: "link"; + href: string; + label: string; + docId: string; + }; + + type SidebarItemCategoryBase = SidebarItemBase & { + type: "category"; + label: string; + collapsed: boolean; + collapsible: boolean; + }; + + export type PropSidebarItemCategory = Expand< + SidebarItemCategoryBase & { + items: PropSidebarItem[]; + } + >; + + export type PropSidebarItem = SidebarItemLink | PropSidebarItemCategory; + export type PropSidebar = PropSidebarItem[]; + export type PropSidebars = { + [sidebarId: string]: PropSidebar; + }; +} + declare module "docusaurus-theme-openapi" { export type ThemeConfig = Partial; } diff --git a/packages/docusaurus-theme-openapi/src/theme/ApiDemoPanel/VSCode/index.tsx b/packages/docusaurus-theme-openapi/src/theme/ApiDemoPanel/VSCode/index.tsx index f8114e297..fad39dfc3 100644 --- a/packages/docusaurus-theme-openapi/src/theme/ApiDemoPanel/VSCode/index.tsx +++ b/packages/docusaurus-theme-openapi/src/theme/ApiDemoPanel/VSCode/index.tsx @@ -5,10 +5,10 @@ * LICENSE file in the root directory of this source tree. * ========================================================================== */ -import React, { useState } from "react"; +import React, { useState, useEffect } from "react"; import { useColorMode } from "@docusaurus/theme-common"; -import Editor, { Monaco } from "@monaco-editor/react"; +import Editor, { useMonaco } from "@monaco-editor/react"; import styles from "./styles.module.css"; @@ -20,31 +20,78 @@ interface Props { function VSCode({ value, language, onChange }: Props) { const [focused, setFocused] = useState(false); - + const monaco = useMonaco(); const { isDarkTheme } = useColorMode(); - function handleEditorWillMount(monaco: Monaco) { + useEffect(() => { + // do conditional chaining + monaco?.languages.typescript.javascriptDefaults.setEagerModelSync(true); + // or make sure that it exists by other ways + if (monaco) { + console.log("here is the monaco instance:", monaco); + } + }, [monaco]); + + function handleEditorWillMount(monaco: any) { const styles = getComputedStyle(document.documentElement); + function getColor(property: string) { // Weird chrome bug, returns " #ffffff " instead of "#ffffff", see: https://github.com/cloud-annotations/docusaurus-openapi/issues/144 - return styles.getPropertyValue(property).trim(); + const color = styles.getPropertyValue(property).trim(); + const isColorRgb = color.includes("rgb"); + const isColorHexShortened = color.length === 4; + + // Convert "rgb(r, g, b)" to color hex code + const getColorHex = (color: string) => { + const rgbValues = color.substring(4).split(")")[0].split(","); + const [r, g, b] = rgbValues; + + const colorToHex = (rgb: string) => { + const hexadecimal = parseInt(rgb).toString(16); + return hexadecimal.length === 1 ? "0" + hexadecimal : hexadecimal; + }; + + return "#" + colorToHex(r) + colorToHex(g) + colorToHex(b); + }; + + // Extend shortened hex codes ie. "#aaa" => "#aaaaaa" or "#xyz" => "#xxyyzz" + const getFullColorHex = (color: string) => { + let fullColorHex = "#"; + const hexValues = color.slice(1); + + for (let i = 0; i < hexValues.length; i++) { + for (let j = 0; j < 2; j++) { + fullColorHex += hexValues[i]; + } + } + + return fullColorHex.toLowerCase(); + }; + + if (isColorRgb) { + return getColorHex(color); + } else if (isColorHexShortened) { + return getFullColorHex(color); + } else { + return color; + } } - const LIGHT_BRIGHT = "#1c1e21"; - const LIGHT_DIM = getColor("--openapi-code-dim-light"); - const LIGHT_BLUE = getColor("--openapi-code-blue-light"); - const LIGHT_GREEN = getColor("--openapi-code-green-light"); const LIGHT_BACKGROUND = getColor( "--openapi-monaco-background-color-light" ); - const LIGHT_SELECT = "#ebedef"; + const LIGHT_BRIGHT = getColor("--openapi-code-bright-light"); + const LIGHT_DIM = getColor("--openapi-code-dim-light"); + const LIGHT_BLUE = getColor("--openapi-code-blue-light"); + const LIGHT_GREEN = getColor("--openapi-code-green-light"); + const LIGHT_SELECT = getColor("--openapi-code-select-light"); - const DARK_BRIGHT = "#f5f6f7"; + const DARK_BACKGROUND = getColor("--openapi-monaco-background-color-dark"); + const DARK_BRIGHT = getColor("--openapi-code-bright-dark"); const DARK_DIM = getColor("--openapi-code-dim-dark"); const DARK_BLUE = getColor("--openapi-code-blue-dark"); const DARK_GREEN = getColor("--openapi-code-green-dark"); - const DARK_BACKGROUND = getColor("--openapi-monaco-background-color-dark"); - const DARK_SELECT = "#515151"; + const DARK_SELECT = getColor("--openapi-code-select-dark"); monaco.editor.defineTheme("OpenApiDark", { base: "vs-dark", diff --git a/packages/docusaurus-theme-openapi/src/theme/ApiDemoPanel/index.tsx b/packages/docusaurus-theme-openapi/src/theme/ApiDemoPanel/index.tsx index 0ea0a6d0c..ad34036e2 100644 --- a/packages/docusaurus-theme-openapi/src/theme/ApiDemoPanel/index.tsx +++ b/packages/docusaurus-theme-openapi/src/theme/ApiDemoPanel/index.tsx @@ -12,6 +12,7 @@ import useDocusaurusContext from "@docusaurus/useDocusaurusContext"; import { ParameterObject } from "@paloaltonetworks/docusaurus-plugin-openapi/src/openapi/types"; // @ts-ignore import sdk from "@paloaltonetworks/postman-collection"; +// @ts-ignore import { Metadata } from "@theme/ApiItem"; import { Provider } from "react-redux"; @@ -48,6 +49,7 @@ function ApiDemoPanel({ const acceptArray = Array.from( new Set( Object.values(item.responses ?? {}) + // @ts-ignore .map((response) => Object.keys(response.content ?? {})) .flat() ) @@ -66,7 +68,8 @@ function ApiDemoPanel({ cookie: [] as ParameterObject[], }; - item.parameters?.forEach((param) => { + item.parameters?.forEach((param: { in: string | number }) => { + // @ts-ignore params[param.in].push(param); }); diff --git a/packages/docusaurus-theme-openapi/src/theme/ApiDemoPanel/styles.module.css b/packages/docusaurus-theme-openapi/src/theme/ApiDemoPanel/styles.module.css index c263ba866..c66d7f6e1 100644 --- a/packages/docusaurus-theme-openapi/src/theme/ApiDemoPanel/styles.module.css +++ b/packages/docusaurus-theme-openapi/src/theme/ApiDemoPanel/styles.module.css @@ -1,3 +1,21 @@ +:root { + /* Dark mode */ + --openapi-code-bright-dark: var(--ifm-color-secondary-lighter); + --openapi-code-dim-dark: var(--ifm-color-secondary-dark); + --openapi-code-blue-dark: var(--ifm-color-info-lightest); + --openapi-code-green-dark: var(--ifm-color-success-lightest); + --openapi-code-select-dark: var(--ifm-color-secondary-lightest); + --openapi-monaco-background-color-dark: var(--ifm-code-background); + + /* Light mode */ + --openapi-code-bright-light: var(--ifm-color-secondary-contrast-foreground); + --openapi-code-dim-light: var(--ifm-color-secondary-contrast-foreground); + --openapi-code-blue-light: var(--ifm-color-info); + --openapi-code-green-light: var(--ifm-color-success); + --openapi-code-select-light: var(--ifm-color-secondary-light); + --openapi-monaco-background-color-light: var(--ifm-code-background); +} + .optionsPanel:empty { display: none; } diff --git a/packages/docusaurus-theme-openapi/src/theme/ApiItem/index.tsx b/packages/docusaurus-theme-openapi/src/theme/ApiItem/index.tsx index c22eb2e81..15aa6c93f 100644 --- a/packages/docusaurus-theme-openapi/src/theme/ApiItem/index.tsx +++ b/packages/docusaurus-theme-openapi/src/theme/ApiItem/index.tsx @@ -9,8 +9,14 @@ import React from "react"; import ExecutionEnvironment from "@docusaurus/ExecutionEnvironment"; import { ThemeClassNames, useWindowSize } from "@docusaurus/theme-common"; +// @ts-ignore import type { Props } from "@theme/ApiItem"; +import DocBreadcrumbs from "@theme/DocBreadcrumbs"; +import DocItemFooter from "@theme/DocItemFooter"; import DocPaginator from "@theme/DocPaginator"; +import DocVersionBadge from "@theme/DocVersionBadge"; +import DocVersionBanner from "@theme/DocVersionBanner"; +import Heading from "@theme/Heading"; import Seo from "@theme/Seo"; import TOC from "@theme/TOC"; import TOCCollapsible from "@theme/TOCCollapsible"; @@ -27,35 +33,37 @@ if (ExecutionEnvironment.canUseDOM) { ApiDemoPanel = require("@theme/ApiDemoPanel").default; } -function ApiItem(props: Props): JSX.Element { +function ApiItem(props: typeof Props): JSX.Element { const { content: ApiContent } = props; const { metadata, frontMatter } = ApiContent; const { image, keywords, + hide_title: hideTitle, hide_table_of_contents: hideTableOfContents, toc_min_heading_level: tocMinHeadingLevel, toc_max_heading_level: tocMaxHeadingLevel, + api, } = frontMatter; - const { description, title, - api, previous, next, showExecuteButton, showManualAuthentication, - type, } = metadata; + // We only add a title if: + // - user doesn't ask to hide it with front matter + // - the markdown content does not already contain a top-level h1 heading + const shouldAddTitle = + !hideTitle && typeof ApiContent.contentTitle === "undefined"; + const windowSize = useWindowSize(); const canRenderTOC = - !hideTableOfContents && - ApiContent.toc && - ApiContent.toc.length > 0 && - type === "doc"; + !hideTableOfContents && ApiContent.toc && ApiContent.toc.length > 0; const renderTocDesktop = canRenderTOC && (windowSize === "desktop" || windowSize === "ssr"); @@ -65,8 +73,12 @@ function ApiItem(props: Props): JSX.Element {
+
+ + +
{canRenderTOC && ( )} + {shouldAddTitle && ( +
+ {title} +
+ )}
+
diff --git a/packages/docusaurus-theme-openapi/src/theme/ApiPage/index.tsx b/packages/docusaurus-theme-openapi/src/theme/ApiPage/index.tsx deleted file mode 100644 index 5a408d377..000000000 --- a/packages/docusaurus-theme-openapi/src/theme/ApiPage/index.tsx +++ /dev/null @@ -1,201 +0,0 @@ -/* ============================================================================ - * Copyright (c) Cloud Annotations - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - * ========================================================================== */ - -import React, { ReactNode, useState, useCallback } from "react"; - -import type { PropSidebar } from "@docusaurus/plugin-content-docs-types"; -import renderRoutes from "@docusaurus/renderRoutes"; -import { matchPath } from "@docusaurus/router"; -import { translate } from "@docusaurus/Translate"; -import { MDXProvider } from "@mdx-js/react"; -import { PropApiMetadata } from "@paloaltonetworks/docusaurus-plugin-openapi"; -import type { ApiRoute } from "@theme/ApiItem"; -import type { Props } from "@theme/ApiPage"; -import BackToTopButton from "@theme/BackToTopButton"; -import DocSidebar from "@theme/DocSidebar"; -import IconArrow from "@theme/IconArrow"; -import Layout from "@theme/Layout"; -import MDXComponents from "@theme/MDXComponents"; -import NotFound from "@theme/NotFound"; -import clsx from "clsx"; - -import styles from "./styles.module.css"; - -type ApiPageContentProps = { - readonly currentApiRoute: ApiRoute; - readonly apiMetadata: PropApiMetadata; - readonly children: ReactNode; -}; - -type SidebarMetadata = Omit; - -function getSidebar({ currentApiRoute, apiMetadata }: SidebarMetadata) { - const sidebarName = currentApiRoute.sidebar; - const sidebar = sidebarName - ? apiMetadata.apiSidebars[sidebarName] - : undefined; - - return sidebar; -} - -function getLinks(sidebar: PropSidebar): string[] { - let links = []; - for (let item of sidebar) { - switch (item.type) { - case "link": - links.push(item.href); - break; - case "category": - links.push(...getLinks(item.items)); - break; - } - } - return links; -} - -function getSidebarPaths({ - currentApiRoute, - apiMetadata, -}: SidebarMetadata): string[] { - const sidebar = getSidebar({ currentApiRoute, apiMetadata }); - if (!sidebar) { - return []; - } - - return getLinks(sidebar); -} - -function ApiPageContent({ - currentApiRoute, - apiMetadata, - children, -}: ApiPageContentProps): JSX.Element { - const sidebar = getSidebar({ currentApiRoute, apiMetadata }); - - const [hiddenSidebarContainer, setHiddenSidebarContainer] = useState(false); - const [hiddenSidebar, setHiddenSidebar] = useState(false); - const toggleSidebar = useCallback(() => { - if (hiddenSidebar) { - setHiddenSidebar(false); - } - - setHiddenSidebarContainer((value) => !value); - }, [hiddenSidebar]); - - return ( - -
- - - {sidebar && ( - - )} -
-
- {children} -
-
-
-
- ); -} - -function ApiPage(props: Props): JSX.Element { - const { - route: { routes: apiRoutes }, - apiMetadata, - location, - } = props; - let currentApiRoute = apiRoutes.find((apiRoute) => - matchPath(location.pathname, apiRoute) - ); - if (!currentApiRoute) { - return ; - } - - // Override the current route path to the first page if it can't be found on the sidebar. - const paths = getSidebarPaths({ currentApiRoute, apiMetadata }); - if (!paths.find((path) => matchPath(location.pathname, path))) { - currentApiRoute = { - ...currentApiRoute, - path: paths[0], - }; - } - - return ( - <> - - {renderRoutes(apiRoutes)} - - - ); -} - -export default ApiPage; diff --git a/packages/docusaurus-theme-openapi/src/theme/ApiPage/styles.module.css b/packages/docusaurus-theme-openapi/src/theme/ApiPage/styles.module.css deleted file mode 100644 index 8e3652ad9..000000000 --- a/packages/docusaurus-theme-openapi/src/theme/ApiPage/styles.module.css +++ /dev/null @@ -1,162 +0,0 @@ -:root { - --openapi-code-dim-dark: #7f7f7f; - --openapi-code-red-dark: #fc929e; - --openapi-code-orange-dark: #f8b886; - --openapi-code-green-dark: #85d996; - --openapi-code-blue-dark: #a4cdfe; - --openapi-code-purple-dark: #d9a0f9; - --openapi-monaco-background-color-dark: #18191a; - - --openapi-code-dim-light: #aaaaaa; - --openapi-code-red-light: #f44336; - --openapi-code-orange-light: var( - --ifm-code-color - ); /* orange looks bad on white background */ - --openapi-code-green-light: #388e3c; - --openapi-code-blue-light: #1976d2; - --openapi-code-purple-light: #ab47bc; - --openapi-monaco-background-color-light: #ffffff; - - --openapi-code-dim: var(--openapi-code-dim-light); - --openapi-code-red: var(--openapi-code-red-light); - --openapi-code-orange: var(--openapi-code-orange-light); - --openapi-code-green: var(--openapi-code-green-light); - --openapi-code-blue: var(--openapi-code-blue-light); - --openapi-code-purple: var(--openapi-code-purple-light); - --openapi-monaco-background-color: var( - --openapi-monaco-background-color-light - ); - - --openapi-monaco-border-color: #dadde1; - - --api-sidebar-width: 300px; - --api-sidebar-hidden-width: 30px; - - --openapi-required: var(--openapi-code-red); - - --openapi-card-background-color: #f6f8fa; - --openapi-card-border-radius: var(--ifm-pre-border-radius); - /* --openapi-card-border-radius: var(--ifm-card-border-radius); */ - - --openapi-input-background: var(--ifm-code-background); - --openapi-input-border: var(--ifm-color-primary); - --openapi-button-dissabled: #c6c6c6; - - --openapi-dropzone-color: rgba(0, 0, 0, 0.5); - --openapi-dropzone-hover-shim: rgba(235, 237, 240, 0.8); - - --openapi-inverse-color: var(--ifm-color-gray-100); -} - -html[data-theme="dark"] { - --openapi-code-dim: var(--openapi-code-dim-dark); - --openapi-code-red: var(--openapi-code-red-dark); - --openapi-code-orange: var(--openapi-code-orange-dark); - --openapi-code-green: var(--openapi-code-green-dark); - --openapi-code-blue: var(--openapi-code-blue-dark); - --openapi-code-purple: var(--openapi-code-purple-dark); - --openapi-monaco-background-color: var( - --openapi-monaco-background-color-dark - ); - - --openapi-monaco-border-color: #606770; - - --openapi-required: var(--openapi-code-red); - - --openapi-card-background-color: var(--ifm-card-background-color); - - --openapi-input-background: #393939; - --openapi-input-border: white; - --openapi-button-dissabled: #525252; - - --openapi-dropzone-color: rgba(255, 255, 255, 0.3); - --openapi-dropzone-hover-shim: rgba(57, 57, 57, 0.8); - - --openapi-inverse-color: var(--ifm-color-gray-900); -} - -:global(.api-wrapper) { - display: flex; -} - -.apiPage, -.apiMainContainer { - display: flex; - width: 100%; -} - -.apiSidebarContainer { - display: none; -} - -@media (min-width: 997px) { - .apiMainContainer { - flex-grow: 1; - max-width: calc(100% - var(--api-sidebar-width)); - } - - .apiMainContainerEnhanced { - max-width: calc(100% - var(--api-sidebar-hidden-width)); - } - - .apiSidebarContainer { - display: block; - width: var(--api-sidebar-width); - margin-top: calc(-1 * var(--ifm-navbar-height)); - border-right: 1px solid var(--ifm-toc-border-color); - will-change: width; - transition: width var(--ifm-transition-fast) ease; - clip-path: inset(0); - } - - .apiSidebarContainerHidden { - width: var(--api-sidebar-hidden-width); - cursor: pointer; - } - - .collapsedApiSidebar { - position: sticky; - top: 0; - height: 100%; - max-height: 100vh; - display: flex; - align-items: center; - justify-content: center; - transition: background-color var(--ifm-transition-fast) ease; - } - - .collapsedApiSidebar:hover, - .collapsedApiSidebar:focus { - background-color: var(--ifm-color-emphasis-200); - } - - .expandSidebarButtonIcon { - transform: rotate(0); - } - html[dir="rtl"] .expandSidebarButtonIcon { - transform: rotate(180deg); - } - - html[data-theme="dark"] .collapsedApiSidebar:hover, - html[data-theme="dark"] .collapsedApiSidebar:focus { - background-color: var(--collapse-button-bg-color-dark); - } - - .apiItemWrapperEnhanced { - max-width: calc( - var(--ifm-container-width) + var(--api-sidebar-width) - ) !important; - } -} - -:global(.menu__list-item--deprecated > *:not(.menu__link--active)) { - opacity: 0.3; - text-decoration: line-through; -} - -/* Make disabled buttons way less bright */ -:global(.button.disabled), -:global(.button:disabled), -:global(.button[disabled]) { - opacity: 0.35; -} diff --git a/yarn.lock b/yarn.lock index e3d2a31de..3bd8ee11a 100644 --- a/yarn.lock +++ b/yarn.lock @@ -195,7 +195,7 @@ semver "^5.4.1" source-map "^0.5.0" -"@babel/core@^7.1.0", "@babel/core@^7.12.3", "@babel/core@^7.15.5", "@babel/core@^7.16.0", "@babel/core@^7.17.5", "@babel/core@^7.7.2", "@babel/core@^7.8.0": +"@babel/core@^7.1.0", "@babel/core@^7.12.3", "@babel/core@^7.15.5", "@babel/core@^7.16.0", "@babel/core@^7.7.2", "@babel/core@^7.8.0": version "7.17.8" resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.17.8.tgz#3dac27c190ebc3a4381110d46c80e77efe172e1a" integrity sha512-OdQDV/7cRBtJHLSOBqqbYNkOcydOgnX59TZx4puf41fzcVtN3e/4yqY8lMQsK+5X2lJtAdmA+6OHqsj1hBJ4IQ== @@ -216,6 +216,27 @@ json5 "^2.1.2" semver "^6.3.0" +"@babel/core@^7.17.5": + version "7.17.9" + resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.17.9.tgz#6bae81a06d95f4d0dec5bb9d74bbc1f58babdcfe" + integrity sha512-5ug+SfZCpDAkVp9SFIZAzlW18rlzsOcJGaetCjkySnrXXDUw9AR8cDUm1iByTmdWM6yxX6/zycaV76w3YTF2gw== + dependencies: + "@ampproject/remapping" "^2.1.0" + "@babel/code-frame" "^7.16.7" + "@babel/generator" "^7.17.9" + "@babel/helper-compilation-targets" "^7.17.7" + "@babel/helper-module-transforms" "^7.17.7" + "@babel/helpers" "^7.17.9" + "@babel/parser" "^7.17.9" + "@babel/template" "^7.16.7" + "@babel/traverse" "^7.17.9" + "@babel/types" "^7.17.0" + convert-source-map "^1.7.0" + debug "^4.1.0" + gensync "^1.0.0-beta.2" + json5 "^2.2.1" + semver "^6.3.0" + "@babel/eslint-parser@^7.16.3": version "7.17.0" resolved "https://registry.yarnpkg.com/@babel/eslint-parser/-/eslint-parser-7.17.0.tgz#eabb24ad9f0afa80e5849f8240d0e5facc2d90d6" @@ -234,6 +255,15 @@ jsesc "^2.5.1" source-map "^0.5.0" +"@babel/generator@^7.17.9": + version "7.17.9" + resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.17.9.tgz#f4af9fd38fa8de143c29fce3f71852406fc1e2fc" + integrity sha512-rAdDousTwxbIxbz5I7GEQ3lUip+xVCXooZNbsydCWs3xA7ZsYOv+CFRdzGxRX78BmQHu9B1Eso59AOZQOJDEdQ== + dependencies: + "@babel/types" "^7.17.0" + jsesc "^2.5.1" + source-map "^0.5.0" + "@babel/helper-annotate-as-pure@^7.16.7": version "7.16.7" resolved "https://registry.yarnpkg.com/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.16.7.tgz#bb2339a7534a9c128e3102024c60760a3a7f3862" @@ -317,6 +347,14 @@ "@babel/template" "^7.16.7" "@babel/types" "^7.16.7" +"@babel/helper-function-name@^7.17.9": + version "7.17.9" + resolved "https://registry.yarnpkg.com/@babel/helper-function-name/-/helper-function-name-7.17.9.tgz#136fcd54bc1da82fcb47565cf16fd8e444b1ff12" + integrity sha512-7cRisGlVtiVqZ0MW0/yFB4atgpGLWEHUVYnb448hZK4x+vih0YO5UoS11XIYtZYqHd0dIPMdUSv8q5K4LdMnIg== + dependencies: + "@babel/template" "^7.16.7" + "@babel/types" "^7.17.0" + "@babel/helper-get-function-arity@^7.16.7": version "7.16.7" resolved "https://registry.yarnpkg.com/@babel/helper-get-function-arity/-/helper-get-function-arity-7.16.7.tgz#ea08ac753117a669f1508ba06ebcc49156387419" @@ -446,6 +484,15 @@ "@babel/traverse" "^7.17.3" "@babel/types" "^7.17.0" +"@babel/helpers@^7.17.9": + version "7.17.9" + resolved "https://registry.yarnpkg.com/@babel/helpers/-/helpers-7.17.9.tgz#b2af120821bfbe44f9907b1826e168e819375a1a" + integrity sha512-cPCt915ShDWUEzEp3+UNRktO2n6v49l5RSnG9M5pS24hA+2FAc5si+Pn1i4VVbQQ+jh+bIZhPFQOJOzbrOYY1Q== + dependencies: + "@babel/template" "^7.16.7" + "@babel/traverse" "^7.17.9" + "@babel/types" "^7.17.0" + "@babel/highlight@^7.10.4", "@babel/highlight@^7.16.7": version "7.16.10" resolved "https://registry.yarnpkg.com/@babel/highlight/-/highlight-7.16.10.tgz#744f2eb81579d6eea753c227b0f570ad785aba88" @@ -460,6 +507,11 @@ resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.17.8.tgz#2817fb9d885dd8132ea0f8eb615a6388cca1c240" integrity sha512-BoHhDJrJXqcg+ZL16Xv39H9n+AqJ4pcDrQBGZN+wHxIysrLZ3/ECwCBUch/1zUNhnsXULcONU3Ei5Hmkfk6kiQ== +"@babel/parser@^7.17.9": + version "7.17.9" + resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.17.9.tgz#9c94189a6062f0291418ca021077983058e171ef" + integrity sha512-vqUSBLP8dQHFPdPi9bc5GK9vRkYHJ49fsZdtoJ8EQ8ibpwk5rPKfvNIwChB0KVXcIjcepEBBd2VHC5r9Gy8ueg== + "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression@^7.16.7": version "7.16.7" resolved "https://registry.yarnpkg.com/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression/-/plugin-bugfix-safari-id-destructuring-collision-in-function-expression-7.16.7.tgz#4eda6d6c2a0aa79c70fa7b6da67763dfe2141050" @@ -1169,7 +1221,7 @@ "@babel/helper-validator-option" "^7.16.7" "@babel/plugin-transform-typescript" "^7.16.7" -"@babel/runtime-corejs3@^7.10.2", "@babel/runtime-corejs3@^7.17.2": +"@babel/runtime-corejs3@^7.10.2": version "7.17.8" resolved "https://registry.yarnpkg.com/@babel/runtime-corejs3/-/runtime-corejs3-7.17.8.tgz#d7dd49fb812f29c61c59126da3792d8740d4e284" integrity sha512-ZbYSUvoSF6dXZmMl/CYTMOvzIFnbGfv4W3SEHYgMvNsFTeLaF2gkGAF4K2ddmtSK4Emej+0aYcnSC6N5dPCXUQ== @@ -1177,13 +1229,28 @@ core-js-pure "^3.20.2" regenerator-runtime "^0.13.4" -"@babel/runtime@^7.1.2", "@babel/runtime@^7.10.2", "@babel/runtime@^7.10.3", "@babel/runtime@^7.12.1", "@babel/runtime@^7.12.13", "@babel/runtime@^7.12.5", "@babel/runtime@^7.14.6", "@babel/runtime@^7.15.4", "@babel/runtime@^7.16.3", "@babel/runtime@^7.17.2", "@babel/runtime@^7.8.4", "@babel/runtime@^7.9.2": +"@babel/runtime-corejs3@^7.17.2": + version "7.17.9" + resolved "https://registry.yarnpkg.com/@babel/runtime-corejs3/-/runtime-corejs3-7.17.9.tgz#3d02d0161f0fbf3ada8e88159375af97690f4055" + integrity sha512-WxYHHUWF2uZ7Hp1K+D1xQgbgkGUfA+5UPOegEXGt2Y5SMog/rYCVaifLZDbw8UkNXozEqqrZTy6bglL7xTaCOw== + dependencies: + core-js-pure "^3.20.2" + regenerator-runtime "^0.13.4" + +"@babel/runtime@^7.1.2", "@babel/runtime@^7.10.2", "@babel/runtime@^7.10.3", "@babel/runtime@^7.12.1", "@babel/runtime@^7.12.13", "@babel/runtime@^7.12.5", "@babel/runtime@^7.14.6", "@babel/runtime@^7.15.4", "@babel/runtime@^7.16.3", "@babel/runtime@^7.8.4", "@babel/runtime@^7.9.2": version "7.17.8" resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.17.8.tgz#3e56e4aff81befa55ac3ac6a0967349fd1c5bca2" integrity sha512-dQpEpK0O9o6lj6oPu0gRDbbnk+4LeHlNcBpspf6Olzt3GIX4P1lWF1gS+pHLDFlaJvbR6q7jCfQ08zA4QJBnmA== dependencies: regenerator-runtime "^0.13.4" +"@babel/runtime@^7.17.2": + version "7.17.9" + resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.17.9.tgz#d19fbf802d01a8cb6cf053a64e472d42c434ba72" + integrity sha512-lSiBBvodq29uShpWGNbgFdKYNiFDo5/HIYsaCEY9ff4sb10x9jizo2+pRrSyF4jKZCXqgzuqBOQKbUm90gQwJg== + dependencies: + regenerator-runtime "^0.13.4" + "@babel/template@^7.12.7", "@babel/template@^7.16.7", "@babel/template@^7.3.3": version "7.16.7" resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.16.7.tgz#8d126c8701fde4d66b264b3eba3d96f07666d155" @@ -1209,6 +1276,22 @@ debug "^4.1.0" globals "^11.1.0" +"@babel/traverse@^7.17.9": + version "7.17.9" + resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.17.9.tgz#1f9b207435d9ae4a8ed6998b2b82300d83c37a0d" + integrity sha512-PQO8sDIJ8SIwipTPiR71kJQCKQYB5NGImbOviK8K+kg5xkNSYXLBupuX9QhatFowrsvo9Hj8WgArg3W7ijNAQw== + dependencies: + "@babel/code-frame" "^7.16.7" + "@babel/generator" "^7.17.9" + "@babel/helper-environment-visitor" "^7.16.7" + "@babel/helper-function-name" "^7.17.9" + "@babel/helper-hoist-variables" "^7.16.7" + "@babel/helper-split-export-declaration" "^7.16.7" + "@babel/parser" "^7.17.9" + "@babel/types" "^7.17.0" + debug "^4.1.0" + globals "^11.1.0" + "@babel/types@^7.0.0", "@babel/types@^7.12.6", "@babel/types@^7.12.7", "@babel/types@^7.15.6", "@babel/types@^7.16.0", "@babel/types@^7.16.7", "@babel/types@^7.16.8", "@babel/types@^7.17.0", "@babel/types@^7.3.0", "@babel/types@^7.3.3", "@babel/types@^7.4.4", "@babel/types@^7.7.0": version "7.17.0" resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.17.0.tgz#a826e368bccb6b3d84acd76acad5c0d87342390b" @@ -3486,6 +3569,11 @@ resolved "https://registry.yarnpkg.com/@types/ms/-/ms-0.7.31.tgz#31b7ca6407128a3d2bbc27fe2d21b345397f6197" integrity sha512-iiUgKzV9AuaEkZqkOLDIvlQiL6ltuZd9tGcW3gwpnX8JbuiuhFlEGmmFXEXkN50Cvq7Os88IY2v0dkDqXYWVgA== +"@types/mustache@^4.1.2": + version "4.1.2" + resolved "https://registry.yarnpkg.com/@types/mustache/-/mustache-4.1.2.tgz#d0e158013c81674a5b6d8780bc3fe234e1804eaf" + integrity sha512-c4OVMMcyodKQ9dpwBwh3ofK9P6U9ZktKU9S+p33UqwMNN1vlv2P0zJZUScTshnx7OEoIIRcCFNQ904sYxZz8kg== + "@types/node@*", "@types/node@^17.0.2", "@types/node@^17.0.5": version "17.0.23" resolved "https://registry.yarnpkg.com/@types/node/-/node-17.0.23.tgz#3b41a6e643589ac6442bdbd7a4a3ded62f33f7da" @@ -4116,9 +4204,9 @@ ajv@^8.0.0, ajv@^8.0.1, ajv@^8.8.0: uri-js "^4.2.2" algoliasearch-helper@^3.7.0: - version "3.7.4" - resolved "https://registry.yarnpkg.com/algoliasearch-helper/-/algoliasearch-helper-3.7.4.tgz#3812ea161da52463ec88da52612c9a363c1b181d" - integrity sha512-KmJrsHVm5TmxZ9Oj53XdXuM4CQeu7eVFnB15tpSFt+7is1d1yVCv3hxCLMqYSw/rH42ccv013miQpRr268P8vw== + version "3.8.2" + resolved "https://registry.yarnpkg.com/algoliasearch-helper/-/algoliasearch-helper-3.8.2.tgz#35726dc6d211f49dbab0bf6d37b4658165539523" + integrity sha512-AXxiF0zT9oYwl8ZBgU/eRXvfYhz7cBA5YrLPlw9inZHdaYF0QEya/f1Zp1mPYMXc1v6VkHwBq4pk6/vayBLICg== dependencies: "@algolia/events" "^4.0.1" @@ -5661,6 +5749,11 @@ css-declaration-sorter@^6.0.3: dependencies: timsort "^0.3.0" +css-declaration-sorter@^6.2.2: + version "6.2.2" + resolved "https://registry.yarnpkg.com/css-declaration-sorter/-/css-declaration-sorter-6.2.2.tgz#bfd2f6f50002d6a3ae779a87d3a0c5d5b10e0f02" + integrity sha512-Ufadglr88ZLsrvS11gjeu/40Lw74D9Am/Jpr3LlYm5Q4ZP5KdlUhG+6u2EjyXeZcxmZ2h1ebCKngDjolpeLHpg== + css-loader@^6.6.0: version "6.7.1" resolved "https://registry.yarnpkg.com/css-loader/-/css-loader-6.7.1.tgz#e98106f154f6e1baf3fc3bc455cb9981c1d5fd2e" @@ -5760,12 +5853,12 @@ cssesc@^3.0.0: integrity sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg== cssnano-preset-advanced@^5.1.12: - version "5.3.1" - resolved "https://registry.yarnpkg.com/cssnano-preset-advanced/-/cssnano-preset-advanced-5.3.1.tgz#f4fa7006aab67e354289b3efd512c93a272b3874" - integrity sha512-kfCknalY5VX/JKJ3Iri5/5rhZmQIqkbqgXsA6oaTnfA4flY/tt+w0hMxbExr0/fVuJL8w56j211op+pkQoNzoQ== + version "5.3.3" + resolved "https://registry.yarnpkg.com/cssnano-preset-advanced/-/cssnano-preset-advanced-5.3.3.tgz#848422118d7a62b5b29a53edc160f58c7f7f7539" + integrity sha512-AB9SmTSC2Gd8T7PpKUsXFJ3eNsg7dc4CTZ0+XAJ29MNxyJsrCEk7N1lw31bpHrsQH2PVJr21bbWgGAfA9j0dIA== dependencies: autoprefixer "^10.3.7" - cssnano-preset-default "^5.2.5" + cssnano-preset-default "^5.2.7" postcss-discard-unused "^5.1.0" postcss-merge-idents "^5.1.1" postcss-reduce-idents "^5.2.0" @@ -5806,12 +5899,56 @@ cssnano-preset-default@^5.2.5: postcss-svgo "^5.1.0" postcss-unique-selectors "^5.1.1" +cssnano-preset-default@^5.2.7: + version "5.2.7" + resolved "https://registry.yarnpkg.com/cssnano-preset-default/-/cssnano-preset-default-5.2.7.tgz#791e3603fb8f1b46717ac53b47e3c418e950f5f3" + integrity sha512-JiKP38ymZQK+zVKevphPzNSGHSlTI+AOwlasoSRtSVMUU285O7/6uZyd5NbW92ZHp41m0sSHe6JoZosakj63uA== + dependencies: + css-declaration-sorter "^6.2.2" + cssnano-utils "^3.1.0" + postcss-calc "^8.2.3" + postcss-colormin "^5.3.0" + postcss-convert-values "^5.1.0" + postcss-discard-comments "^5.1.1" + postcss-discard-duplicates "^5.1.0" + postcss-discard-empty "^5.1.1" + postcss-discard-overridden "^5.1.0" + postcss-merge-longhand "^5.1.4" + postcss-merge-rules "^5.1.1" + postcss-minify-font-values "^5.1.0" + postcss-minify-gradients "^5.1.1" + postcss-minify-params "^5.1.2" + postcss-minify-selectors "^5.2.0" + postcss-normalize-charset "^5.1.0" + postcss-normalize-display-values "^5.1.0" + postcss-normalize-positions "^5.1.0" + postcss-normalize-repeat-style "^5.1.0" + postcss-normalize-string "^5.1.0" + postcss-normalize-timing-functions "^5.1.0" + postcss-normalize-unicode "^5.1.0" + postcss-normalize-url "^5.1.0" + postcss-normalize-whitespace "^5.1.1" + postcss-ordered-values "^5.1.1" + postcss-reduce-initial "^5.1.0" + postcss-reduce-transforms "^5.1.0" + postcss-svgo "^5.1.0" + postcss-unique-selectors "^5.1.1" + cssnano-utils@^3.1.0: version "3.1.0" resolved "https://registry.yarnpkg.com/cssnano-utils/-/cssnano-utils-3.1.0.tgz#95684d08c91511edfc70d2636338ca37ef3a6861" integrity sha512-JQNR19/YZhz4psLX/rQ9M83e3z2Wf/HdJbryzte4a3NSuafyp9w/I4U+hx5C2S9g41qlstH7DEWnZaaj83OuEA== -cssnano@^5.0.17, cssnano@^5.0.6: +cssnano@^5.0.17: + version "5.1.7" + resolved "https://registry.yarnpkg.com/cssnano/-/cssnano-5.1.7.tgz#99858bef6c76c9240f0cdc9239570bc7db8368be" + integrity sha512-pVsUV6LcTXif7lvKKW9ZrmX+rGRzxkEdJuVJcp5ftUjWITgwam5LMZOgaTvUrWPkcORBey6he7JKb4XAJvrpKg== + dependencies: + cssnano-preset-default "^5.2.7" + lilconfig "^2.0.3" + yaml "^1.10.2" + +cssnano@^5.0.6: version "5.1.5" resolved "https://registry.yarnpkg.com/cssnano/-/cssnano-5.1.5.tgz#5f3f519538c7f1c182c527096892243db3e17397" integrity sha512-VZO1e+bRRVixMeia1zKagrv0lLN1B/r/u12STGNNUFxnp97LIFgZHQa0JxqlwEkvzUyA9Oz/WnCTAFkdEbONmg== @@ -9194,7 +9331,7 @@ json-stringify-safe@^5.0.1, json-stringify-safe@~5.0.1: resolved "https://registry.yarnpkg.com/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz#1296a2d58fd45f19a0f6ce01d65701e2c735b6eb" integrity sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus= -json5@2.x, json5@^2.1.2: +json5@2.x, json5@^2.1.2, json5@^2.2.1: version "2.2.1" resolved "https://registry.yarnpkg.com/json5/-/json5-2.2.1.tgz#655d50ed1e6f95ad1a3caababd2b0efda10b395c" integrity sha512-1hqLFMSrGHRHxav9q9gNjJ5EXznIxGVO09xQRrwplcS8qs28pZ8s8hupZAmqDwZUmVZ2Qb2jnyPOWcDH8m8dlA== @@ -10368,6 +10505,11 @@ multimatch@^5.0.0: arrify "^2.0.1" minimatch "^3.0.4" +mustache@^4.2.0: + version "4.2.0" + resolved "https://registry.yarnpkg.com/mustache/-/mustache-4.2.0.tgz#e5892324d60a12ec9c2a73359edca52972bf6f64" + integrity sha512-71ippSywq5Yb7/tVYyGbkBggbU8H3u5Rz56fH60jGFgr8uHwxs+aSKeqmluIVzM0m0kB7xQjKS6qPfd0b2ZoqQ== + mute-stream@0.0.8, mute-stream@~0.0.4: version "0.0.8" resolved "https://registry.yarnpkg.com/mute-stream/-/mute-stream-0.0.8.tgz#1630c42b2251ff81e2a283de96a5497ea92e5e0d" @@ -11375,6 +11517,14 @@ postcss-merge-longhand@^5.1.3: postcss-value-parser "^4.2.0" stylehacks "^5.1.0" +postcss-merge-longhand@^5.1.4: + version "5.1.4" + resolved "https://registry.yarnpkg.com/postcss-merge-longhand/-/postcss-merge-longhand-5.1.4.tgz#0f46f8753989a33260efc47de9a0cdc571f2ec5c" + integrity sha512-hbqRRqYfmXoGpzYKeW0/NCZhvNyQIlQeWVSao5iKWdyx7skLvCfQFGIUsP9NUs3dSbPac2IC4Go85/zG+7MlmA== + dependencies: + postcss-value-parser "^4.2.0" + stylehacks "^5.1.0" + postcss-merge-rules@^5.1.1: version "5.1.1" resolved "https://registry.yarnpkg.com/postcss-merge-rules/-/postcss-merge-rules-5.1.1.tgz#d327b221cd07540bcc8d9ff84446d8b404d00162" @@ -14714,9 +14864,9 @@ webpack-sources@^3.2.3: integrity sha512-/DyMEOrDgLKKIG0fmvtz+4dUX/3Ghozwgm6iPp8KRhvn+eQf9+Q7GWxVNMk3+uCPWfdXYC4ExGBckIXdFEfH1w== webpack@^5.61.0, webpack@^5.69.1: - version "5.70.0" - resolved "https://registry.yarnpkg.com/webpack/-/webpack-5.70.0.tgz#3461e6287a72b5e6e2f4872700bc8de0d7500e6d" - integrity sha512-ZMWWy8CeuTTjCxbeaQI21xSswseF2oNOwc70QSKNePvmxE7XW36i7vpBMYZFAUHPwQiEbNGCEYIOOlyRbdGmxw== + version "5.72.0" + resolved "https://registry.yarnpkg.com/webpack/-/webpack-5.72.0.tgz#f8bc40d9c6bb489a4b7a8a685101d6022b8b6e28" + integrity sha512-qmSmbspI0Qo5ld49htys8GY9XhS9CGqFoHTsOVAnjBdg0Zn79y135R+k4IR4rKK6+eKaabMhJwiVB7xw0SJu5w== dependencies: "@types/eslint-scope" "^3.7.3" "@types/estree" "^0.0.51"