diff --git a/README.md b/README.md index 22ac9347f..ef6c4fe7b 100644 --- a/README.md +++ b/README.md @@ -307,6 +307,16 @@ yarn docusaurus gen-api-docs all --all-versions > This will generate API docs for all versions of all the OpenAPI specification (OAS) files referenced in your `docusaurus-plugin-openapi-docs` config. +To generate only schema MDX files—without updating the sidebar or requiring `showSchemas` in your plugin config—use the `--schemas-only` flag: + +```bash +yarn docusaurus gen-api-docs petstore --schemas-only +``` + +> This command writes the schema pages to the configured output directory while leaving other generated docs untouched. + +The `--schemas-only` flag is also available for `gen-api-docs:version`. + ### Cleaning API Docs To clean/remove all API Docs, run the following command from the root directory of your project: @@ -339,6 +349,14 @@ yarn docusaurus clean-api-docs all --all-versions > This will clean API docs for all versions of all the OpenAPI specification (OAS) files referenced in your `docusaurus-plugin-openapi-docs` config. +To clean only schema docs while leaving API, info, and tag docs untouched, use the `--schemas-only` flag: + +```bash +yarn docusaurus clean-api-docs petstore --schemas-only +``` + +> The `--schemas-only` flag is also available for `clean-api-docs:version`. + ### Versioning OpenAPI docs To generate _all_ versioned OpenAPI docs, run the following command from the root directory of your project: diff --git a/demo/docs/intro.mdx b/demo/docs/intro.mdx index 71585306e..e7fd759af 100644 --- a/demo/docs/intro.mdx +++ b/demo/docs/intro.mdx @@ -363,6 +363,16 @@ Example: yarn docusaurus gen-api-docs petstore ``` +To generate only schema MDX files—without updating the sidebar or requiring `showSchemas` in your plugin config—use the `--schemas-only` flag: + +```bash title="generating only schema docs for 'petstore'" +yarn docusaurus gen-api-docs petstore --schemas-only +``` + +> This command writes the schema pages to the configured output directory while leaving other generated docs untouched. + +The `--schemas-only` flag is also available for `gen-api-docs:version`. + ### Cleaning API Docs To clean/remove all API Docs, run the following command from the root directory of your project: @@ -383,6 +393,14 @@ Example: yarn docusaurus clean-api-docs petstore ``` +To clean only schema docs while leaving API, info, and tag docs untouched, use the `--schemas-only` flag: + +```bash title="cleaning only schema docs for 'petstore'" +yarn docusaurus clean-api-docs petstore --schemas-only +``` + +> The `--schemas-only` flag is also available for `clean-api-docs:version`. + ### Versioning OpenAPI docs To generate _all_ versioned OpenAPI docs, run the following command from the root directory of your project: diff --git a/packages/docusaurus-plugin-openapi-docs/README.md b/packages/docusaurus-plugin-openapi-docs/README.md index cde77d0f2..4bbbdbd7a 100644 --- a/packages/docusaurus-plugin-openapi-docs/README.md +++ b/packages/docusaurus-plugin-openapi-docs/README.md @@ -328,6 +328,16 @@ yarn docusaurus gen-api-docs all --all-versions > This will generate API docs for all versions of all the OpenAPI specification (OAS) files referenced in your `docusaurus-plugin-openapi-docs` config. +To generate only schema MDX files—without updating the sidebar or requiring `showSchemas` in your plugin config—use the `--schemas-only` flag: + +```bash +yarn docusaurus gen-api-docs petstore --schemas-only +``` + +> This command writes the schema pages to the configured output directory while leaving other generated docs untouched. + +The `--schemas-only` flag is also available for `gen-api-docs:version`. + ### Cleaning API Docs To clean/remove all API Docs, run the following command from the root directory of your project: @@ -360,6 +370,14 @@ yarn docusaurus clean-api-docs all --all-versions > This will clean API docs for all versions of all the OpenAPI specification (OAS) files referenced in your `docusaurus-plugin-openapi-docs` config. +To clean only schema docs while leaving API, info, and tag docs untouched, use the `--schemas-only` flag: + +```bash +yarn docusaurus clean-api-docs petstore --schemas-only +``` + +> The `--schemas-only` flag is also available for `clean-api-docs:version`. + ### Versioning OpenAPI docs To generate _all_ versioned OpenAPI docs, run the following command from the root directory of your project: diff --git a/packages/docusaurus-plugin-openapi-docs/src/index.ts b/packages/docusaurus-plugin-openapi-docs/src/index.ts index 570fcf2b5..8c4311561 100644 --- a/packages/docusaurus-plugin-openapi-docs/src/index.ts +++ b/packages/docusaurus-plugin-openapi-docs/src/index.ts @@ -123,9 +123,12 @@ export default function pluginOpenAPIDocs( markdownGenerators, downloadUrl, sidebarOptions, + schemasOnly, disableCompression, } = options; + const isSchemasOnly = schemasOnly === true; + // Remove trailing slash before proceeding outputDir = outputDir.replace(/\/$/, ""); @@ -160,7 +163,7 @@ export default function pluginOpenAPIDocs( } // TODO: figure out better way to set default - if (Object.keys(sidebarOptions ?? {}).length > 0) { + if (!isSchemasOnly && Object.keys(sidebarOptions ?? {}).length > 0) { const sidebarSlice = generateSidebarSlice( sidebarOptions!, options, @@ -332,6 +335,9 @@ custom_edit_url: null } const markdown = pageGeneratorByType[item.type](item as any); item.markdown = markdown; + if (isSchemasOnly && item.type !== "schema") { + return; + } if (item.type === "api") { // opportunity to compress JSON // const serialize = (o: any) => { @@ -485,29 +491,53 @@ custom_edit_url: null } } - async function cleanApiDocs(options: APIOptions) { + async function cleanApiDocs(options: APIOptions, schemasOnly = false) { const { outputDir } = options; const apiDir = posixPath(path.join(siteDir, outputDir)); - const apiMdxFiles = await Globby(["*.api.mdx", "*.info.mdx", "*.tag.mdx"], { - cwd: path.resolve(apiDir), - deep: 1, - }); - const sidebarFile = await Globby(["sidebar.js", "sidebar.ts"], { - cwd: path.resolve(apiDir), - deep: 1, - }); - apiMdxFiles.map((mdx) => - fs.unlink(`${apiDir}/${mdx}`, (err) => { - if (err) { - console.error( - chalk.red(`Cleanup failed for "${apiDir}/${mdx}"`), - chalk.yellow(err) - ); - } else { - console.log(chalk.green(`Cleanup succeeded for "${apiDir}/${mdx}"`)); + + // When schemasOnly is true, only clean the schemas directory + if (!schemasOnly) { + const apiMdxFiles = await Globby( + ["*.api.mdx", "*.info.mdx", "*.tag.mdx"], + { + cwd: path.resolve(apiDir), + deep: 1, } - }) - ); + ); + const sidebarFile = await Globby(["sidebar.js", "sidebar.ts"], { + cwd: path.resolve(apiDir), + deep: 1, + }); + apiMdxFiles.map((mdx) => + fs.unlink(`${apiDir}/${mdx}`, (err) => { + if (err) { + console.error( + chalk.red(`Cleanup failed for "${apiDir}/${mdx}"`), + chalk.yellow(err) + ); + } else { + console.log( + chalk.green(`Cleanup succeeded for "${apiDir}/${mdx}"`) + ); + } + }) + ); + + sidebarFile.map((sidebar) => + fs.unlink(`${apiDir}/${sidebar}`, (err) => { + if (err) { + console.error( + chalk.red(`Cleanup failed for "${apiDir}/${sidebar}"`), + chalk.yellow(err) + ); + } else { + console.log( + chalk.green(`Cleanup succeeded for "${apiDir}/${sidebar}"`) + ); + } + }) + ); + } try { fs.rmSync(`${apiDir}/schemas`, { recursive: true }); @@ -520,21 +550,6 @@ custom_edit_url: null ); } } - - sidebarFile.map((sidebar) => - fs.unlink(`${apiDir}/${sidebar}`, (err) => { - if (err) { - console.error( - chalk.red(`Cleanup failed for "${apiDir}/${sidebar}"`), - chalk.yellow(err) - ); - } else { - console.log( - chalk.green(`Cleanup succeeded for "${apiDir}/${sidebar}"`) - ); - } - }) - ); } async function generateVersions(versions: object, outputDir: string) { @@ -635,7 +650,7 @@ custom_edit_url: null } } - async function cleanAllVersions(options: APIOptions) { + async function cleanAllVersions(options: APIOptions, schemasOnly = false) { const parentOptions = Object.assign({}, options); const { versions } = parentOptions as any; @@ -643,14 +658,16 @@ custom_edit_url: null delete parentOptions.versions; if (versions != null && Object.keys(versions).length > 0) { - await cleanVersions(parentOptions.outputDir); + if (!schemasOnly) { + await cleanVersions(parentOptions.outputDir); + } Object.keys(versions).forEach(async (key) => { const versionOptions = versions[key]; const mergedOptions = { ...parentOptions, ...versionOptions, }; - await cleanApiDocs(mergedOptions); + await cleanApiDocs(mergedOptions, schemasOnly); }); } } @@ -668,10 +685,12 @@ custom_edit_url: null .arguments("") .option("-p, --plugin-id ", "OpenAPI docs plugin ID.") .option("--all-versions", "Generate all versions.") + .option("--schemas-only", "Generate only schema docs.") .action(async (id, instance) => { const options = instance.opts(); const pluginId = options.pluginId; const allVersions = options.allVersions; + const schemasOnly = options.schemasOnly; const pluginInstances = getPluginInstances(plugins); let targetConfig: any; let targetDocsPluginId: any; @@ -698,6 +717,9 @@ custom_edit_url: null targetConfig = config; } + const withSchemaOverride = (apiOptions: APIOptions): APIOptions => + schemasOnly ? { ...apiOptions, schemasOnly: true } : apiOptions; + if (id === "all") { if (targetConfig[id]) { console.error( @@ -707,12 +729,10 @@ custom_edit_url: null ); } else { Object.keys(targetConfig).forEach(async function (key) { - await generateApiDocs(targetConfig[key], targetDocsPluginId); + const apiOptions = withSchemaOverride(targetConfig[key]); + await generateApiDocs(apiOptions, targetDocsPluginId); if (allVersions) { - await generateAllVersions( - targetConfig[key], - targetDocsPluginId - ); + await generateAllVersions(apiOptions, targetDocsPluginId); } }); } @@ -721,9 +741,10 @@ custom_edit_url: null chalk.red(`ID '${id}' does not exist in OpenAPI docs config.`) ); } else { - await generateApiDocs(targetConfig[id], targetDocsPluginId); + const apiOptions = withSchemaOverride(targetConfig[id]); + await generateApiDocs(apiOptions, targetDocsPluginId); if (allVersions) { - await generateAllVersions(targetConfig[id], targetDocsPluginId); + await generateAllVersions(apiOptions, targetDocsPluginId); } } }); @@ -736,9 +757,11 @@ custom_edit_url: null .usage("") .arguments("") .option("-p, --plugin-id ", "OpenAPI docs plugin ID.") + .option("--schemas-only", "Generate only schema docs.") .action(async (id, instance) => { const options = instance.opts(); const pluginId = options.pluginId; + const schemasOnly = options.schemasOnly; const pluginInstances = getPluginInstances(plugins); let targetConfig: any; let targetDocsPluginId: any; @@ -767,6 +790,9 @@ custom_edit_url: null const [parentId, versionId] = id.split(":"); const parentConfig = Object.assign({}, targetConfig[parentId]); + const withSchemaOverride = (apiOptions: APIOptions): APIOptions => + schemasOnly ? { ...apiOptions, schemasOnly: true } : apiOptions; + const version = parentConfig.version as string; const label = parentConfig.label as string; const baseUrl = parentConfig.baseUrl as string; @@ -796,10 +822,10 @@ custom_edit_url: null await generateVersions(mergedVersions, parentConfig.outputDir); Object.keys(versions).forEach(async (key) => { const versionConfig = versions[key]; - const mergedConfig = { + const mergedConfig = withSchemaOverride({ ...parentConfig, ...versionConfig, - }; + }); await generateApiDocs(mergedConfig, targetDocsPluginId); }); } @@ -811,10 +837,10 @@ custom_edit_url: null ); } else { const versionConfig = versions[versionId]; - const mergedConfig = { + const mergedConfig = withSchemaOverride({ ...parentConfig, ...versionConfig, - }; + }); await generateVersions(mergedVersions, parentConfig.outputDir); await generateApiDocs(mergedConfig, targetDocsPluginId); } @@ -829,10 +855,12 @@ custom_edit_url: null .arguments("") .option("-p, --plugin-id ", "OpenAPI docs plugin ID.") .option("--all-versions", "Clean all versions.") + .option("--schemas-only", "Clean only schema docs.") .action(async (id, instance) => { const options = instance.opts(); const pluginId = options.pluginId; const allVersions = options.allVersions; + const schemasOnly = options.schemasOnly; const pluginInstances = getPluginInstances(plugins); let targetConfig: any; if (pluginId) { @@ -865,16 +893,16 @@ custom_edit_url: null ); } else { Object.keys(targetConfig).forEach(async function (key) { - await cleanApiDocs(targetConfig[key]); + await cleanApiDocs(targetConfig[key], schemasOnly); if (allVersions) { - await cleanAllVersions(targetConfig[key]); + await cleanAllVersions(targetConfig[key], schemasOnly); } }); } } else { - await cleanApiDocs(targetConfig[id]); + await cleanApiDocs(targetConfig[id], schemasOnly); if (allVersions) { - await cleanAllVersions(targetConfig[id]); + await cleanAllVersions(targetConfig[id], schemasOnly); } } }); @@ -887,9 +915,11 @@ custom_edit_url: null .usage("") .arguments("") .option("-p, --plugin-id ", "OpenAPI docs plugin ID.") + .option("--schemas-only", "Clean only schema docs.") .action(async (id, instance) => { const options = instance.opts(); const pluginId = options.pluginId; + const schemasOnly = options.schemasOnly; const pluginInstances = getPluginInstances(plugins); let targetConfig: any; if (pluginId) { @@ -925,14 +955,16 @@ custom_edit_url: null "Can't use id 'all' for OpenAPI docs versions configuration key." ); } else { - await cleanVersions(parentConfig.outputDir); + if (!schemasOnly) { + await cleanVersions(parentConfig.outputDir); + } Object.keys(versions).forEach(async (key) => { const versionConfig = versions[key]; const mergedConfig = { ...parentConfig, ...versionConfig, }; - await cleanApiDocs(mergedConfig); + await cleanApiDocs(mergedConfig, schemasOnly); }); } } else { @@ -941,7 +973,7 @@ custom_edit_url: null ...parentConfig, ...versionConfig, }; - await cleanApiDocs(mergedConfig); + await cleanApiDocs(mergedConfig, schemasOnly); } }); }, diff --git a/packages/docusaurus-plugin-openapi-docs/src/openapi/openapi.test.ts b/packages/docusaurus-plugin-openapi-docs/src/openapi/openapi.test.ts index 678a003ff..a25734a22 100644 --- a/packages/docusaurus-plugin-openapi-docs/src/openapi/openapi.test.ts +++ b/packages/docusaurus-plugin-openapi-docs/src/openapi/openapi.test.ts @@ -11,6 +11,8 @@ import path from "path"; import { posixPath } from "@docusaurus/utils"; import { readOpenapiFiles } from "."; +import { processOpenapiFile } from "./openapi"; +import type { APIOptions, SidebarOptions } from "../types"; // npx jest packages/docusaurus-plugin-openapi/src/openapi/openapi.test.ts --watch @@ -37,4 +39,60 @@ describe("openapi", () => { ).toBeDefined(); }); }); + + describe("schemasOnly", () => { + it("includes schema metadata when showSchemas is disabled", async () => { + const openapiData = { + openapi: "3.0.0", + info: { + title: "Schema Only", + version: "1.0.0", + }, + paths: { + "/ping": { + get: { + summary: "Ping", + responses: { + "200": { + description: "OK", + }, + }, + }, + }, + }, + components: { + schemas: { + WithoutTags: { + title: "Without Tags", + type: "object", + properties: { + value: { + type: "string", + }, + }, + }, + }, + }, + }; + + const options: APIOptions = { + specPath: "dummy", // required by the type but unused in this context + outputDir: "build", + showSchemas: false, + schemasOnly: true, + }; + + const sidebarOptions = {} as SidebarOptions; + + const [items] = await processOpenapiFile( + openapiData as any, + options, + sidebarOptions + ); + + const schemaItems = items.filter((item) => item.type === "schema"); + expect(schemaItems).toHaveLength(1); + expect(schemaItems[0].id).toBe("without-tags"); + }); + }); }); diff --git a/packages/docusaurus-plugin-openapi-docs/src/openapi/openapi.ts b/packages/docusaurus-plugin-openapi-docs/src/openapi/openapi.ts index 486a13751..773c6e317 100644 --- a/packages/docusaurus-plugin-openapi-docs/src/openapi/openapi.ts +++ b/packages/docusaurus-plugin-openapi-docs/src/openapi/openapi.ts @@ -95,6 +95,7 @@ function createItems( let items: PartialPage[] = []; const infoIdSpaces = openapiData.info.title.replace(" ", "-").toLowerCase(); const infoId = kebabCase(infoIdSpaces); + const schemasOnly = options?.schemasOnly === true; if (openapiData.info.description || openapiData.info.title) { // Only create an info page if we have a description. @@ -434,6 +435,7 @@ function createItems( } if ( + schemasOnly || options?.showSchemas === true || Object.entries(openapiData?.components?.schemas ?? {}) .flatMap(([_, s]) => s["x-tags"]) @@ -443,7 +445,11 @@ function createItems( for (let [schema, schemaObject] of Object.entries( openapiData?.components?.schemas ?? {} )) { - if (options?.showSchemas === true || schemaObject["x-tags"]) { + if ( + schemasOnly || + options?.showSchemas === true || + schemaObject["x-tags"] + ) { const baseIdSpaces = schemaObject?.title?.replace(" ", "-").toLowerCase() ?? ""; const baseId = kebabCase(baseIdSpaces); diff --git a/packages/docusaurus-plugin-openapi-docs/src/types.ts b/packages/docusaurus-plugin-openapi-docs/src/types.ts index 0c65e4360..45a74cf9a 100644 --- a/packages/docusaurus-plugin-openapi-docs/src/types.ts +++ b/packages/docusaurus-plugin-openapi-docs/src/types.ts @@ -51,6 +51,7 @@ export interface APIOptions { proxy?: string; markdownGenerators?: MarkdownGenerator; showSchemas?: boolean; + schemasOnly?: boolean; disableCompression?: boolean; maskCredentials?: boolean; }