From ee1c7b1918c37be2880a6b5e252019972c2eaeff Mon Sep 17 00:00:00 2001 From: Craigory Coppola Date: Tue, 17 Jan 2023 11:56:08 -0600 Subject: [PATCH] feat(core): support nx-plugin-openapi for more advanced openapi generation (#589) --- docs/core/generators/add-swagger-target.md | 4 + docs/core/generators/application.md | 4 + package.json | 1 + .../add-swagger-target/add-swagger-target.ts | 149 +++++++++++++----- .../generators/add-swagger-target/schema.d.ts | 1 + .../generators/add-swagger-target/schema.json | 5 + packages/core/src/generators/app/schema.json | 4 + .../src/generators/utils/generate-project.ts | 1 + .../src/models/project-generator-schema.ts | 1 + .../models/swagger-executor-configuration.ts | 1 + yarn.lock | 7 + 11 files changed, 142 insertions(+), 36 deletions(-) diff --git a/docs/core/generators/add-swagger-target.md b/docs/core/generators/add-swagger-target.md index 237f31c9..49ebdf5a 100644 --- a/docs/core/generators/add-swagger-target.md +++ b/docs/core/generators/add-swagger-target.md @@ -29,3 +29,7 @@ Generates a swagger setup for a given project ### target - (string): What should the project be called? + +### useNxPluginOpenAPI + +- (boolean): Should the codegen target use nx-plugin-openapi instead? diff --git a/docs/core/generators/application.md b/docs/core/generators/application.md index fe8acf8e..9da40710 100644 --- a/docs/core/generators/application.md +++ b/docs/core/generators/application.md @@ -43,3 +43,7 @@ Generate a dotnet project under the application directory. ### pathScheme - (string): Determines if the project should follow NX or dotnet path naming conventions + +### useNxPluginOpenAPI + +- (boolean): If using a codgen project, use openapi-generator diff --git a/package.json b/package.json index 7fdb1f26..27c9ad04 100644 --- a/package.json +++ b/package.json @@ -23,6 +23,7 @@ "@docusaurus/theme-search-algolia": "^2.1.0", "@mdx-js/react": "^1.6.21", "@swc/helpers": "~0.3.3", + "@trumbitta/nx-plugin-openapi": "^1.12.1", "@types/xmldoc": "^1.1.6", "chokidar": "^3.5.2", "clsx": "^1.1.1", diff --git a/packages/core/src/generators/add-swagger-target/add-swagger-target.ts b/packages/core/src/generators/add-swagger-target/add-swagger-target.ts index 6d686f77..ea511624 100644 --- a/packages/core/src/generators/add-swagger-target/add-swagger-target.ts +++ b/packages/core/src/generators/add-swagger-target/add-swagger-target.ts @@ -1,11 +1,15 @@ import { addProjectConfiguration, + ensurePackage, + GeneratorCallback, getWorkspaceLayout, joinPathFragments, ProjectConfiguration, readProjectConfiguration, + readWorkspaceConfiguration, Tree, updateProjectConfiguration, + updateWorkspaceConfiguration, } from '@nrwl/devkit'; import { libraryGenerator } from '@nrwl/js/src/generators/library/library'; @@ -16,6 +20,7 @@ export default async function generateSwaggerSetup( host: Tree, options: AddSwaggerJsonExecutorSchema, ) { + const tasks: GeneratorCallback[] = []; const project = readProjectConfiguration(host, options.project); project.targets ??= {}; if (!options.output) { @@ -25,6 +30,7 @@ export default async function generateSwaggerSetup( 'swagger.json', ); generateShellProject(host, { + ...options, swaggerProject: options.swaggerProject, project: options.project, codegenProject: options.codegenProject, @@ -33,19 +39,14 @@ export default async function generateSwaggerSetup( throw new Error('Either specify --output or --swagger-project'); } } else { - if (options.codegenProject) { + if (options.codegenProject && !options.useNxPluginOpenAPI) { project.targets.codegen = { executor: '@nx-dotnet/core:openapi-codegen', options: { openapiJsonPath: options.output, outputProject: options.codegenProject, }, - dependsOn: [ - { - target: options.target || 'swagger', - projects: 'self', - }, - ], + dependsOn: ['swagger'], }; } } @@ -54,22 +55,16 @@ export default async function generateSwaggerSetup( }; if (options.codegenProject) { - await libraryGenerator(host, { - name: options.codegenProject, - directory: 'generated', - buildable: true, - }); - const codegenProjectConfiguration = readProjectConfiguration( - host, - `generated-${options.codegenProject}`, - ); - codegenProjectConfiguration.implicitDependencies ??= []; - codegenProjectConfiguration.implicitDependencies.push( - options.swaggerProject ? options.swaggerProject : options.project, - ); + tasks.push(...(await generateCodegenProject(host, options))); } updateProjectConfiguration(host, options.project, project); + + return async () => { + for (const task of tasks) { + await task(); + } + }; } function swaggerProjectRoot(host: Tree, swaggerProject: string) { @@ -82,7 +77,7 @@ function swaggerProjectRoot(host: Tree, swaggerProject: string) { function generateShellProject( host: Tree, - options: { project: string; swaggerProject: string; codegenProject?: string }, + options: AddSwaggerJsonExecutorSchema & { swaggerProject: string }, ) { const root = swaggerProjectRoot(host, options.swaggerProject); const targets: ProjectConfiguration['targets'] = {}; @@ -94,22 +89,19 @@ function generateShellProject( executor: 'nx:noop', outputs: [root], }; - targets.codegen = { - executor: '@nx-dotnet/core:openapi-codegen', - options: { - openapiJsonPath: `${swaggerProjectRoot( - host, - options.swaggerProject, - )}/swagger.json`, - outputProject: `generated-${options.codegenProject}`, - }, - dependsOn: [ - { - projects: 'dependencies', - target: 'swagger', + if (!options.useNxPluginOpenAPI) { + targets.codegen = { + executor: '@nx-dotnet/core:openapi-codegen', + options: { + openapiJsonPath: `${swaggerProjectRoot( + host, + options.swaggerProject, + )}/swagger.json`, + outputProject: `generated-${options.codegenProject}`, }, - ], - }; + dependsOn: ['^swagger'], + }; + } } addProjectConfiguration(host, options.swaggerProject, { root, @@ -117,3 +109,88 @@ function generateShellProject( implicitDependencies: [options.project], }); } + +async function generateCodegenProject( + host: Tree, + options: AddSwaggerJsonExecutorSchema, +): Promise { + const tasks: GeneratorCallback[] = []; + const nameWithDirectory = `generated-${options.codegenProject}`; + if (options.useNxPluginOpenAPI) { + ensurePackage(host, '@trumbitta/nx-plugin-openapi', '^1.12.1'); + const { + default: nxPluginOpenAPIGenerator, + }: // eslint-disable-next-line @typescript-eslint/no-var-requires + typeof import('@trumbitta/nx-plugin-openapi/src/generators/api-lib/generator') = require('@trumbitta/nx-plugin-openapi/src/generators/api-lib/generator'); + const { + default: nxPluginOpenAPIInitGenerator, + }: // eslint-disable-next-line @typescript-eslint/no-var-requires + typeof import('@trumbitta/nx-plugin-openapi/src/generators/init/generator') = require('@trumbitta/nx-plugin-openapi/src/generators/init/generator'); + + tasks.push(await nxPluginOpenAPIInitGenerator(host)); + + tasks.push( + await nxPluginOpenAPIGenerator(host, { + isRemoteSpec: false, + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + name: options.codegenProject!, + directory: 'generated', + generator: 'typescript-fetch', + sourceSpecLib: options.swaggerProject, + }), + ); + + const configuration = readProjectConfiguration(host, nameWithDirectory); + configuration.targets ??= {}; + const targetConfiguration = configuration.targets?.['generate-sources']; + targetConfiguration.options['sourceSpecPathOrUrl'] = joinPathFragments( + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + readProjectConfiguration(host, options.swaggerProject!).root, + 'swagger.json', + ); + targetConfiguration.dependsOn = ['^swagger']; + configuration.targets['codegen'] = targetConfiguration; + delete configuration.targets['generate-sources']; + updateProjectConfiguration(host, nameWithDirectory, configuration); + } else { + tasks.push( + await libraryGenerator(host, { + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + name: options.codegenProject!, + directory: 'generated', + buildable: true, + }), + ); + const codegenProjectConfiguration = readProjectConfiguration( + host, + nameWithDirectory, + ); + codegenProjectConfiguration.implicitDependencies ??= []; + codegenProjectConfiguration.implicitDependencies.push( + options.swaggerProject ? options.swaggerProject : options.project, + ); + updateProjectConfiguration( + host, + nameWithDirectory, + codegenProjectConfiguration, + ); + } + + const wc = readWorkspaceConfiguration(host); + + const cacheableOperations: string[] | null = + wc.tasksRunnerOptions?.default?.options?.cacheableOperations; + if (cacheableOperations) { + cacheableOperations.push('codegen', options.target ?? 'swagger'); + } + + const newBuildDeps = ['codegen', '^codegen']; + wc.targetDefaults ??= {}; + wc.targetDefaults['build'] ??= {}; + wc.targetDefaults['build'].dependsOn ??= []; + wc.targetDefaults['build'].dependsOn.push(...newBuildDeps); + + updateWorkspaceConfiguration(host, wc); + + return tasks; +} diff --git a/packages/core/src/generators/add-swagger-target/schema.d.ts b/packages/core/src/generators/add-swagger-target/schema.d.ts index a72e074e..75e7fb4d 100644 --- a/packages/core/src/generators/add-swagger-target/schema.d.ts +++ b/packages/core/src/generators/add-swagger-target/schema.d.ts @@ -6,4 +6,5 @@ export type AddSwaggerJsonExecutorSchema = { target?: string; swaggerProject?: string; codegenProject?: string; + useNxPluginOpenAPI?: boolean; }; diff --git a/packages/core/src/generators/add-swagger-target/schema.json b/packages/core/src/generators/add-swagger-target/schema.json index 95c75547..b2fb21bd 100644 --- a/packages/core/src/generators/add-swagger-target/schema.json +++ b/packages/core/src/generators/add-swagger-target/schema.json @@ -31,6 +31,11 @@ "type": "string", "description": "What should the project be called?", "default": "swagger" + }, + "useNxPluginOpenAPI": { + "type": "boolean", + "description": "Should the codegen target use nx-plugin-openapi instead?", + "default": "false" } }, "required": ["project"] diff --git a/packages/core/src/generators/app/schema.json b/packages/core/src/generators/app/schema.json index fc0dca46..a6f4f2e3 100644 --- a/packages/core/src/generators/app/schema.json +++ b/packages/core/src/generators/app/schema.json @@ -86,6 +86,10 @@ { "value": "dotnet", "label": "Dotnet naming conventions" } ] } + }, + "useNxPluginOpenAPI": { + "type": "boolean", + "description": "If using a codgen project, use openapi-generator" } }, "required": ["name", "language"] diff --git a/packages/core/src/generators/utils/generate-project.ts b/packages/core/src/generators/utils/generate-project.ts index f3070d62..722c0a69 100644 --- a/packages/core/src/generators/utils/generate-project.ts +++ b/packages/core/src/generators/utils/generate-project.ts @@ -228,6 +228,7 @@ export async function GenerateProject( project: normalizedOptions.projectName, swaggerProject: `${normalizedOptions.projectName}-swagger`, codegenProject: `${normalizedOptions.projectName}-types`, + useNxPluginOpenAPI: normalizedOptions.useNxPluginOpenAPI, }); } diff --git a/packages/core/src/models/project-generator-schema.ts b/packages/core/src/models/project-generator-schema.ts index 1235d47c..551a35f6 100644 --- a/packages/core/src/models/project-generator-schema.ts +++ b/packages/core/src/models/project-generator-schema.ts @@ -16,4 +16,5 @@ export interface NxDotnetProjectGeneratorSchema { solutionFile?: string | boolean; skipSwaggerLib: boolean; pathScheme: 'nx' | 'dotnet'; + useNxPluginOpenAPI?: boolean; } diff --git a/packages/core/src/models/swagger-executor-configuration.ts b/packages/core/src/models/swagger-executor-configuration.ts index ad38510a..35537deb 100644 --- a/packages/core/src/models/swagger-executor-configuration.ts +++ b/packages/core/src/models/swagger-executor-configuration.ts @@ -14,6 +14,7 @@ export function getSwaggerExecutorConfiguration( options: { output: outputDirectory, }, + dependsOn: ['build'], }; } diff --git a/yarn.lock b/yarn.lock index e98b408f..cfe4b906 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5433,6 +5433,13 @@ resolved "https://registry.yarnpkg.com/@tootallnate/once/-/once-2.0.0.tgz#f544a148d3ab35801c1f633a7441fd87c2e484bf" integrity sha512-XCuKFP5PS55gnMVu3dty8KPatLqUoy/ZYzDzAGCQ8JNFCkLXzmI7vNHCR+XpbZaMWQK/vQubr7PkYq8g470J/A== +"@trumbitta/nx-plugin-openapi@^1.12.1": + version "1.12.1" + resolved "https://registry.yarnpkg.com/@trumbitta/nx-plugin-openapi/-/nx-plugin-openapi-1.12.1.tgz#c350213980509373fd25e48efc6d86c85071d657" + integrity sha512-vSQ+cDMtPuB/aBHtwrHsNKgElD55ubUMxsyCm7Vv81e/UQrNhIQ+/4zBFj3QWE2WO1PEi3TQD6jF5nbg173bUQ== + dependencies: + cross-spawn "^7.0.3" + "@trysound/sax@0.2.0": version "0.2.0" resolved "https://registry.yarnpkg.com/@trysound/sax/-/sax-0.2.0.tgz#cccaab758af56761eb7bf37af6f03f326dd798ad"