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);
+ }
+}