From 5a93d6a3aefb367e6f2a27313a10f3edabbfb0e0 Mon Sep 17 00:00:00 2001 From: Jeff Strunk Date: Thu, 1 Aug 2024 17:26:57 -0400 Subject: [PATCH] feat(type-safe-api): add commitGeneratedCode option to control generated code This commit introduces a new `commitGeneratedCode` option to the TypeSafeApiProject, which allows controlling whether generated code should be committed or ignored for all generated projects. The main changes include: - Add a `commitGeneratedCode` option to the TypeSafeApiProject and related options interfaces. - Set the default value of `commitGeneratedCode` to `false`. - Conditionally add patterns to .gitignore based on the `commitGeneratedCode` option for all generated projects. - Update tests to cover the new `commitGeneratedCode` option. By default, the generated code will be ignored in the repository, except for Python projects where it will be included to allow for easier distribution and deployment of the generated artifacts using Poetry. Fixes: #813 --- ...ted-asyncapi-html-documentation-project.ts | 4 +- ...asyncapi-markdown-documentation-project.ts | 4 +- ...erated-html-redoc-documentation-project.ts | 4 +- .../generated-html2-documentation-project.ts | 6 +- ...enerated-markdown-documentation-project.ts | 16 +- ...enerated-plantuml-documentation-project.ts | 6 +- ...-python-cdk-infrastructure-base-project.ts | 12 +- ...escript-cdk-infrastructure-base-project.ts | 12 +- .../generated-typescript-library-project.ts | 16 +- .../generated-java-runtime-base-project.ts | 20 ++- .../generated-python-runtime-base-project.ts | 18 +- ...nerated-typescript-runtime-base-project.ts | 18 +- .../src/project/type-safe-api-project.ts | 46 +++++ packages/type-safe-api/src/project/types.ts | 11 +- .../project/type-safe-api-project.test.ts | 162 ++++++++++++++++++ 15 files changed, 308 insertions(+), 47 deletions(-) diff --git a/packages/type-safe-api/src/project/codegen/documentation/generated-asyncapi-html-documentation-project.ts b/packages/type-safe-api/src/project/codegen/documentation/generated-asyncapi-html-documentation-project.ts index 5bee24def..8e91d6d73 100644 --- a/packages/type-safe-api/src/project/codegen/documentation/generated-asyncapi-html-documentation-project.ts +++ b/packages/type-safe-api/src/project/codegen/documentation/generated-asyncapi-html-documentation-project.ts @@ -33,6 +33,8 @@ export class GeneratedAsyncApiHtmlDocumentationProject extends Project { ); this.compileTask.spawn(this.generateTask); - this.gitignore.addPatterns("index.html"); + if (!options.commitGeneratedCode) { + this.gitignore.addPatterns("index.html"); + } } } diff --git a/packages/type-safe-api/src/project/codegen/documentation/generated-asyncapi-markdown-documentation-project.ts b/packages/type-safe-api/src/project/codegen/documentation/generated-asyncapi-markdown-documentation-project.ts index 618b55214..4f4a697b6 100644 --- a/packages/type-safe-api/src/project/codegen/documentation/generated-asyncapi-markdown-documentation-project.ts +++ b/packages/type-safe-api/src/project/codegen/documentation/generated-asyncapi-markdown-documentation-project.ts @@ -33,6 +33,8 @@ export class GeneratedAsyncApiMarkdownDocumentationProject extends Project { ); this.compileTask.spawn(this.generateTask); - this.gitignore.addPatterns("index.md"); + if (!options.commitGeneratedCode) { + this.gitignore.addPatterns("index.md"); + } } } diff --git a/packages/type-safe-api/src/project/codegen/documentation/generated-html-redoc-documentation-project.ts b/packages/type-safe-api/src/project/codegen/documentation/generated-html-redoc-documentation-project.ts index 9b86bce8c..b3c037c1b 100644 --- a/packages/type-safe-api/src/project/codegen/documentation/generated-html-redoc-documentation-project.ts +++ b/packages/type-safe-api/src/project/codegen/documentation/generated-html-redoc-documentation-project.ts @@ -39,6 +39,8 @@ export class GeneratedHtmlRedocDocumentationProject extends Project { ); this.compileTask.spawn(this.generateTask); - this.gitignore.addPatterns("index.html"); + if (!options.commitGeneratedCode) { + this.gitignore.addPatterns("index.html"); + } } } diff --git a/packages/type-safe-api/src/project/codegen/documentation/generated-html2-documentation-project.ts b/packages/type-safe-api/src/project/codegen/documentation/generated-html2-documentation-project.ts index fdc7eea8e..f51560304 100644 --- a/packages/type-safe-api/src/project/codegen/documentation/generated-html2-documentation-project.ts +++ b/packages/type-safe-api/src/project/codegen/documentation/generated-html2-documentation-project.ts @@ -47,6 +47,10 @@ export class GeneratedHtml2DocumentationProject extends Project { this.compileTask.spawn(this.generateTask); - this.gitignore.addPatterns(".openapi-generator", "index.html"); + if (!options.commitGeneratedCode) { + this.gitignore.addPatterns(".openapi-generator", "index.html"); + } else { + this.gitignore.addPatterns(".openapi-generator"); + } } } diff --git a/packages/type-safe-api/src/project/codegen/documentation/generated-markdown-documentation-project.ts b/packages/type-safe-api/src/project/codegen/documentation/generated-markdown-documentation-project.ts index 2d8d32481..1699bc677 100644 --- a/packages/type-safe-api/src/project/codegen/documentation/generated-markdown-documentation-project.ts +++ b/packages/type-safe-api/src/project/codegen/documentation/generated-markdown-documentation-project.ts @@ -47,11 +47,15 @@ export class GeneratedMarkdownDocumentationProject extends Project { this.compileTask.spawn(this.generateTask); - this.gitignore.addPatterns( - ".openapi-generator", - "Apis", - "Models", - "README.md" - ); + if (!options.commitGeneratedCode) { + this.gitignore.addPatterns( + ".openapi-generator", + "Apis", + "Models", + "README.md" + ); + } else { + this.gitignore.addPatterns(".openapi-generator"); + } } } diff --git a/packages/type-safe-api/src/project/codegen/documentation/generated-plantuml-documentation-project.ts b/packages/type-safe-api/src/project/codegen/documentation/generated-plantuml-documentation-project.ts index e1b95ed41..cd6ca8f87 100644 --- a/packages/type-safe-api/src/project/codegen/documentation/generated-plantuml-documentation-project.ts +++ b/packages/type-safe-api/src/project/codegen/documentation/generated-plantuml-documentation-project.ts @@ -47,6 +47,10 @@ export class GeneratedPlantumlDocumentationProject extends Project { this.compileTask.spawn(this.generateTask); - this.gitignore.addPatterns(".openapi-generator", "schemas.plantuml"); + if (!options.commitGeneratedCode) { + this.gitignore.addPatterns(".openapi-generator", "schemas.plantuml"); + } else { + this.gitignore.addPatterns(".openapi-generator"); + } } } diff --git a/packages/type-safe-api/src/project/codegen/infrastructure/cdk/generated-python-cdk-infrastructure-base-project.ts b/packages/type-safe-api/src/project/codegen/infrastructure/cdk/generated-python-cdk-infrastructure-base-project.ts index 27c0194bc..013d8e3ef 100644 --- a/packages/type-safe-api/src/project/codegen/infrastructure/cdk/generated-python-cdk-infrastructure-base-project.ts +++ b/packages/type-safe-api/src/project/codegen/infrastructure/cdk/generated-python-cdk-infrastructure-base-project.ts @@ -114,8 +114,16 @@ export abstract class GeneratedPythonCdkInfrastructureBaseProject extends Python this.preCompileTask.spawn(generateTask); - // Ignore the generated code - this.gitignore.addPatterns(this.moduleName, ".openapi-generator", "mocks"); + if (!options.commitGeneratedCode) { + // Ignore the generated code + this.gitignore.addPatterns( + this.moduleName, + ".openapi-generator", + "mocks" + ); + } else { + this.gitignore.addPatterns(".openapi-generator"); + } // The poetry install that runs as part of post synthesis expects there to be some code present, but code isn't // generated until build time. This means that the first install will fail when either generating the project for diff --git a/packages/type-safe-api/src/project/codegen/infrastructure/cdk/generated-typescript-cdk-infrastructure-base-project.ts b/packages/type-safe-api/src/project/codegen/infrastructure/cdk/generated-typescript-cdk-infrastructure-base-project.ts index ff3cb1b25..2d523e490 100644 --- a/packages/type-safe-api/src/project/codegen/infrastructure/cdk/generated-typescript-cdk-infrastructure-base-project.ts +++ b/packages/type-safe-api/src/project/codegen/infrastructure/cdk/generated-typescript-cdk-infrastructure-base-project.ts @@ -159,12 +159,18 @@ export abstract class GeneratedTypescriptCdkInfrastructureBaseProject extends Ty generateTask.exec( `cp -f ${this.options.specPath} ${this.packagedSpecPath}` ); - this.gitignore.addPatterns(`/${this.packagedSpecPath}`); + if (!options.commitGeneratedCode) { + this.gitignore.addPatterns(`/${this.packagedSpecPath}`); + } this.preCompileTask.spawn(generateTask); - // Ignore the generated code - this.gitignore.addPatterns(this.srcdir, ".openapi-generator", "mocks"); + if (!options.commitGeneratedCode) { + // Ignore the generated code + this.gitignore.addPatterns(this.srcdir, ".openapi-generator", "mocks"); + } else { + this.gitignore.addPatterns(".openapi-generator"); + } // If we're not in a monorepo, we need to link the generated types such that the local dependency can be resolved if (!options.isWithinMonorepo) { diff --git a/packages/type-safe-api/src/project/codegen/library/generated-typescript-library-project.ts b/packages/type-safe-api/src/project/codegen/library/generated-typescript-library-project.ts index 44ea657cd..87ae9cb4c 100644 --- a/packages/type-safe-api/src/project/codegen/library/generated-typescript-library-project.ts +++ b/packages/type-safe-api/src/project/codegen/library/generated-typescript-library-project.ts @@ -114,13 +114,15 @@ export abstract class GeneratedTypescriptLibraryProject extends TypeScriptProjec this.preCompileTask.spawn(generateTask); - // Ignore all the generated code - this.gitignore.addPatterns( - "src", - ".npmignore", - "README.md", - ".openapi-generator" - ); + if (!options.commitGeneratedCode) { + // Ignore all the generated code + this.gitignore.addPatterns( + "src", + ".npmignore", + "README.md", + ".openapi-generator" + ); + } // If we're not in a monorepo, we need to link the generated client such that any local dependency on it can be // resolved diff --git a/packages/type-safe-api/src/project/codegen/runtime/generated-java-runtime-base-project.ts b/packages/type-safe-api/src/project/codegen/runtime/generated-java-runtime-base-project.ts index ff18ff40b..3f263dba2 100644 --- a/packages/type-safe-api/src/project/codegen/runtime/generated-java-runtime-base-project.ts +++ b/packages/type-safe-api/src/project/codegen/runtime/generated-java-runtime-base-project.ts @@ -136,14 +136,18 @@ export abstract class GeneratedJavaRuntimeBaseProject extends JavaProject { this.preCompileTask.spawn(generateTask); - // Ignore all the generated code - this.gitignore.addPatterns( - "src", - "docs", - "api", - "README.md", - ".openapi-generator" - ); + if (!options.commitGeneratedCode) { + // Ignore all the generated code + this.gitignore.addPatterns( + "src", + "docs", + "api", + "README.md", + ".openapi-generator" + ); + } else { + this.gitignore.addPatterns(".openapi-generator"); + } } public buildGenerateCommandArgs = () => { diff --git a/packages/type-safe-api/src/project/codegen/runtime/generated-python-runtime-base-project.ts b/packages/type-safe-api/src/project/codegen/runtime/generated-python-runtime-base-project.ts index 0e06ad4a1..2ec88b990 100644 --- a/packages/type-safe-api/src/project/codegen/runtime/generated-python-runtime-base-project.ts +++ b/packages/type-safe-api/src/project/codegen/runtime/generated-python-runtime-base-project.ts @@ -102,13 +102,17 @@ export abstract class GeneratedPythonRuntimeBaseProject extends PythonProject { this.preCompileTask.spawn(generateTask); - // Ignore all the generated code - this.gitignore.addPatterns( - this.moduleName, - "docs", - "README.md", - ".openapi-generator" - ); + if (!this.options.commitGeneratedCode) { + // Ignore all the generated code + this.gitignore.addPatterns( + this.moduleName, + "docs", + "README.md", + ".openapi-generator" + ); + } else { + this.gitignore.addPatterns(".openapi-generator"); + } // The poetry install that runs as part of post synthesis expects there to be some code present, but code isn't // generated until build time. This means that the first install will fail when either generating the project for diff --git a/packages/type-safe-api/src/project/codegen/runtime/generated-typescript-runtime-base-project.ts b/packages/type-safe-api/src/project/codegen/runtime/generated-typescript-runtime-base-project.ts index 5bee0ee53..249f435f0 100644 --- a/packages/type-safe-api/src/project/codegen/runtime/generated-typescript-runtime-base-project.ts +++ b/packages/type-safe-api/src/project/codegen/runtime/generated-typescript-runtime-base-project.ts @@ -127,13 +127,17 @@ export abstract class GeneratedTypescriptRuntimeBaseProject extends TypeScriptPr this.preCompileTask.spawn(generateTask); - // Ignore all the generated code - this.gitignore.addPatterns( - this.srcdir, - ".npmignore", - "README.md", - ".openapi-generator" - ); + if (!options.commitGeneratedCode) { + // Ignore all the generated code + this.gitignore.addPatterns( + this.srcdir, + ".npmignore", + "README.md", + ".openapi-generator" + ); + } else { + this.gitignore.addPatterns(".openapi-generator"); + } // If we're not in a monorepo, we need to link the generated client such that any local dependency on it can be // resolved diff --git a/packages/type-safe-api/src/project/type-safe-api-project.ts b/packages/type-safe-api/src/project/type-safe-api-project.ts index b6afe6c0a..d54b9d662 100644 --- a/packages/type-safe-api/src/project/type-safe-api-project.ts +++ b/packages/type-safe-api/src/project/type-safe-api-project.ts @@ -159,6 +159,11 @@ export interface TypeSafeApiProjectOptions extends ProjectOptions { * fully-fledged runtimes, for example react hooks or clients in languages that aren't supported as runtimes. */ readonly library?: LibraryConfiguration; + /** + * Whether to commit the code generated by the OpenAPI Generator. + * @default false + */ + readonly commitGeneratedCode?: boolean; } /** @@ -265,16 +270,28 @@ export class TypeSafeApiProject extends Project { ProjectUtils.isNamedInstanceOf(this.parent, NodeProject) ? this.parent.package.packageManager : NodePackageManager.PNPM, + commitGeneratedCode: + options.runtime?.options?.typescript?.commitGeneratedCode ?? + options.commitGeneratedCode ?? + false, ...options.runtime?.options?.typescript, }, pythonOptions: { authorName: "APJ Cope", authorEmail: "apj-cope@amazon.com", version: "0.0.0", + commitGeneratedCode: + options.runtime?.options?.python?.commitGeneratedCode ?? + options.commitGeneratedCode ?? + false, ...options.runtime?.options?.python, }, javaOptions: { version: "0.0.0", + commitGeneratedCode: + options.runtime?.options?.java?.commitGeneratedCode ?? + options.commitGeneratedCode ?? + false, ...options.runtime?.options?.java, }, }); @@ -327,6 +344,11 @@ export class TypeSafeApiProject extends Project { ProjectUtils.isNamedInstanceOf(this.parent, NodeProject) ? this.parent.package.packageManager : NodePackageManager.PNPM, + commitGeneratedCode: + options.library?.options?.typescriptReactQueryHooks + ?.commitGeneratedCode ?? + options.commitGeneratedCode ?? + false, ...options.library?.options?.typescriptReactQueryHooks, }, }); @@ -396,16 +418,28 @@ export class TypeSafeApiProject extends Project { ProjectUtils.isNamedInstanceOf(this.parent, NodeProject) ? this.parent.package.packageManager : NodePackageManager.PNPM, + commitGeneratedCode: + options.handlers?.options?.typescript?.commitGeneratedCode ?? + options.commitGeneratedCode ?? + false, ...options.handlers?.options?.typescript, }, pythonOptions: { authorName: "APJ Cope", authorEmail: "apj-cope@amazon.com", version: "0.0.0", + commitGeneratedCode: + options.handlers?.options?.python?.commitGeneratedCode ?? + options.commitGeneratedCode ?? + false, ...options.handlers?.options?.python, }, javaOptions: { version: "0.0.0", + commitGeneratedCode: + options.handlers?.options?.java?.commitGeneratedCode ?? + options.commitGeneratedCode ?? + false, ...options.handlers?.options?.java, }, generatedRuntimes: { @@ -470,16 +504,28 @@ export class TypeSafeApiProject extends Project { ProjectUtils.isNamedInstanceOf(this.parent, NodeProject) ? this.parent.package.packageManager : NodePackageManager.PNPM, + commitGeneratedCode: + options.infrastructure.options?.typescript?.commitGeneratedCode ?? + options.commitGeneratedCode ?? + false, ...options.infrastructure.options?.typescript, }, pythonOptions: { authorName: "APJ Cope", authorEmail: "apj-cope@amazon.com", version: "0.0.0", + commitGeneratedCode: + options.infrastructure.options?.python?.commitGeneratedCode ?? + options.commitGeneratedCode ?? + false, ...options.infrastructure.options?.python, }, javaOptions: { version: "0.0.0", + commitGeneratedCode: + options.infrastructure.options?.java?.commitGeneratedCode ?? + options.commitGeneratedCode ?? + false, ...options.infrastructure.options?.java, }, generatedRuntimes: { diff --git a/packages/type-safe-api/src/project/types.ts b/packages/type-safe-api/src/project/types.ts index 3e4178d27..9f1b5c991 100644 --- a/packages/type-safe-api/src/project/types.ts +++ b/packages/type-safe-api/src/project/types.ts @@ -139,6 +139,11 @@ export interface OpenApiGeneratorCliConfig { * Options for a code project generated with OpenAPI Generator */ export interface GeneratedWithOpenApiGeneratorOptions { + /** + * Whether to commit the code generated by the OpenAPI Generator. + * @default false + */ + readonly commitGeneratedCode?: boolean; /** * Configuration for the OpenAPI Generator CLI. Overrides default values if specified. * @see https://github.com/OpenAPITools/openapi-generator-cli#configuration @@ -481,12 +486,14 @@ export interface GeneratedPlantumlDocumentationOptions /** * Options for the async api html documentation project */ -export interface GeneratedAsyncApiHtmlDocumentationOptions {} +export interface GeneratedAsyncApiHtmlDocumentationOptions + extends GeneratedWithOpenApiGeneratorOptions {} /** * Options for the async api markdown documentation project */ -export interface GeneratedAsyncApiMarkdownDocumentationOptions {} +export interface GeneratedAsyncApiMarkdownDocumentationOptions + extends GeneratedWithOpenApiGeneratorOptions {} /** * Options for generated documentation projects diff --git a/packages/type-safe-api/test/project/type-safe-api-project.test.ts b/packages/type-safe-api/test/project/type-safe-api-project.test.ts index 1e2c0a1e1..a4b434e6b 100644 --- a/packages/type-safe-api/test/project/type-safe-api-project.test.ts +++ b/packages/type-safe-api/test/project/type-safe-api-project.test.ts @@ -469,4 +469,166 @@ describe("Type Safe Api Project Unit Tests", () => { ] ).toMatchSnapshot(); }); + + it("commitGeneratedCode includes generated code", () => { + const project = new TypeSafeApiProject({ + name: `openapi-commitAll`, + outdir: path.resolve(__dirname, `openapi-commitAll`), + infrastructure: { + language: Language.TYPESCRIPT, + }, + runtime: { + languages: [Language.PYTHON, Language.TYPESCRIPT], + }, + model: { + language: ModelLanguage.OPENAPI, + options: { + openapi: { + title: "MyService", + }, + }, + }, + commitGeneratedCode: true, + }); + + const snapshot = synthProject(project); + expect( + ( + snapshot[ + `${path.relative( + project.outdir, + project.infrastructure.typescript!.outdir + )}/.gitignore` + ] as string + ).split("\n") + ).toEqual( + expect.not.arrayContaining([project.infrastructure.typescript!.srcdir]) + ); + expect( + ( + snapshot[ + `${path.relative( + project.outdir, + project.runtime.python!.outdir + )}/.gitignore` + ] as string + ).split("\n") + ).toEqual( + expect.not.arrayContaining([ + "README.md", + project.runtime.python!.moduleName, + ]) + ); + }); + + it("commitGeneratedCode override in subproject", () => { + const project = new TypeSafeApiProject({ + name: `openapi-commitOverride`, + outdir: path.resolve(__dirname, `openapi-commitOverride`), + infrastructure: { + language: Language.TYPESCRIPT, + options: { + typescript: { + commitGeneratedCode: false, + }, + }, + }, + runtime: { + languages: [Language.PYTHON, Language.TYPESCRIPT], + }, + model: { + language: ModelLanguage.OPENAPI, + options: { + openapi: { + title: "MyService", + }, + }, + }, + commitGeneratedCode: true, + }); + + const snapshot = synthProject(project); + expect( + ( + snapshot[ + `${path.relative( + project.outdir, + project.infrastructure.typescript!.outdir + )}/.gitignore` + ] as string + ).split("\n") + ).toEqual( + expect.arrayContaining([project.infrastructure.typescript!.srcdir]) + ); + expect( + ( + snapshot[ + `${path.relative( + project.outdir, + project.runtime.python!.outdir + )}/.gitignore` + ] as string + ).split("\n") + ).toEqual( + expect.not.arrayContaining([ + "README.md", + project.runtime.python!.moduleName, + ]) + ); + }); + + it("commitGeneratedCode only python runtime", () => { + const project = new TypeSafeApiProject({ + name: `openapi-commitRuntime`, + outdir: path.resolve(__dirname, `openapi-commitRuntime`), + infrastructure: { + language: Language.TYPESCRIPT, + }, + runtime: { + languages: [Language.PYTHON, Language.TYPESCRIPT], + options: { + python: { + commitGeneratedCode: true, + }, + }, + }, + model: { + language: ModelLanguage.OPENAPI, + options: { + openapi: { + title: "MyService", + }, + }, + }, + }); + + const snapshot = synthProject(project); + expect( + ( + snapshot[ + `${path.relative( + project.outdir, + project.infrastructure.typescript!.outdir + )}/.gitignore` + ] as string + ).split("\n") + ).toEqual( + expect.arrayContaining([project.infrastructure.typescript!.srcdir]) + ); + expect( + ( + snapshot[ + `${path.relative( + project.outdir, + project.runtime.python!.outdir + )}/.gitignore` + ] as string + ).split("\n") + ).toEqual( + expect.not.arrayContaining([ + "README.md", + project.runtime.python!.moduleName, + ]) + ); + }); });