From afe857fb8d132b5c72e5a8bf602a985c6e9b5c32 Mon Sep 17 00:00:00 2001 From: Tine Kondo Date: Mon, 11 Jul 2022 22:14:26 +0000 Subject: [PATCH] feat(nx-spring-boot): add `install` executor + make `build` depend on it This executor publishes project's artifacts (i.e jar file) to local Maven repository (`~/.m2/repository`). This allows dependant projects to properly resolve the dependency during their on build. Besides, a task dependency was added to `build` executor, so that Nx will automatically call this `install` executor on dependencies, when running the `build` task from the dependant project, meaning you don't have to worry about it. Closes #65, #66 and #71 --- .../tests/nx-spring-boot.spec.ts | 85 ++++++++++++------- packages/nx-spring-boot/README.md | 7 ++ packages/nx-spring-boot/executors.json | 5 ++ packages/nx-spring-boot/src/core/constants.ts | 2 + .../src/executors/install/executor.spec.ts | 47 ++++++++++ .../src/executors/install/executor.ts | 11 +++ .../src/executors/install/schema.d.ts | 6 ++ .../src/executors/install/schema.json | 24 ++++++ .../src/generators/project/generator.spec.ts | 16 ++-- .../src/generators/project/generator.ts | 12 ++- .../project/lib/add-maven-publish-plugin.ts | 40 +++++++++ 11 files changed, 214 insertions(+), 41 deletions(-) create mode 100644 packages/nx-spring-boot/src/executors/install/executor.spec.ts create mode 100644 packages/nx-spring-boot/src/executors/install/executor.ts create mode 100644 packages/nx-spring-boot/src/executors/install/schema.d.ts create mode 100644 packages/nx-spring-boot/src/executors/install/schema.json create mode 100644 packages/nx-spring-boot/src/generators/project/lib/add-maven-publish-plugin.ts diff --git a/e2e/nx-spring-boot-e2e/tests/nx-spring-boot.spec.ts b/e2e/nx-spring-boot-e2e/tests/nx-spring-boot.spec.ts index d312a005..690666bc 100644 --- a/e2e/nx-spring-boot-e2e/tests/nx-spring-boot.spec.ts +++ b/e2e/nx-spring-boot-e2e/tests/nx-spring-boot.spec.ts @@ -32,8 +32,8 @@ describe('nx-spring-boot e2e', () => { `generate @nxrocks/nx-spring-boot:new ${prjName}` ); - const resultBuildInfo= await runNxCommandAsync(`buildInfo ${prjName}`); - expect(resultBuildInfo.stdout).toContain(`Executing command: ${isWin ? 'mvnw.cmd' : './mvnw'} spring-boot:build-info`) + const resultBuild= await runNxCommandAsync(`build ${prjName}`); + expect(resultBuild.stdout).toContain(`Executing command: ${isWin ? 'mvnw.cmd' : './mvnw'} package`) expect(() => checkFilesExist(`apps/${prjName}/mvnw`,`apps/${prjName}/pom.xml`, `apps/${prjName}/HELP.md`) @@ -51,7 +51,11 @@ describe('nx-spring-boot e2e', () => { }, 200000); - it('should create nx-spring-boot with given options', async() => { + it.each` + projectType + ${'application'} + ${'library'} +`(`should create a spring-boot '$projectType' with given options`, async ({ projectType }) => { const prjName = uniq('nx-spring-boot'); const buildSystem = 'maven-project'; const javaVersion = '11'; @@ -60,23 +64,24 @@ describe('nx-spring-boot e2e', () => { const artifactId = 'api' ; const description = 'My sample app'; //const version = '1.2.3'; https://github.com/nrwl/nx/issues/10786 + const prjDir = projectType === 'application' ? 'apps' : 'libs'; await runNxCommandAsync( - `generate @nxrocks/nx-spring-boot:new ${prjName} --projectType application --buildSystem=${buildSystem} --packageName=${packageName} --groupId=${groupId} --artifactId=${artifactId} --description="${description}" --javaVersion=${javaVersion}` + `generate @nxrocks/nx-spring-boot:new ${prjName} --projectType ${projectType} --buildSystem=${buildSystem} --packageName=${packageName} --groupId=${groupId} --artifactId=${artifactId} --description="${description}" --javaVersion=${javaVersion}` ); - const resultBuildInfo= await runNxCommandAsync(`buildInfo ${prjName}`); - expect(resultBuildInfo.stdout).toContain(`Executing command: ${isWin ? 'mvnw.cmd' : './mvnw'} spring-boot:build-info`) + const resultBuild= await runNxCommandAsync(`build ${prjName}`); + expect(resultBuild.stdout).toContain(`Executing command: ${isWin ? 'mvnw.cmd' : './mvnw'} package`) expect(() => checkFilesExist( - `apps/${prjName}/mvnw`, - `apps/${prjName}/pom.xml`, - `apps/${prjName}/HELP.md`, - `apps/${prjName}/src/main/java/com/tinesoft/api/${names(prjName).className}Application.java`) + `${prjDir}/${prjName}/mvnw`, + `${prjDir}/${prjName}/pom.xml`, + `${prjDir}/${prjName}/HELP.md`, + `${prjDir}/${prjName}/src/main/java/com/tinesoft/api/${names(prjName).className}Application.java`) ).not.toThrow(); - const pomXml = readFile(`apps/${prjName}/pom.xml`); + const pomXml = readFile(`${prjDir}/${prjName}/pom.xml`); expect(pomXml).toContain(`${groupId}`); expect(pomXml).toContain(`${artifactId}`); expect(pomXml).toContain(`${prjName}`); @@ -89,25 +94,30 @@ describe('nx-spring-boot e2e', () => { const execPermission = '755'; expect( lstatSync( - tmpProjPath(`apps/${prjName}/mvnw`) + tmpProjPath(`${prjDir}/${prjName}/mvnw`) ).mode & octal(execPermission) ).toEqual(octal(execPermission)); } }, 200000); describe('--buildSystem=gradle-project', () => { - it('should create a gradle spring-boot project', async() => { + it.each` + projectType + ${'application'} + ${'library'} + `(`should create a gradle spring-boot '$projectType'`, async ({ projectType }) => { const prjName = uniq('nx-spring-boot'); + const prjDir = projectType === 'application' ? 'apps' : 'libs'; await runNxCommandAsync( - `generate @nxrocks/nx-spring-boot:new ${prjName} --projectType application --buildSystem gradle-project` + `generate @nxrocks/nx-spring-boot:new ${prjName} --projectType ${projectType} --buildSystem gradle-project` ); - const resultBuildInfo= await runNxCommandAsync(`buildInfo ${prjName}`); - expect(resultBuildInfo.stdout).toContain(`Executing command: ${isWin ? 'gradlew.bat' : './gradlew'} bootBuildInfo`) + const resultBuild= await runNxCommandAsync(`build ${prjName}`); + expect(resultBuild.stdout).toContain(`Executing command: ${isWin ? 'gradlew.bat' : './gradlew'} build`) expect(() => - checkFilesExist(`apps/${prjName}/gradlew`,`apps/${prjName}/build.gradle`, `apps/${prjName}/HELP.md`) + checkFilesExist(`${prjDir}/${prjName}/gradlew`,`${prjDir}/${prjName}/build.gradle`, `${prjDir}/${prjName}/HELP.md`) ).not.toThrow(); // make sure the build wrapper file is executable (*nix only) @@ -115,7 +125,7 @@ describe('nx-spring-boot e2e', () => { const execPermission = '755'; expect( lstatSync( - tmpProjPath(`apps/${prjName}/gradlew`) + tmpProjPath(`${prjDir}/${prjName}/gradlew`) ).mode & octal(execPermission) ).toEqual(octal(execPermission)); } @@ -124,18 +134,23 @@ describe('nx-spring-boot e2e', () => { }); describe('--buildSystem=gradle-project and --language=kotlin', () => { - it('should create a gradle spring-boot project with kotlin', async() => { + it.each` + projectType + ${'application'} + ${'library'} + `(`should create a gradle spring-boot '$projectType' with kotlin`, async ({ projectType }) => { const prjName = uniq('nx-spring-boot'); + const prjDir = projectType === 'application' ? 'apps' : 'libs'; await runNxCommandAsync( - `generate @nxrocks/nx-spring-boot:new ${prjName} --projectType application --buildSystem gradle-project --language kotlin` + `generate @nxrocks/nx-spring-boot:new ${prjName} --projectType ${projectType} --buildSystem gradle-project --language kotlin` ); - const resultBuildInfo= await runNxCommandAsync(`buildInfo ${prjName}`); - expect(resultBuildInfo.stdout).toContain(`Executing command: ${isWin ? 'gradlew.bat' : './gradlew'} bootBuildInfo`) + const resultBuild= await runNxCommandAsync(`build ${prjName}`); + expect(resultBuild.stdout).toContain(`Executing command: ${isWin ? 'gradlew.bat' : './gradlew'} build`) expect(() => - checkFilesExist(`apps/${prjName}/gradlew`,`apps/${prjName}/build.gradle.kts`, `apps/${prjName}/HELP.md`) + checkFilesExist(`${prjDir}/${prjName}/gradlew`,`${prjDir}/${prjName}/build.gradle.kts`, `${prjDir}/${prjName}/HELP.md`) ).not.toThrow(); // make sure the build wrapper file is executable (*nix only) @@ -143,7 +158,7 @@ describe('nx-spring-boot e2e', () => { const execPermission = '755'; expect( lstatSync( - tmpProjPath(`apps/${prjName}/gradlew`) + tmpProjPath(`${prjDir}/${prjName}/gradlew`) ).mode & octal(execPermission) ).toEqual(octal(execPermission)); } @@ -152,27 +167,37 @@ describe('nx-spring-boot e2e', () => { describe('--directory', () => { - it('should create src in the specified directory', async() => { + it.each` + projectType + ${'application'} + ${'library'} + `(`should create src in the specified directory when generating a '$projectType'`, async ({ projectType }) => { const prjName = uniq('nx-spring-boot'); + const prjDir = projectType === 'application' ? 'apps' : 'libs'; await runNxCommandAsync( - `generate @nxrocks/nx-spring-boot:new ${prjName} --projectType application --directory subdir` + `generate @nxrocks/nx-spring-boot:new ${prjName} --projectType ${projectType} --directory subdir` ); expect(() => - checkFilesExist(`apps/subdir/${prjName}/mvnw`,`apps/subdir/${prjName}/pom.xml`, `apps/subdir/${prjName}/HELP.md`) + checkFilesExist(`${prjDir}/subdir/${prjName}/mvnw`,`${prjDir}/subdir/${prjName}/pom.xml`, `${prjDir}/subdir/${prjName}/HELP.md`) ).not.toThrow(); }, 200000); }); describe('--tags', () => { - it('should add tags to nx.json', async() => { + it.each` + projectType + ${'application'} + ${'library'} + `(`should add tags to nx.json' when generating a '$projectType'`, async ({ projectType }) => { const prjName = uniq('nx-spring-boot'); + const prjDir = projectType === 'application' ? 'apps' : 'libs'; await runNxCommandAsync( - `generate @nxrocks/nx-spring-boot:new ${prjName} --projectType application --tags e2etag,e2ePackage` + `generate @nxrocks/nx-spring-boot:new ${prjName} --projectType ${projectType} --tags e2etag,e2ePackage` ); - const project = readJson(`apps/${prjName}/project.json`); + const project = readJson(`${prjDir}/${prjName}/project.json`); expect(project.tags).toEqual(['e2etag', 'e2ePackage']); }, 200000); diff --git a/packages/nx-spring-boot/README.md b/packages/nx-spring-boot/README.md index 831cd33b..3c68bcf5 100644 --- a/packages/nx-spring-boot/README.md +++ b/packages/nx-spring-boot/README.md @@ -149,6 +149,7 @@ Here the list of available executors: | `format` | `ignoreWrapper:boolean`, `args: string[]` | Format the project using [Spotless](https://github.com/diffplug/spotless) plugin for Maven or Gradle | | `format-check` | `ignoreWrapper:boolean`, `args: string[]` | Check whether the project is well formatted using [Spotless](https://github.com/diffplug/spotless) plugin for Maven or Gradle | | `build` | `ignoreWrapper:boolean`, `args: string[]` | Packages the project into an executable Jar using either `./mvnw\|mvn package` or `./gradlew\|gradle build` | +| `install` | `ignoreWrapper:boolean`, `args: string[]` | Installs the project's artifacts to local Maven repository (in `~/.m2/repository`) using either `./mvnw\|mvn install` or `./gradlew\|gradle publishToMavenLocal` | | `buildInfo`* | `ignoreWrapper:boolean`, | Generates a `build-info.properties` using either `./mvnw\|mvn spring-boot:build-info` or `./gradlew\|gradle bootBuildInfo` | | `buildImage`* | `ignoreWrapper:boolean`, `args: string[]` | Generates an [OCI Image](https://github.com/opencontainers/image-spec) using either `./mvnw\|mvn spring-boot:build-image` or `./gradlew\|gradle bootBuildImage` | @@ -197,6 +198,12 @@ You can pass in additional arguments by editing the related section in the `work nx build your-boot-app ``` +### Install the project's artifacts to local Maven repository (in ~/.m2/repository) - ('install' Executor) + +``` +nx install your-boot-app +``` + ### Building the OCI Image - ('buildImage' Executor) ``` diff --git a/packages/nx-spring-boot/executors.json b/packages/nx-spring-boot/executors.json index 54b49540..9e366110 100644 --- a/packages/nx-spring-boot/executors.json +++ b/packages/nx-spring-boot/executors.json @@ -25,6 +25,11 @@ "schema": "./src/executors/build/schema.json", "description": "Executor to build the project's Jar or War" }, + "install": { + "implementation": "./src/executors/install/executor", + "schema": "./src/executors/install/schema.json", + "description": "Executor to install the project's Jar to the local Maven repository" + }, "format": { "implementation": "./src/executors/format/executor", "schema": "./src/executors/format/schema.json", diff --git a/packages/nx-spring-boot/src/core/constants.ts b/packages/nx-spring-boot/src/core/constants.ts index 426d5fba..a7e2dd0c 100644 --- a/packages/nx-spring-boot/src/core/constants.ts +++ b/packages/nx-spring-boot/src/core/constants.ts @@ -5,6 +5,7 @@ export const GRADLE_BOOT_COMMAND_MAPPER : BuilderCommandAliasMapper = { 'test': 'test', 'clean': 'clean', 'build': 'build', + 'install': 'publishToMavenLocal', 'format': 'spotlessApply', 'format-check': 'spotlessCheck', 'buildImage': 'bootBuildImage', @@ -18,6 +19,7 @@ export const MAVEN_BOOT_COMMAND_MAPPER: BuilderCommandAliasMapper = { 'test': 'test', 'clean': 'clean', 'build': 'package', + 'install': 'install', 'format': 'spotless:apply', 'format-check': 'spotless:check', 'buildImage': 'spring-boot:build-image', diff --git a/packages/nx-spring-boot/src/executors/install/executor.spec.ts b/packages/nx-spring-boot/src/executors/install/executor.spec.ts new file mode 100644 index 00000000..6be56f25 --- /dev/null +++ b/packages/nx-spring-boot/src/executors/install/executor.spec.ts @@ -0,0 +1,47 @@ +import { logger } from '@nrwl/devkit'; +import { mocked } from 'jest-mock'; + +import { installExecutor } from './executor'; +import { InstallExecutorOptions } from './schema'; +import { GRADLE_WRAPPER_EXECUTABLE, MAVEN_WRAPPER_EXECUTABLE, NX_SPRING_BOOT_PKG } from '@nxrocks/common'; +import { expectExecutorCommandRanWith, mockExecutorContext } from '@nxrocks/common/testing'; + +//first, we mock +jest.mock('child_process'); +jest.mock('@nrwl/workspace/src/utils/fileutils'); + +//then, we import +import * as fsUtility from '@nrwl/workspace/src/utils/fileutils'; +import * as cp from 'child_process'; + +const mockContext = mockExecutorContext(NX_SPRING_BOOT_PKG, 'install'); +const options: InstallExecutorOptions = { + root: 'apps/bootapp' +}; + +describe('Install Executor', () => { + + beforeEach(async () => { + jest.spyOn(logger, 'info'); + jest.spyOn(cp, 'execSync'); + }); + + afterEach(() => { + jest.resetAllMocks(); + }); + + it.each` + ignoreWrapper | buildSystem | buildFile | execute + ${true} | ${'maven'} | ${'pom.xml'} | ${'mvn install '} + ${true} | ${'gradle'} | ${'build.gradle'} | ${'gradle publishToMavenLocal '} + ${false} | ${'maven'} | ${'pom.xml'} | ${MAVEN_WRAPPER_EXECUTABLE + ' install '} + ${false} | ${'gradle'} | ${'build.gradle'} | ${GRADLE_WRAPPER_EXECUTABLE + ' publishToMavenLocal '} + `('should execute a $buildSystem build and ignoring wrapper : $ignoreWrapper', async ({ ignoreWrapper, buildFile, execute }) => { + mocked(fsUtility.fileExists).mockImplementation((filePath: string) => filePath.indexOf(buildFile) !== -1); + + await installExecutor({ ...options, ignoreWrapper }, mockContext); + + expectExecutorCommandRanWith(execute, mockContext, options); + }); + +}); diff --git a/packages/nx-spring-boot/src/executors/install/executor.ts b/packages/nx-spring-boot/src/executors/install/executor.ts new file mode 100644 index 00000000..34bae0b0 --- /dev/null +++ b/packages/nx-spring-boot/src/executors/install/executor.ts @@ -0,0 +1,11 @@ +import { ExecutorContext } from '@nrwl/devkit' +import * as path from 'path' +import { InstallExecutorOptions } from './schema' +import { runBootPluginCommand } from '../../utils/boot-utils' + +export async function installExecutor(options: InstallExecutorOptions, context: ExecutorContext){ + const root = path.resolve(context.root, options.root); + return runBootPluginCommand('install', options.args, { cwd : root, ignoreWrapper: options.ignoreWrapper}); +} + +export default installExecutor; \ No newline at end of file diff --git a/packages/nx-spring-boot/src/executors/install/schema.d.ts b/packages/nx-spring-boot/src/executors/install/schema.d.ts new file mode 100644 index 00000000..b7501ee8 --- /dev/null +++ b/packages/nx-spring-boot/src/executors/install/schema.d.ts @@ -0,0 +1,6 @@ + +export interface InstallExecutorOptions { + root: string; + ignoreWrapper?: boolean; + args?: string[]; +} \ No newline at end of file diff --git a/packages/nx-spring-boot/src/executors/install/schema.json b/packages/nx-spring-boot/src/executors/install/schema.json new file mode 100644 index 00000000..eecb0ce7 --- /dev/null +++ b/packages/nx-spring-boot/src/executors/install/schema.json @@ -0,0 +1,24 @@ +{ + "$schema": "http://json-schema.org/schema", + "title": "Install executor", + "description": "", + "cli": "nx", + "type": "object", + "properties": { + "root": { + "description": "The project root", + "type": "string" + }, + "ignoreWrapper": { + "description": "Whether or not to use the embedded wrapper (`mvnw`or `gradlew`) to perfom build operations", + "type": "boolean", + "default": false + }, + "args": { + "description": "The argument to be passed to the underlying Spring Boot command", + "type": "array", + "default": [] + } + }, + "required": [] +} \ No newline at end of file diff --git a/packages/nx-spring-boot/src/generators/project/generator.spec.ts b/packages/nx-spring-boot/src/generators/project/generator.spec.ts index 206debea..5e668176 100644 --- a/packages/nx-spring-boot/src/generators/project/generator.spec.ts +++ b/packages/nx-spring-boot/src/generators/project/generator.spec.ts @@ -140,13 +140,15 @@ describe('project generator', () => { expect(logger.info).toHaveBeenNthCalledWith(2, `Extracting Spring Boot project zip to '${workspaceRoot}/${rootDir}/${options.name}'...`); + if (buildSystem === 'gradle-project') { - + if (projectType === 'library') { - expect(logger.debug).toHaveBeenCalledWith(`Disabling 'spring-boot' gradle plugin on a library project...`); + expect(logger.debug).toHaveBeenNthCalledWith(1,`Disabling 'spring-boot' gradle plugin on a library project...`); } else { - expect(logger.debug).toHaveBeenCalledWith(`Adding 'buildInfo' task to the build.gradle file...`); + expect(logger.debug).toHaveBeenNthCalledWith(1,`Adding 'buildInfo' task to the build.gradle file...`); } + expect(logger.debug).toHaveBeenNthCalledWith(2,`Adding 'maven-publish' plugin...`); } if (buildSystem === 'maven-project' && projectType === 'library') { @@ -171,16 +173,16 @@ describe('project generator', () => { const projectDir = projectType === 'application' ? 'apps' : 'libs'; expect(project.root).toBe(`${projectDir}/${options.name}`); - const commands = ['build', 'format', 'format-check', 'test', 'clean'] - const bootOnlyCommands = ['run', 'serve', 'buildImage', 'buildInfo']; + const commands = ['build', 'install', 'format', 'format-check', 'test', 'clean'] + const appOnlyCommands = ['run', 'serve', 'buildImage', 'buildInfo']; if (projectType === 'application') { - commands.push(...bootOnlyCommands); + commands.push(...appOnlyCommands); } commands.forEach(cmd => { expect(project.targets[cmd].executor).toBe(`${NX_SPRING_BOOT_PKG}:${cmd}`); - if(cmd === 'build') { + if(['build', 'install'].includes(cmd)) { expect(project.targets[cmd].outputs).toEqual([`${project.root}/target`]); } }); diff --git a/packages/nx-spring-boot/src/generators/project/generator.ts b/packages/nx-spring-boot/src/generators/project/generator.ts index bbdeeff0..23f1b42f 100644 --- a/packages/nx-spring-boot/src/generators/project/generator.ts +++ b/packages/nx-spring-boot/src/generators/project/generator.ts @@ -4,17 +4,18 @@ import { normalizeOptions, generateBootProject, addBuilInfoTask, removeBootMaven import { addPluginToNxJson, NX_SPRING_BOOT_PKG } from '@nxrocks/common'; import { addFormattingWithSpotless } from './lib/add-formatting-with-spotless'; import { disableBootGradlePlugin } from './lib/disable-boot-gradle-plugin'; +import { addMavenPublishPlugin } from './lib/add-maven-publish-plugin'; export async function projectGenerator(tree: Tree, options: ProjectGeneratorOptions) { const normalizedOptions = normalizeOptions(tree, options); const targets = {}; - const commands = ['build', 'test', 'clean']; - const bootOnlyCommands = ['run', 'serve', 'buildImage', 'buildInfo']; + const commands = ['build', 'install', 'test', 'clean']; + const appOnlyCommands = ['run', 'serve', 'buildImage', 'buildInfo']; if (options.projectType === 'application') { //only 'application' projects should have 'boot' related commands - commands.push(...bootOnlyCommands); + commands.push(...appOnlyCommands); } if(!options.skipFormat) { @@ -27,7 +28,8 @@ export async function projectGenerator(tree: Tree, options: ProjectGeneratorOpti options: { root: normalizedOptions.projectRoot }, - ...( command == 'build' ? {outputs: [`${normalizedOptions.projectRoot}/${normalizedOptions.buildSystem === 'maven-project' ? 'target' : 'build'}`]}: {}) + ...(command === 'build' ? { dependsOn: ['^înstall'] } : {}), + ...( ['build', 'install'].includes(command) ? {outputs: [`${normalizedOptions.projectRoot}/${normalizedOptions.buildSystem === 'maven-project' ? 'target' : 'build'}`]}: {}) }; } addProjectConfiguration(tree, normalizedOptions.projectName, { @@ -51,6 +53,8 @@ export async function projectGenerator(tree: Tree, options: ProjectGeneratorOpti } } + addMavenPublishPlugin(tree, normalizedOptions); + if(!options.skipFormat) { //if skipFormat is true, then we don't want to add Spotless plugin addFormattingWithSpotless(tree, normalizedOptions); } diff --git a/packages/nx-spring-boot/src/generators/project/lib/add-maven-publish-plugin.ts b/packages/nx-spring-boot/src/generators/project/lib/add-maven-publish-plugin.ts new file mode 100644 index 00000000..79aea413 --- /dev/null +++ b/packages/nx-spring-boot/src/generators/project/lib/add-maven-publish-plugin.ts @@ -0,0 +1,40 @@ +import { + logger, + Tree +} from '@nrwl/devkit'; +import { addGradlePlugin, stripIndent } from '@nxrocks/common'; +import { NormalizedSchema } from '../schema'; + +export function addMavenPublishPlugin(tree: Tree, options: NormalizedSchema) { + if (options.buildSystem === 'gradle-project') { + logger.debug(`Adding 'maven-publish' plugin...`); + + addGradlePlugin(tree, options.projectRoot, options.language, 'maven-publish'); + + const artifactSource = options.projectType === 'application' ? 'bootJar' : 'jar'; + const publishing = options.language === 'kotlin' ? + stripIndent` + publishing { + publications { + create("mavenJava") { + artifact(tasks.getByName("${artifactSource}")) + } + } + } + ` + : + stripIndent` + publishing { + publications { + mavenJava(MavenPublication) { + artifact ${artifactSource} + } + } + } + `; + const ext = options.language === 'kotlin' ? '.kts' : ''; + const buildGradlePath = `${options.projectRoot}/build.gradle${ext}`; + const content = tree.read(buildGradlePath, 'utf-8') + '\n' + publishing; + tree.write(buildGradlePath, content); + } +}