diff --git a/.github/workflows/e2e-plugin.yml b/.github/workflows/e2e-plugin.yml index 1697af92f..485bc8628 100644 --- a/.github/workflows/e2e-plugin.yml +++ b/.github/workflows/e2e-plugin.yml @@ -18,6 +18,8 @@ on: - nx-gradle quarkus kotlin dsl e2e - nx-gradle spring-boot e2e - nx-gradle spring-boot kotlin dsl e2e + - nx-gradle spring-boot-4 e2e + - nx-gradle spring-boot-4 kotlin dsl e2e - graph should works with optional project.json - should use specified options to create an sb application - should create a sb java application @@ -30,6 +32,7 @@ on: - nx-maven quarkus bom e2e - nx-maven spring-boot bom e2e - nx-maven spring-boot-parent-pom e2e + - nx-maven spring-boot-4 parent-pom e2e - should use specified options and hyphen in groupId to create an application - should --aggregator-project option works and generate java nested sub-projects - should generate java apps that use a parent project diff --git a/packages/common/src/lib/types/index.ts b/packages/common/src/lib/types/index.ts index 0a66fbe95..abbd07e02 100644 --- a/packages/common/src/lib/types/index.ts +++ b/packages/common/src/lib/types/index.ts @@ -9,12 +9,24 @@ export type CustomCli = | 'create-nx-maven-workspace' | 'create-nx-gradle-workspace'; export type TargetsType = Record; -export type FrameworkType = 'spring-boot' | 'quarkus' | 'micronaut' | 'none'; -export type PresetType = 'spring-boot' | 'quarkus' | 'micronaut' | 'none'; +export type FrameworkType = + | 'spring-boot' + | 'spring-boot-4' + | 'quarkus' + | 'micronaut' + | 'none'; +export type PresetType = + | 'spring-boot' + | 'spring-boot-4' + | 'quarkus' + | 'micronaut' + | 'none'; export type DependencyManagementType = | 'none' | 'spring-boot-parent-pom' | 'spring-boot-bom' + | 'spring-boot-4-parent-pom' + | 'spring-boot-4-bom' | 'quarkus-bom' | 'micronaut-parent-pom' | 'micronaut-bom'; diff --git a/packages/common/src/lib/versions/index.ts b/packages/common/src/lib/versions/index.ts index bcf0c1e5a..8692fcf11 100644 --- a/packages/common/src/lib/versions/index.ts +++ b/packages/common/src/lib/versions/index.ts @@ -1,10 +1,14 @@ //Kotlin export const kotlinVersion = '1.9.21'; -//Spring boot +//Spring boot 3 export const springBootVersion = '3.2.0'; export const springDependencyManagementVersion = '1.1.4'; +//Spring boot 4 +export const springBoot4Version = '4.0.0'; +export const springDependencyManagement4Version = '1.1.7'; + //Quarkus export const quarkusVersion = '3.5.3'; diff --git a/packages/internal/generators-files/spring-boot-4/application/gradle/java/build.gradle__kotlinExtension__ b/packages/internal/generators-files/spring-boot-4/application/gradle/java/build.gradle__kotlinExtension__ new file mode 100644 index 000000000..92c3c9ecf --- /dev/null +++ b/packages/internal/generators-files/spring-boot-4/application/gradle/java/build.gradle__kotlinExtension__ @@ -0,0 +1,139 @@ +<% if(versionManagement === 'properties') { -%> +<% if(dsl === 'groovy') { -%> +plugins { + id 'java' +<% if(packaging === 'war') { -%> + id 'war' +<% } -%> + id 'org.springframework.boot' + id 'io.spring.dependency-management' +} + +group = '<%= groupId %>' +version = '<%= projectVersion %>' + +java { + sourceCompatibility = "${javaVersion}" +} + +repositories { + mavenCentral() +} + +dependencies { + implementation 'org.springframework.boot:spring-boot-starter-web' +<% if(packaging === 'war') { -%> + providedRuntime 'org.springframework.boot:spring-boot-starter-tomcat' +<% } -%> + testImplementation 'org.springframework.boot:spring-boot-starter-test' +} + +tasks.named('test') { + useJUnitPlatform() +} +<% } -%> +<% if(dsl === 'kotlin') { -%> +val javaVersion: String by project + +plugins { + java +<% if(packaging === 'war') { -%> + war +<% } -%> + id("org.springframework.boot") + id("io.spring.dependency-management") +} + +group = "<%= groupId %>" +version = "<%= projectVersion %>" + +java { + sourceCompatibility = JavaVersion.toVersion(javaVersion) +} + +repositories { + mavenCentral() +} + +dependencies { + implementation("org.springframework.boot:spring-boot-starter-web") +<% if(packaging === 'war') { -%> + providedRuntime("org.springframework.boot:spring-boot-starter-tomcat") +<% } -%> + testImplementation("org.springframework.boot:spring-boot-starter-test") +} + +tasks.withType { + useJUnitPlatform() +} +<% } -%> +<% } -%> +<% if(versionManagement === 'version-catalog') { -%> +<% if(dsl === 'groovy') { -%> +plugins { + id 'java' +<% if(packaging === 'war') { -%> + id 'war' +<% } -%> + alias libs.plugins.springframework.boot + alias libs.plugins.spring.dependency.management +} + +group = '<%= groupId %>' +version = '<%= projectVersion %>' + +java { + sourceCompatibility = libs.versions.java.get() +} + +repositories { + mavenCentral() +} + +dependencies { + implementation 'org.springframework.boot:spring-boot-starter-web' +<% if(packaging === 'war') { -%> + providedRuntime 'org.springframework.boot:spring-boot-starter-tomcat' +<% } -%> + testImplementation 'org.springframework.boot:spring-boot-starter-test' +} + +tasks.named('test') { + useJUnitPlatform() +} +<% } -%> +<% if(dsl === 'kotlin') { -%> +plugins { + java +<% if(packaging === 'war') { -%> + war +<% } -%> + alias(libs.plugins.springframework.boot) + alias(libs.plugins.spring.dependency.management) +} + +group = "<%= groupId %>" +version = "<%= projectVersion %>" + +java { + sourceCompatibility = JavaVersion.toVersion(libs.versions.java.get()) +} + +repositories { + mavenCentral() +} + +dependencies { + implementation("org.springframework.boot:spring-boot-starter-web") +<% if(packaging === 'war') { -%> + providedRuntime("org.springframework.boot:spring-boot-starter-tomcat") +<% } -%> + testImplementation("org.springframework.boot:spring-boot-starter-test") +} + +tasks.withType { + useJUnitPlatform() +} +<% } -%> +<% } -%> + diff --git a/packages/internal/generators-files/spring-boot-4/application/gradle/kotlin/build.gradle__kotlinExtension__ b/packages/internal/generators-files/spring-boot-4/application/gradle/kotlin/build.gradle__kotlinExtension__ new file mode 100644 index 000000000..436a69b87 --- /dev/null +++ b/packages/internal/generators-files/spring-boot-4/application/gradle/kotlin/build.gradle__kotlinExtension__ @@ -0,0 +1,190 @@ +<% if(versionManagement === 'properties') { -%> +<% if(dsl === 'groovy') { -%> +import org.jetbrains.kotlin.gradle.tasks.KotlinCompile + +plugins { +<% if(packaging === 'war') { -%> + id 'war' +<% } -%> + id 'org.springframework.boot' + id 'io.spring.dependency-management' + id 'org.jetbrains.kotlin.jvm' + id 'org.jetbrains.kotlin.plugin.spring' +} + +group = '<%= groupId %>' +version = '<%= projectVersion %>' + +java { + sourceCompatibility = "${javaVersion}" +} + +repositories { + mavenCentral() +} + +dependencies { + implementation 'org.springframework.boot:spring-boot-starter-web' + implementation 'com.fasterxml.jackson.module:jackson-module-kotlin' + implementation 'org.jetbrains.kotlin:kotlin-reflect' + implementation 'org.jetbrains.kotlin:kotlin-stdlib-jdk8' +<% if(packaging === 'war') { -%> + providedRuntime 'org.springframework.boot:spring-boot-starter-tomcat' +<% } -%> + testImplementation 'org.springframework.boot:spring-boot-starter-test' +} + +tasks.withType(KotlinCompile) { + kotlinOptions { + freeCompilerArgs += '-Xjsr305=strict' + jvmTarget = "${javaVersion}" + } +} + +tasks.named('test') { + useJUnitPlatform() +} +<% } -%> +<% if(dsl === 'kotlin') { -%> +import org.jetbrains.kotlin.gradle.tasks.KotlinCompile + +val javaVersion: String by project + +plugins { +<% if(packaging === 'war') { -%> + war +<% } -%> + id("org.springframework.boot") + id("io.spring.dependency-management") + kotlin("jvm") + kotlin("plugin.spring") +} + +group = "<%= groupId %>" +version = "<%= projectVersion %>" + +java { + sourceCompatibility = JavaVersion.toVersion(javaVersion) +} + +repositories { + mavenCentral() +} + +dependencies { + implementation("org.springframework.boot:spring-boot-starter-web") + implementation("com.fasterxml.jackson.module:jackson-module-kotlin") + implementation("org.jetbrains.kotlin:kotlin-reflect") + implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk8") +<% if(packaging === 'war') { -%> + providedRuntime("org.springframework.boot:spring-boot-starter-tomcat") +<% } -%> + testImplementation("org.springframework.boot:spring-boot-starter-test") +} + +tasks.withType { + kotlinOptions { + freeCompilerArgs += "-Xjsr305=strict" + jvmTarget = javaVersion + } +} + +tasks.withType { + useJUnitPlatform() +} +<% } -%> +<% } -%> +<% if(versionManagement === 'version-catalog') { -%> +<% if(dsl === 'groovy') { -%> +import org.jetbrains.kotlin.gradle.tasks.KotlinCompile + +plugins { +<% if(packaging === 'war') { -%> + id 'war' +<% } -%> + alias libs.plugins.springframework.boot + alias libs.plugins.spring.dependency.management + alias libs.plugins.jetbrains.kotlin.jvm + alias libs.plugins.jetbrains.kotlin.plugin.spring +} + +group = '<%= groupId %>' +version = '<%= projectVersion %>' + +java { + sourceCompatibility = libs.versions.java.get() +} + +repositories { + mavenCentral() +} + +dependencies { + implementation 'org.springframework.boot:spring-boot-starter-web' + implementation 'com.fasterxml.jackson.module:jackson-module-kotlin' + implementation 'org.jetbrains.kotlin:kotlin-reflect' + implementation 'org.jetbrains.kotlin:kotlin-stdlib-jdk8' +<% if(packaging === 'war') { -%> + providedRuntime 'org.springframework.boot:spring-boot-starter-tomcat' +<% } -%> + testImplementation 'org.springframework.boot:spring-boot-starter-test' +} + +tasks.withType(KotlinCompile) { + kotlinOptions { + freeCompilerArgs += '-Xjsr305=strict' + jvmTarget = libs.versions.java.get() + } +} + +tasks.named('test') { + useJUnitPlatform() +} +<% } -%> +<% if(dsl === 'kotlin') { -%> +import org.jetbrains.kotlin.gradle.tasks.KotlinCompile + +plugins { +<% if(packaging === 'war') { -%> + war +<% } -%> + alias(libs.plugins.springframework.boot) + alias(libs.plugins.spring.dependency.management) + alias(libs.plugins.jetbrains.kotlin.jvm) + alias(libs.plugins.jetbrains.kotlin.plugin.spring) +} + +group = "<%= groupId %>" +version = "<%= projectVersion %>" + +java { + sourceCompatibility = JavaVersion.toVersion(libs.versions.java.get()) +} + +repositories { + mavenCentral() +} + +dependencies { + implementation("org.springframework.boot:spring-boot-starter-web") + implementation("com.fasterxml.jackson.module:jackson-module-kotlin") + implementation("org.jetbrains.kotlin:kotlin-reflect") + implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk8") +<% if(packaging === 'war') { -%> + providedRuntime("org.springframework.boot:spring-boot-starter-tomcat") +<% } -%> + testImplementation("org.springframework.boot:spring-boot-starter-test") +} + +tasks.withType { + kotlinOptions { + freeCompilerArgs += "-Xjsr305=strict" + jvmTarget = libs.versions.java.get() + } +} + +tasks.withType { + useJUnitPlatform() +} +<% } -%> +<% } -%> diff --git a/packages/internal/generators-files/spring-boot-4/application/maven/java/pom.xml__template__ b/packages/internal/generators-files/spring-boot-4/application/maven/java/pom.xml__template__ new file mode 100644 index 000000000..5d59f4eae --- /dev/null +++ b/packages/internal/generators-files/spring-boot-4/application/maven/java/pom.xml__template__ @@ -0,0 +1,51 @@ + + + 4.0.0 + <%= groupId %> + <%= projectName %> + <%= projectVersion %> +<% if(packaging === 'war') { -%> + war +<% } -%> + <%= projectName %> + This project was generated with nx-maven + +<% if(parentProjectName) { -%> + + <%= parentGroupId %> + <%= parentProjectName %> + <%= parentProjectVersion %> + <%= relativePath %> + +<% } -%> + + + + org.springframework.boot + spring-boot-starter-web + +<% if(packaging === 'war') { -%> + + org.springframework.boot + spring-boot-starter-tomcat + provided + +<% } -%> + + org.springframework.boot + spring-boot-starter-test + test + + + + + + + org.springframework.boot + spring-boot-maven-plugin + + + + + diff --git a/packages/internal/generators-files/spring-boot-4/application/maven/kotlin/pom.xml__template__ b/packages/internal/generators-files/spring-boot-4/application/maven/kotlin/pom.xml__template__ new file mode 100644 index 000000000..831a16e13 --- /dev/null +++ b/packages/internal/generators-files/spring-boot-4/application/maven/kotlin/pom.xml__template__ @@ -0,0 +1,80 @@ + + + 4.0.0 + <%= groupId %> + <%= projectName %> + <%= projectVersion %> +<% if(packaging === 'war') { -%> + war +<% } -%> + <%= projectName %> + This project was generated with nx-maven + +<% if(parentProjectName) { -%> + + <%= parentGroupId %> + <%= parentProjectName %> + <%= parentProjectVersion %> + <%= relativePath %> + +<% } -%> + + + + org.springframework.boot + spring-boot-starter-web + + + org.jetbrains.kotlin + kotlin-reflect + + + org.jetbrains.kotlin + kotlin-stdlib-jdk8 + +<% if(packaging === 'war') { -%> + + org.springframework.boot + spring-boot-starter-tomcat + provided + + <% } -%> + + org.springframework.boot + spring-boot-starter-test + test + + + + + ${project.basedir}/src/main/kotlin + ${project.basedir}/src/test/kotlin + + + org.springframework.boot + spring-boot-maven-plugin + + + org.jetbrains.kotlin + kotlin-maven-plugin + + + -Xjsr305=strict + + + spring + + + + + org.jetbrains.kotlin + kotlin-maven-allopen + ${kotlin.version} + + + + + + + diff --git a/packages/internal/generators-files/spring-boot-4/application/shared/java/src/main/java/__packageDirectory__/HelloController.java b/packages/internal/generators-files/spring-boot-4/application/shared/java/src/main/java/__packageDirectory__/HelloController.java new file mode 100644 index 000000000..36ddf6f8a --- /dev/null +++ b/packages/internal/generators-files/spring-boot-4/application/shared/java/src/main/java/__packageDirectory__/HelloController.java @@ -0,0 +1,13 @@ +package <%= packageName %>; + +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RestController; + +@RestController +public class HelloController { + + @GetMapping("/") + public String greeting() { + return "Hello World!"; + } +} diff --git a/packages/internal/generators-files/spring-boot-4/application/shared/java/src/main/java/__packageDirectory__/ServletInitializer.java b/packages/internal/generators-files/spring-boot-4/application/shared/java/src/main/java/__packageDirectory__/ServletInitializer.java new file mode 100644 index 000000000..00e8d6246 --- /dev/null +++ b/packages/internal/generators-files/spring-boot-4/application/shared/java/src/main/java/__packageDirectory__/ServletInitializer.java @@ -0,0 +1,13 @@ +package <%= packageName %>; + +import org.springframework.boot.builder.SpringApplicationBuilder; +import org.springframework.boot.web.servlet.support.SpringBootServletInitializer; + +public class ServletInitializer extends SpringBootServletInitializer { + + @Override + protected SpringApplicationBuilder configure(SpringApplicationBuilder application) { + return application.sources(<%= appClassName %>.class); + } + +} diff --git a/packages/internal/generators-files/spring-boot-4/application/shared/java/src/main/java/__packageDirectory__/__appClassName__.java b/packages/internal/generators-files/spring-boot-4/application/shared/java/src/main/java/__packageDirectory__/__appClassName__.java new file mode 100644 index 000000000..046ead25b --- /dev/null +++ b/packages/internal/generators-files/spring-boot-4/application/shared/java/src/main/java/__packageDirectory__/__appClassName__.java @@ -0,0 +1,13 @@ +package <%= packageName %>; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +@SpringBootApplication<% if(!minimal) { %>(scanBasePackages = "<%= basePackage %>")<% } %> +public class <%= appClassName %> { + + public static void main(String[] args) { + SpringApplication.run(<%= appClassName %>.class, args); + } + +} diff --git a/packages/internal/generators-files/spring-boot-4/application/shared/java/src/main/resources/application__configFormat__ b/packages/internal/generators-files/spring-boot-4/application/shared/java/src/main/resources/application__configFormat__ new file mode 100644 index 000000000..7e9edbf03 --- /dev/null +++ b/packages/internal/generators-files/spring-boot-4/application/shared/java/src/main/resources/application__configFormat__ @@ -0,0 +1,15 @@ +<% if(configFormat === '.properties') { -%> +spring.application.name=<%= projectName %> +<% if(isCustomPort) { -%> +server.port=<%= port %> +<% } -%> +<% } -%> +<% if(configFormat === '.yml') { -%> +spring: + application: + name: <%= projectName %> +<% if(isCustomPort) { -%> +server: + port : <%= port %> +<% } -%> +<% } -%> diff --git a/packages/internal/generators-files/spring-boot-4/application/shared/java/src/test/java/__packageDirectory__/HelloControllerTests.java b/packages/internal/generators-files/spring-boot-4/application/shared/java/src/test/java/__packageDirectory__/HelloControllerTests.java new file mode 100644 index 000000000..a9f74d554 --- /dev/null +++ b/packages/internal/generators-files/spring-boot-4/application/shared/java/src/test/java/__packageDirectory__/HelloControllerTests.java @@ -0,0 +1,29 @@ +package <%= packageName %>; + +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.web.servlet.MockMvc; + +import static org.hamcrest.Matchers.containsString; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; +import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +@SpringBootTest +@AutoConfigureMockMvc +public class HelloControllerTests { + + @Autowired + private MockMvc mockMvc; + + @Test + public void shouldReturnHelloWorld() throws Exception { + + this.mockMvc.perform(get("/")).andDo(print()).andExpect(status().isOk()) + .andExpect(content().string(containsString("Hello World"))); + } + +} diff --git a/packages/internal/generators-files/spring-boot-4/application/shared/java/src/test/java/__packageDirectory__/__appClassName__Tests.java b/packages/internal/generators-files/spring-boot-4/application/shared/java/src/test/java/__packageDirectory__/__appClassName__Tests.java new file mode 100644 index 000000000..e797ed78d --- /dev/null +++ b/packages/internal/generators-files/spring-boot-4/application/shared/java/src/test/java/__packageDirectory__/__appClassName__Tests.java @@ -0,0 +1,14 @@ +package <%= packageName %>; + +import org.junit.jupiter.api.Test; +import org.springframework.boot.test.context.SpringBootTest; + +@SpringBootTest +class <%= appClassName %>Tests { + + @Test + void contextLoads() { + } + +} + diff --git a/packages/internal/generators-files/spring-boot-4/application/shared/java/src/test/resources/application__configFormat__ b/packages/internal/generators-files/spring-boot-4/application/shared/java/src/test/resources/application__configFormat__ new file mode 100644 index 000000000..8b1378917 --- /dev/null +++ b/packages/internal/generators-files/spring-boot-4/application/shared/java/src/test/resources/application__configFormat__ @@ -0,0 +1 @@ + diff --git a/packages/internal/generators-files/spring-boot-4/application/shared/kotlin/src/main/kotlin/__packageDirectory__/HelloController.kt b/packages/internal/generators-files/spring-boot-4/application/shared/kotlin/src/main/kotlin/__packageDirectory__/HelloController.kt new file mode 100644 index 000000000..32a683303 --- /dev/null +++ b/packages/internal/generators-files/spring-boot-4/application/shared/kotlin/src/main/kotlin/__packageDirectory__/HelloController.kt @@ -0,0 +1,12 @@ +package <%= packageName %> + +import org.springframework.web.bind.annotation.GetMapping +import org.springframework.web.bind.annotation.RestController + +@RestController +class HelloController { + + @GetMapping("/") + fun greeting():String = "Hello World!" + +} diff --git a/packages/internal/generators-files/spring-boot-4/application/shared/kotlin/src/main/kotlin/__packageDirectory__/ServletInitializer.kt b/packages/internal/generators-files/spring-boot-4/application/shared/kotlin/src/main/kotlin/__packageDirectory__/ServletInitializer.kt new file mode 100644 index 000000000..e6d2f8843 --- /dev/null +++ b/packages/internal/generators-files/spring-boot-4/application/shared/kotlin/src/main/kotlin/__packageDirectory__/ServletInitializer.kt @@ -0,0 +1,12 @@ +package <%= packageName %> + +import org.springframework.boot.builder.SpringApplicationBuilder +import org.springframework.boot.web.servlet.support.SpringBootServletInitializer + +class ServletInitializer : SpringBootServletInitializer() { + + override fun configure(application: SpringApplicationBuilder): SpringApplicationBuilder { + return application.sources(<%= appClassName %>::class.java) + } + +} diff --git a/packages/internal/generators-files/spring-boot-4/application/shared/kotlin/src/main/kotlin/__packageDirectory__/__appClassName__.kt b/packages/internal/generators-files/spring-boot-4/application/shared/kotlin/src/main/kotlin/__packageDirectory__/__appClassName__.kt new file mode 100644 index 000000000..f0a377288 --- /dev/null +++ b/packages/internal/generators-files/spring-boot-4/application/shared/kotlin/src/main/kotlin/__packageDirectory__/__appClassName__.kt @@ -0,0 +1,13 @@ +package <%= packageName %> + +import org.springframework.boot.autoconfigure.SpringBootApplication +import org.springframework.boot.runApplication + +@SpringBootApplication<% if(!minimal) { %>(scanBasePackages = arrayOf("<%= basePackage %>"))<% } %> +class <%= appClassName %> + +fun main(args: Array) { + runApplication<<%= appClassName %>>(*args) +} + + diff --git a/packages/internal/generators-files/spring-boot-4/application/shared/kotlin/src/main/resources/application__configFormat__ b/packages/internal/generators-files/spring-boot-4/application/shared/kotlin/src/main/resources/application__configFormat__ new file mode 100644 index 000000000..febc100d7 --- /dev/null +++ b/packages/internal/generators-files/spring-boot-4/application/shared/kotlin/src/main/resources/application__configFormat__ @@ -0,0 +1,8 @@ +<% if(configFormat === '.properties' && isCustomPort) { -%> +server.port=<%= port %> +<% } -%> +<% if(configFormat === '.yml' && isCustomPort) { -%> +server: + port : <%= port %> +<% } -%> + diff --git a/packages/internal/generators-files/spring-boot-4/application/shared/kotlin/src/test/kotlin/__packageDirectory__/HelloControllerTests.kt b/packages/internal/generators-files/spring-boot-4/application/shared/kotlin/src/test/kotlin/__packageDirectory__/HelloControllerTests.kt new file mode 100644 index 000000000..61ad0aaad --- /dev/null +++ b/packages/internal/generators-files/spring-boot-4/application/shared/kotlin/src/test/kotlin/__packageDirectory__/HelloControllerTests.kt @@ -0,0 +1,37 @@ +package <%= packageName %> + +import org.assertj.core.api.Assertions.assertThat +import org.junit.jupiter.api.AfterAll +import org.junit.jupiter.api.BeforeAll +import org.junit.jupiter.api.Test +import org.springframework.beans.factory.annotation.Autowired +import org.springframework.boot.test.context.SpringBootTest +import org.springframework.boot.test.resttestclient.AutoConfigureTestRestTemplate +import org.springframework.boot.test.web.client.TestRestTemplate +import org.springframework.boot.test.web.client.getForEntity +import org.springframework.http.HttpStatus + +@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) +@AutoConfigureTestRestTemplate +class HelloControllerTests(@Autowired val restTemplate: TestRestTemplate) { + + @BeforeAll + fun setup() { + println(">> Setup") + } + + @Test + fun `Should return Hello World`() { + println(">> Should return Hello World") + val entity = restTemplate.getForEntity("/") + assertThat(entity.statusCode).isEqualTo(HttpStatus.OK) + assertThat(entity.body).contains("Hello World") + } + + + @AfterAll + fun teardown() { + println(">> Tear down") + } + +} diff --git a/packages/internal/generators-files/spring-boot-4/application/shared/kotlin/src/test/kotlin/__packageDirectory__/__appClassName__Tests.kt b/packages/internal/generators-files/spring-boot-4/application/shared/kotlin/src/test/kotlin/__packageDirectory__/__appClassName__Tests.kt new file mode 100644 index 000000000..eaddf967a --- /dev/null +++ b/packages/internal/generators-files/spring-boot-4/application/shared/kotlin/src/test/kotlin/__packageDirectory__/__appClassName__Tests.kt @@ -0,0 +1,13 @@ +package <%= packageName %> + +import org.junit.jupiter.api.Test +import org.springframework.boot.test.context.SpringBootTest + +@SpringBootTest +class <%= appClassName %>Tests { + + @Test + fun contextLoads() { + } + +} diff --git a/packages/internal/generators-files/spring-boot-4/application/shared/kotlin/src/test/resources/application__configFormat__ b/packages/internal/generators-files/spring-boot-4/application/shared/kotlin/src/test/resources/application__configFormat__ new file mode 100644 index 000000000..8b1378917 --- /dev/null +++ b/packages/internal/generators-files/spring-boot-4/application/shared/kotlin/src/test/resources/application__configFormat__ @@ -0,0 +1 @@ + diff --git a/packages/internal/generators-files/spring-boot-4/application/shared/kotlin/src/test/resources/junit-platform.properties b/packages/internal/generators-files/spring-boot-4/application/shared/kotlin/src/test/resources/junit-platform.properties new file mode 100644 index 000000000..d265fd838 --- /dev/null +++ b/packages/internal/generators-files/spring-boot-4/application/shared/kotlin/src/test/resources/junit-platform.properties @@ -0,0 +1 @@ +junit.jupiter.testinstance.lifecycle.default = per_class diff --git a/packages/internal/generators-files/spring-boot-4/library/gradle/java/build.gradle__kotlinExtension__ b/packages/internal/generators-files/spring-boot-4/library/gradle/java/build.gradle__kotlinExtension__ new file mode 100644 index 000000000..5d2b51f1f --- /dev/null +++ b/packages/internal/generators-files/spring-boot-4/library/gradle/java/build.gradle__kotlinExtension__ @@ -0,0 +1,138 @@ +<% if(versionManagement === 'properties') { -%> +<% if(dsl === 'groovy') { -%> +plugins { + id 'java' + id 'org.springframework.boot' apply false + id 'io.spring.dependency-management' +} + +group = '<%= groupId %>' +version = '<%= projectVersion %>' + +java { + sourceCompatibility = "${javaVersion}" +} + +repositories { + mavenCentral() +} + +dependencyManagement { + imports { + mavenBom org.springframework.boot.gradle.plugin.SpringBootPlugin.BOM_COORDINATES + } +} + +dependencies { + implementation 'org.springframework.boot:spring-boot-starter' + testImplementation 'org.springframework.boot:spring-boot-starter-test' +} + +tasks.named('test') { + useJUnitPlatform() +} +<% } -%> +<% if(dsl === 'kotlin') { -%> +val javaVersion: String by project + +plugins { + java + id("org.springframework.boot") apply false + id("io.spring.dependency-management") +} + +group = "<%= groupId %>" +version = "<%= projectVersion %>" + +java { + sourceCompatibility = JavaVersion.toVersion(javaVersion) +} + +repositories { + mavenCentral() +} + +dependencyManagement { + imports { + mavenBom(org.springframework.boot.gradle.plugin.SpringBootPlugin.BOM_COORDINATES) + } +} + +dependencies { + implementation("org.springframework.boot:spring-boot-starter") + testImplementation("org.springframework.boot:spring-boot-starter-test") +} + +tasks.withType { + useJUnitPlatform() +} +<% } -%> +<% } -%> +<% if(versionManagement === 'version-catalog') { -%> +<% if(dsl === 'groovy') { -%> +plugins { + id 'java' + alias libs.plugins.springframework.boot apply false + alias libs.plugins.spring.dependency.management +} + +group = '<%= groupId %>' +version = '<%= projectVersion %>' + +java { + sourceCompatibility = libs.versions.java.get() +} + +repositories { + mavenCentral() +} + +dependencyManagement { + imports { + mavenBom org.springframework.boot.gradle.plugin.SpringBootPlugin.BOM_COORDINATES + } +} + +dependencies { + implementation 'org.springframework.boot:spring-boot-starter' + testImplementation 'org.springframework.boot:spring-boot-starter-test' +} + +tasks.named('test') { + useJUnitPlatform() +} +<% } -%> +<% if(dsl === 'kotlin') { -%> +plugins { + java + alias(libs.plugins.springframework.boot) apply false + alias(libs.plugins.spring.dependency.management) +} + +group = "<%= groupId %>" +version = "<%= projectVersion %>" + +java { + sourceCompatibility = JavaVersion.toVersion(libs.versions.java.get()) +} + +repositories { + mavenCentral() +} + +dependencyManagement { + imports { + mavenBom(org.springframework.boot.gradle.plugin.SpringBootPlugin.BOM_COORDINATES) + } +} + +dependencies { + implementation("org.springframework.boot:spring-boot-starter") + testImplementation("org.springframework.boot:spring-boot-starter-test") +} + +tasks.withType { + useJUnitPlatform() +} +<% } -%> +<% } -%> diff --git a/packages/internal/generators-files/spring-boot-4/library/gradle/kotlin/build.gradle__kotlinExtension__ b/packages/internal/generators-files/spring-boot-4/library/gradle/kotlin/build.gradle__kotlinExtension__ new file mode 100644 index 000000000..8353452a8 --- /dev/null +++ b/packages/internal/generators-files/spring-boot-4/library/gradle/kotlin/build.gradle__kotlinExtension__ @@ -0,0 +1,186 @@ +<% if(versionManagement === 'properties') { -%> +<% if(dsl === 'groovy') { -%> +import org.jetbrains.kotlin.gradle.tasks.KotlinCompile + +plugins { + id 'org.springframework.boot' apply false + id 'io.spring.dependency-management' + id 'org.jetbrains.kotlin.jvm' + id 'org.jetbrains.kotlin.plugin.spring' +} + +group = '<%= groupId %>' +version = '<%= projectVersion %>' + +java { + sourceCompatibility = "${javaVersion}" +} + +repositories { + mavenCentral() +} + +dependencyManagement { + imports { + mavenBom(org.springframework.boot.gradle.plugin.SpringBootPlugin.BOM_COORDINATES) + } +} + +dependencies { + implementation 'org.springframework.boot:spring-boot-starter' + implementation 'org.jetbrains.kotlin:kotlin-reflect' + implementation 'org.jetbrains.kotlin:kotlin-stdlib-jdk8' + testImplementation 'org.springframework.boot:spring-boot-starter-test' +} + +tasks.withType(KotlinCompile) { + kotlinOptions { + freeCompilerArgs += '-Xjsr305=strict' + jvmTarget = "${javaVersion}" + } +} + +tasks.named('test') { + useJUnitPlatform() +} +<% } -%> +<% if(dsl === 'kotlin') { -%> +import org.jetbrains.kotlin.gradle.tasks.KotlinCompile + +val javaVersion: String by project + +plugins { + id("org.springframework.boot") apply false + id("io.spring.dependency-management") + kotlin("jvm") + kotlin("plugin.spring") +} + +group = "<%= groupId %>" +version = "<%= projectVersion %>" + +java { + sourceCompatibility = JavaVersion.toVersion(javaVersion) +} + +repositories { + mavenCentral() +} + +dependencyManagement { + imports { + mavenBom(org.springframework.boot.gradle.plugin.SpringBootPlugin.BOM_COORDINATES) + } +} + +dependencies { + implementation("org.springframework.boot:spring-boot-starter") + implementation("org.jetbrains.kotlin:kotlin-reflect") + implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk8") + testImplementation("org.springframework.boot:spring-boot-starter-test") +} + +tasks.withType { + kotlinOptions { + freeCompilerArgs += "-Xjsr305=strict" + jvmTarget = javaVersion + } +} + +tasks.withType { + useJUnitPlatform() +} +<% } -%> +<% } -%> +<% if(versionManagement === 'version-catalog') { -%> +<% if(dsl === 'groovy') { -%> +import org.jetbrains.kotlin.gradle.tasks.KotlinCompile + +plugins { + alias libs.plugins.springframework.boot apply false + alias libs.plugins.spring.dependency.management + alias libs.plugins.jetbrains.kotlin.jvm + alias libs.plugins.jetbrains.kotlin.plugin.spring +} + +group = '<%= groupId %>' +version = '<%= projectVersion %>' + +java { + sourceCompatibility = libs.versions.java.get() +} + +repositories { + mavenCentral() +} + +dependencyManagement { + imports { + mavenBom(org.springframework.boot.gradle.plugin.SpringBootPlugin.BOM_COORDINATES) + } +} + +dependencies { + implementation 'org.springframework.boot:spring-boot-starter' + implementation 'org.jetbrains.kotlin:kotlin-reflect' + implementation 'org.jetbrains.kotlin:kotlin-stdlib-jdk8' + testImplementation 'org.springframework.boot:spring-boot-starter-test' +} + +tasks.withType(KotlinCompile) { + kotlinOptions { + freeCompilerArgs += '-Xjsr305=strict' + jvmTarget = libs.versions.java.get() + } +} + +tasks.named('test') { + useJUnitPlatform() +} +<% } -%> +<% if(dsl === 'kotlin') { -%> +import org.jetbrains.kotlin.gradle.tasks.KotlinCompile + +plugins { + alias(libs.plugins.springframework.boot) apply false + alias(libs.plugins.spring.dependency.management) + alias(libs.plugins.jetbrains.kotlin.jvm) + alias(libs.plugins.jetbrains.kotlin.plugin.spring) +} + +group = "<%= groupId %>" +version = "<%= projectVersion %>" + +java { + sourceCompatibility = JavaVersion.toVersion(libs.versions.java.get()) +} + +repositories { + mavenCentral() +} + +dependencyManagement { + imports { + mavenBom(org.springframework.boot.gradle.plugin.SpringBootPlugin.BOM_COORDINATES) + } +} + +dependencies { + implementation("org.springframework.boot:spring-boot-starter") + implementation("org.jetbrains.kotlin:kotlin-reflect") + implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk8") + testImplementation("org.springframework.boot:spring-boot-starter-test") +} + +tasks.withType { + kotlinOptions { + freeCompilerArgs += "-Xjsr305=strict" + jvmTarget = libs.versions.java.get() + } +} + +tasks.withType { + useJUnitPlatform() +} +<% } -%> +<% } -%> diff --git a/packages/internal/generators-files/spring-boot-4/library/maven/java/pom.xml__template__ b/packages/internal/generators-files/spring-boot-4/library/maven/java/pom.xml__template__ new file mode 100644 index 000000000..34b78fd54 --- /dev/null +++ b/packages/internal/generators-files/spring-boot-4/library/maven/java/pom.xml__template__ @@ -0,0 +1,33 @@ + + + 4.0.0 + <%= groupId %> + <%= projectName %> + <%= projectVersion %> + <%= projectName %> + This project was generated with nx-maven + +<% if(parentProjectName) { -%> + + <%= parentGroupId %> + <%= parentProjectName %> + <%= parentProjectVersion %> + <%= relativePath %> + +<% } -%> + + + + org.springframework.boot + spring-boot-starter + + + + org.springframework.boot + spring-boot-starter-test + test + + + + \ No newline at end of file diff --git a/packages/internal/generators-files/spring-boot-4/library/maven/kotlin/pom.xml__template__ b/packages/internal/generators-files/spring-boot-4/library/maven/kotlin/pom.xml__template__ new file mode 100644 index 000000000..e2540cc13 --- /dev/null +++ b/packages/internal/generators-files/spring-boot-4/library/maven/kotlin/pom.xml__template__ @@ -0,0 +1,69 @@ + + + 4.0.0 + <%= groupId %> + <%= projectName %> + <%= projectVersion %> + <%= projectName %> + This project was generated with nx-maven + +<% if(parentProjectName) { -%> + + <%= parentGroupId %> + <%= parentProjectName %> + <%= parentProjectVersion %> + <%= relativePath %> + +<% } -%> + + + + org.springframework.boot + spring-boot-starter + + + org.jetbrains.kotlin + kotlin-reflect + + + org.jetbrains.kotlin + kotlin-stdlib-jdk8 + + + + org.springframework.boot + spring-boot-starter-test + test + + + + + + ${project.basedir}/src/main/kotlin + ${project.basedir}/src/test/kotlin + + + org.jetbrains.kotlin + kotlin-maven-plugin + + + -Xjsr305=strict + + + spring + + + + + org.jetbrains.kotlin + kotlin-maven-allopen + ${kotlin.version} + + + + + + + + diff --git a/packages/internal/generators-files/spring-boot-4/library/shared/java/src/main/java/__packageDirectory__/HelloService.java b/packages/internal/generators-files/spring-boot-4/library/shared/java/src/main/java/__packageDirectory__/HelloService.java new file mode 100644 index 000000000..fb7989baa --- /dev/null +++ b/packages/internal/generators-files/spring-boot-4/library/shared/java/src/main/java/__packageDirectory__/HelloService.java @@ -0,0 +1,11 @@ +package <%= packageName %>; + +import org.springframework.stereotype.Service; + +@Service +public class HelloService { + + public String message() { + return "Hello World!"; + } +} diff --git a/packages/internal/generators-files/spring-boot-4/library/shared/java/src/test/java/__packageDirectory__/HelloServiceTests.java b/packages/internal/generators-files/spring-boot-4/library/shared/java/src/test/java/__packageDirectory__/HelloServiceTests.java new file mode 100644 index 000000000..cf98642eb --- /dev/null +++ b/packages/internal/generators-files/spring-boot-4/library/shared/java/src/test/java/__packageDirectory__/HelloServiceTests.java @@ -0,0 +1,21 @@ +package <%= packageName %>; + +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; + +import static org.assertj.core.api.Assertions.assertThat; + + +@SpringBootTest +public class HelloServiceTests { + + @Autowired + private HelloService helloService; + + @Test + public void shouldReturnHelloWorld() { + assertThat(helloService.message()).contains("Hello World"); + } + +} diff --git a/packages/internal/generators-files/spring-boot-4/library/shared/java/src/test/java/__packageDirectory__/TestConfiguration.java b/packages/internal/generators-files/spring-boot-4/library/shared/java/src/test/java/__packageDirectory__/TestConfiguration.java new file mode 100644 index 000000000..588dc553d --- /dev/null +++ b/packages/internal/generators-files/spring-boot-4/library/shared/java/src/test/java/__packageDirectory__/TestConfiguration.java @@ -0,0 +1,7 @@ +package <%= packageName %>; + +import org.springframework.boot.autoconfigure.SpringBootApplication; + +@SpringBootApplication +public class TestConfiguration { +} diff --git a/packages/internal/generators-files/spring-boot-4/library/shared/kotlin/src/main/kotlin/__packageDirectory__/HelloService.kt b/packages/internal/generators-files/spring-boot-4/library/shared/kotlin/src/main/kotlin/__packageDirectory__/HelloService.kt new file mode 100644 index 000000000..6dae605da --- /dev/null +++ b/packages/internal/generators-files/spring-boot-4/library/shared/kotlin/src/main/kotlin/__packageDirectory__/HelloService.kt @@ -0,0 +1,11 @@ +package <%= packageName %> + +import org.springframework.stereotype.Service + +@Service +class HelloService { + + fun message():String { + return "Hello World!" + } +} diff --git a/packages/internal/generators-files/spring-boot-4/library/shared/kotlin/src/test/kotlin/__packageDirectory__/HelloServiceTests.kt b/packages/internal/generators-files/spring-boot-4/library/shared/kotlin/src/test/kotlin/__packageDirectory__/HelloServiceTests.kt new file mode 100644 index 000000000..35159f3ff --- /dev/null +++ b/packages/internal/generators-files/spring-boot-4/library/shared/kotlin/src/test/kotlin/__packageDirectory__/HelloServiceTests.kt @@ -0,0 +1,32 @@ +package <%= packageName %> + +import org.assertj.core.api.Assertions.assertThat +import org.junit.jupiter.api.AfterAll +import org.junit.jupiter.api.BeforeAll +import org.junit.jupiter.api.Test +import org.springframework.beans.factory.annotation.Autowired +import org.springframework.boot.test.context.SpringBootTest + + +@SpringBootTest +class HelloServiceTests(@Autowired val helloService: HelloService) { + + @BeforeAll + fun setup() { + println(">> Setup") + } + + @Test + fun `Should return Hello World`() { + println(">> Should return Hello World") + assertThat(helloService.message()).contains("Hello World") + } + + + @AfterAll + fun teardown() { + println(">> Tear down") + } + + +} diff --git a/packages/internal/generators-files/spring-boot-4/library/shared/kotlin/src/test/kotlin/__packageDirectory__/TestConfiguration.kt b/packages/internal/generators-files/spring-boot-4/library/shared/kotlin/src/test/kotlin/__packageDirectory__/TestConfiguration.kt new file mode 100644 index 000000000..b0527a8fb --- /dev/null +++ b/packages/internal/generators-files/spring-boot-4/library/shared/kotlin/src/test/kotlin/__packageDirectory__/TestConfiguration.kt @@ -0,0 +1,7 @@ +package <%= packageName %> + +import org.springframework.boot.autoconfigure.SpringBootApplication + +@SpringBootApplication +open class TestConfiguration { +} diff --git a/packages/internal/generators-files/spring-boot-4/library/shared/kotlin/src/test/resources/junit-platform.properties b/packages/internal/generators-files/spring-boot-4/library/shared/kotlin/src/test/resources/junit-platform.properties new file mode 100644 index 000000000..d265fd838 --- /dev/null +++ b/packages/internal/generators-files/spring-boot-4/library/shared/kotlin/src/test/resources/junit-platform.properties @@ -0,0 +1 @@ +junit.jupiter.testinstance.lifecycle.default = per_class diff --git a/packages/nx-gradle/project.json b/packages/nx-gradle/project.json index 026033d9a..8cd3b6e67 100644 --- a/packages/nx-gradle/project.json +++ b/packages/nx-gradle/project.json @@ -72,6 +72,26 @@ "glob": "**", "output": "./src/generators/library/files/spring-boot" }, + { + "input": "./packages/internal/generators-files/spring-boot-4/application/shared", + "glob": "**", + "output": "./src/generators/application/files/spring-boot-4" + }, + { + "input": "./packages/internal/generators-files/spring-boot-4/application/gradle", + "glob": "**", + "output": "./src/generators/application/files/spring-boot-4" + }, + { + "input": "./packages/internal/generators-files/spring-boot-4/library/shared", + "glob": "**", + "output": "./src/generators/library/files/spring-boot-4" + }, + { + "input": "./packages/internal/generators-files/spring-boot-4/library/gradle", + "glob": "**", + "output": "./src/generators/library/files/spring-boot-4" + }, { "input": "./packages/internal/generators-files/quarkus/application/shared", "glob": "**", diff --git a/packages/nx-gradle/src/generators/application/generator.ts b/packages/nx-gradle/src/generators/application/generator.ts index 7da932d45..e2bdbd01b 100644 --- a/packages/nx-gradle/src/generators/application/generator.ts +++ b/packages/nx-gradle/src/generators/application/generator.ts @@ -162,7 +162,10 @@ function addFiles(tree: Tree, options: NormalizedSchema) { template: '', }; - if (options.framework === 'spring-boot') { + if ( + options.framework === 'spring-boot' || + options.framework === 'spring-boot-4' + ) { addSpringBootFiles(tree, options, templateOptions); } @@ -222,9 +225,11 @@ function addSpringBootFiles( options: NormalizedSchema, templateOptions: TemplateOptionsType, ) { + const springBootFolder = + options.framework === 'spring-boot-4' ? 'spring-boot-4' : 'spring-boot'; generateFiles( tree, - path.join(__dirname, 'files', 'spring-boot', options.language), + path.join(__dirname, 'files', springBootFolder, options.language), options.projectRoot, templateOptions, ); @@ -393,7 +398,10 @@ async function applicationGenerator( const targets = projectConfiguration.targets ?? {}; - if (options.framework === 'spring-boot') { + if ( + options.framework === 'spring-boot' || + options.framework === 'spring-boot-4' + ) { targets[`${normalizedOptions.buildTargetName}`].options = { ...targets[`${normalizedOptions.buildTargetName}`].options, task: normalizedOptions.packaging === 'war' ? 'bootWar' : 'bootJar', diff --git a/packages/nx-gradle/src/generators/application/schema.json b/packages/nx-gradle/src/generators/application/schema.json index dfcd9f286..68eec874e 100644 --- a/packages/nx-gradle/src/generators/application/schema.json +++ b/packages/nx-gradle/src/generators/application/schema.json @@ -76,14 +76,18 @@ "description": "framework", "type": "string", "default": "spring-boot", - "enum": ["spring-boot", "quarkus", "micronaut", "none"], + "enum": ["spring-boot", "spring-boot-4", "quarkus", "micronaut", "none"], "x-prompt": { "message": "Which framework to use? or 'none' to skip.", "type": "list", "items": [ { "value": "spring-boot", - "label": "spring-boot" + "label": "Spring Boot 3" + }, + { + "value": "spring-boot-4", + "label": "Spring Boot 4" }, { "value": "quarkus", diff --git a/packages/nx-gradle/src/generators/init/files/gradle/config/spring-boot-4/build.gradle__kotlinExtension__ b/packages/nx-gradle/src/generators/init/files/gradle/config/spring-boot-4/build.gradle__kotlinExtension__ new file mode 100644 index 000000000..e17386bc6 --- /dev/null +++ b/packages/nx-gradle/src/generators/init/files/gradle/config/spring-boot-4/build.gradle__kotlinExtension__ @@ -0,0 +1,20 @@ +<% if(versionManagement === 'properties') { -%> +plugins { +<% if(dsl === 'groovy') { -%> + id 'io.github.khalilou88.jnxplus' +<% } -%> +<% if(dsl === 'kotlin') { -%> + id("io.github.khalilou88.jnxplus") +<% } -%> +} +<% } -%> +<% if(versionManagement === 'version-catalog') { -%> +plugins { +<% if(dsl === 'groovy') { -%> + alias libs.plugins.github.khalilou88.jnxplus +<% } -%> +<% if(dsl === 'kotlin') { -%> + alias(libs.plugins.github.khalilou88.jnxplus) +<% } -%> +} +<% } -%> diff --git a/packages/nx-gradle/src/generators/init/files/gradle/config/spring-boot-4/gradle.properties__template__ b/packages/nx-gradle/src/generators/init/files/gradle/config/spring-boot-4/gradle.properties__template__ new file mode 100644 index 000000000..441186b90 --- /dev/null +++ b/packages/nx-gradle/src/generators/init/files/gradle/config/spring-boot-4/gradle.properties__template__ @@ -0,0 +1,12 @@ +<% if(versionManagement === 'properties') { -%> +#Gradle properties +javaVersion=<%= javaVersion %> +kotlinVersion=<%= kotlinVersion %> +springBootVersion=<%= springBoot4Version %> +springDependencyManagementVersion=<%= springDependencyManagement4Version %> +jnxplusGradlePluginVersion=<%= jnxplusGradlePluginVersion %> +<% } -%> + + + + diff --git a/packages/nx-gradle/src/generators/init/files/gradle/config/spring-boot-4/settings.gradle__kotlinExtension__ b/packages/nx-gradle/src/generators/init/files/gradle/config/spring-boot-4/settings.gradle__kotlinExtension__ new file mode 100644 index 000000000..e0d83a246 --- /dev/null +++ b/packages/nx-gradle/src/generators/init/files/gradle/config/spring-boot-4/settings.gradle__kotlinExtension__ @@ -0,0 +1,61 @@ +<% if(versionManagement === 'properties') { -%> +<% if(dsl === 'groovy') { -%> +pluginManagement { + plugins { + id 'org.springframework.boot' version "${springBootVersion}" + id 'io.spring.dependency-management' version "${springDependencyManagementVersion}" + id 'org.jetbrains.kotlin.plugin.spring' version "${kotlinVersion}" + id 'org.jetbrains.kotlin.jvm' version "${kotlinVersion}" + id 'io.github.khalilou88.jnxplus' version "${jnxplusGradlePluginVersion}" + } +<% if(generateRepositories) { -%> + repositories { + mavenCentral() + gradlePluginPortal() + mavenLocal() + } +<% } -%> +} +rootProject.name = '<%= rootProjectName %>' +<% } -%> +<% if(dsl === 'kotlin') { -%> +pluginManagement { + val springBootVersion: String by settings + val springDependencyManagementVersion: String by settings + val kotlinVersion: String by settings + val jnxplusGradlePluginVersion: String by settings + plugins { + id("org.springframework.boot") version springBootVersion + id("io.spring.dependency-management") version springDependencyManagementVersion + id("org.jetbrains.kotlin.plugin.spring") version kotlinVersion + id("org.jetbrains.kotlin.jvm") version kotlinVersion + id("io.github.khalilou88.jnxplus") version jnxplusGradlePluginVersion + } +<% if(generateRepositories) { -%> + repositories { + mavenCentral() + gradlePluginPortal() + mavenLocal() + } +<% } -%> +} +rootProject.name = "<%= rootProjectName %>" +<% } -%> +<% } -%> +<% if(versionManagement === 'version-catalog') { -%> +<% if(generateRepositories) { -%> +pluginManagement { + repositories { + mavenCentral() + gradlePluginPortal() + mavenLocal() + } +} +<% } -%> +<% if(dsl === 'groovy') { -%> +rootProject.name = '<%= rootProjectName %>' +<% } -%> +<% if(dsl === 'kotlin') { -%> +rootProject.name = "<%= rootProjectName %>" +<% } -%> +<% } -%> diff --git a/packages/nx-gradle/src/generators/init/generator.ts b/packages/nx-gradle/src/generators/init/generator.ts index 480cdf17c..c8edd7c92 100644 --- a/packages/nx-gradle/src/generators/init/generator.ts +++ b/packages/nx-gradle/src/generators/init/generator.ts @@ -11,6 +11,8 @@ import { shadowVersion, springBootVersion, springDependencyManagementVersion, + springBoot4Version, + springDependencyManagement4Version, updateNxJsonConfiguration, } from '@jnxplus/common'; import { @@ -35,6 +37,8 @@ interface NormalizedSchema extends NxGradleInitGeneratorSchema { kotlinExtension: string; springBootVersion: string; springDependencyManagementVersion: string; + springBoot4Version: string; + springDependencyManagement4Version: string; quarkusVersion: string; micronautVersion: string; kspVersion: string; @@ -57,6 +61,8 @@ function normalizeOptions( kotlinExtension, springBootVersion, springDependencyManagementVersion, + springBoot4Version, + springDependencyManagement4Version, quarkusVersion, micronautVersion, kspVersion, diff --git a/packages/nx-gradle/src/generators/init/schema.json b/packages/nx-gradle/src/generators/init/schema.json index 7a095e94a..ddaf5cae3 100644 --- a/packages/nx-gradle/src/generators/init/schema.json +++ b/packages/nx-gradle/src/generators/init/schema.json @@ -69,14 +69,18 @@ "description": "preset", "type": "string", "default": "spring-boot", - "enum": ["spring-boot", "quarkus", "micronaut", "none"], + "enum": ["spring-boot", "spring-boot-4", "quarkus", "micronaut", "none"], "x-prompt": { "message": "Which preset to use? or 'none' to skip.", "type": "list", "items": [ { "value": "spring-boot", - "label": "Spring Boot" + "label": "Spring Boot 3" + }, + { + "value": "spring-boot-4", + "label": "Spring Boot 4" }, { "value": "quarkus", diff --git a/packages/nx-gradle/src/generators/library/generator.ts b/packages/nx-gradle/src/generators/library/generator.ts index 1a3c31787..41d3d3ccb 100644 --- a/packages/nx-gradle/src/generators/library/generator.ts +++ b/packages/nx-gradle/src/generators/library/generator.ts @@ -129,7 +129,10 @@ function addFiles(tree: Tree, options: NormalizedSchema) { template: '', }; - if (options.framework === 'spring-boot') { + if ( + options.framework === 'spring-boot' || + options.framework === 'spring-boot-4' + ) { addSpringBootFiles(tree, options, templateOptions); } @@ -181,9 +184,11 @@ function addSpringBootFiles( options: NormalizedSchema, templateOptions: TemplateOptionsType, ) { + const springBootFolder = + options.framework === 'spring-boot-4' ? 'spring-boot-4' : 'spring-boot'; generateFiles( tree, - path.join(__dirname, 'files', 'spring-boot', options.language), + path.join(__dirname, 'files', springBootFolder, options.language), options.projectRoot, templateOptions, ); @@ -320,7 +325,10 @@ async function libraryGenerator( const targets = projectConfiguration.targets ?? {}; - if (options.framework === 'spring-boot') { + if ( + options.framework === 'spring-boot' || + options.framework === 'spring-boot-4' + ) { targets['build'].options = { ...targets['build'].options, task: 'jar', diff --git a/packages/nx-gradle/src/generators/library/schema.json b/packages/nx-gradle/src/generators/library/schema.json index 127596a1c..be560a10f 100644 --- a/packages/nx-gradle/src/generators/library/schema.json +++ b/packages/nx-gradle/src/generators/library/schema.json @@ -70,14 +70,18 @@ "description": "framework", "type": "string", "default": "spring-boot", - "enum": ["spring-boot", "quarkus", "micronaut", "none"], + "enum": ["spring-boot", "spring-boot-4", "quarkus", "micronaut", "none"], "x-prompt": { "message": "Which framework to use? or 'none' to skip.", "type": "list", "items": [ { "value": "spring-boot", - "label": "spring-boot" + "label": "Spring Boot 3" + }, + { + "value": "spring-boot-4", + "label": "Spring Boot 4" }, { "value": "quarkus", diff --git a/packages/nx-gradle/src/utils/libs-versions-toml.ts b/packages/nx-gradle/src/utils/libs-versions-toml.ts index 87ffb6c56..f4c2bdcfc 100644 --- a/packages/nx-gradle/src/utils/libs-versions-toml.ts +++ b/packages/nx-gradle/src/utils/libs-versions-toml.ts @@ -10,6 +10,8 @@ import { shadowVersion, springBootVersion, springDependencyManagementVersion, + springBoot4Version, + springDependencyManagement4Version, } from '@jnxplus/common'; import { Tree, joinPathFragments } from '@nx/devkit'; @@ -121,6 +123,33 @@ function getElements( } } + if (preset === 'spring-boot-4') { + if (!catalog?.versions['spring-boot']) { + elements.versions.push(`spring-boot = "${springBoot4Version}"`); + } + + if (!catalog?.plugins['springframework-boot']) { + elements.plugins.push( + 'springframework-boot = { id = "org.springframework.boot", version.ref = "spring-boot" }', + ); + } + + if (!catalog?.plugins['spring-dependency-management']) { + elements.plugins.push( + `spring-dependency-management = { id = "io.spring.dependency-management", version = "${springDependencyManagement4Version}" }`, + ); + } + + if ( + language === 'kotlin' && + !catalog?.plugins['jetbrains-kotlin-plugin-spring'] + ) { + elements.plugins.push( + 'jetbrains-kotlin-plugin-spring = { id = "org.jetbrains.kotlin.plugin.spring", version.ref = "kotlin" }', + ); + } + } + if (preset === 'quarkus') { if (!catalog?.versions['quarkus']) { elements.versions.push(`quarkus = "${quarkusVersion}"`); diff --git a/packages/nx-maven/project.json b/packages/nx-maven/project.json index 0dc76186b..7caf21b2a 100644 --- a/packages/nx-maven/project.json +++ b/packages/nx-maven/project.json @@ -75,6 +75,26 @@ "glob": "**", "output": "./src/generators/library/files/spring-boot" }, + { + "input": "./packages/internal/generators-files/spring-boot-4/application/shared", + "glob": "**", + "output": "./src/generators/application/files/spring-boot-4" + }, + { + "input": "./packages/internal/generators-files/spring-boot-4/application/maven", + "glob": "**", + "output": "./src/generators/application/files/spring-boot-4" + }, + { + "input": "./packages/internal/generators-files/spring-boot-4/library/shared", + "glob": "**", + "output": "./src/generators/library/files/spring-boot-4" + }, + { + "input": "./packages/internal/generators-files/spring-boot-4/library/maven", + "glob": "**", + "output": "./src/generators/library/files/spring-boot-4" + }, { "input": "./packages/internal/generators-files/quarkus/application/shared", "glob": "**", diff --git a/packages/nx-maven/src/generators/application/generator.ts b/packages/nx-maven/src/generators/application/generator.ts index 0ed0f12ee..6a48a88c0 100644 --- a/packages/nx-maven/src/generators/application/generator.ts +++ b/packages/nx-maven/src/generators/application/generator.ts @@ -197,9 +197,11 @@ function addSpringBootFiles( options: NormalizedSchema, templateOptions: TemplateOptionsType, ) { + const springBootFolder = + options.framework === 'spring-boot-4' ? 'spring-boot-4' : 'spring-boot'; generateFiles( tree, - path.join(d, 'files', 'spring-boot', options.language), + path.join(d, 'files', springBootFolder, options.language), options.projectRoot, templateOptions, ); @@ -339,7 +341,10 @@ function addFiles(tree: Tree, options: NormalizedSchema) { template: '', }; - if (options.framework === 'spring-boot') { + if ( + options.framework === 'spring-boot' || + options.framework === 'spring-boot-4' + ) { addSpringBootFiles(__dirname, tree, options, templateOptions); } @@ -396,7 +401,10 @@ async function applicationGenerator( const targets = projectConfiguration.targets ?? {}; - if (options.framework === 'spring-boot') { + if ( + options.framework === 'spring-boot' || + options.framework === 'spring-boot-4' + ) { targets[`${normalizedOptions.buildTargetName}`].options = { ...targets[`${normalizedOptions.buildTargetName}`].options, task: 'package spring-boot:repackage -DskipTests=true', diff --git a/packages/nx-maven/src/generators/application/schema.json b/packages/nx-maven/src/generators/application/schema.json index e0418f9e9..212eb2d19 100644 --- a/packages/nx-maven/src/generators/application/schema.json +++ b/packages/nx-maven/src/generators/application/schema.json @@ -76,14 +76,18 @@ "description": "framework", "type": "string", "default": "spring-boot", - "enum": ["spring-boot", "quarkus", "micronaut", "none"], + "enum": ["spring-boot", "spring-boot-4", "quarkus", "micronaut", "none"], "x-prompt": { "message": "Which framework to use? or 'none' to skip.", "type": "list", "items": [ { "value": "spring-boot", - "label": "spring-boot" + "label": "Spring Boot 3" + }, + { + "value": "spring-boot-4", + "label": "Spring Boot 4" }, { "value": "quarkus", diff --git a/packages/nx-maven/src/generators/init/files/maven/config/pom.xml__template__ b/packages/nx-maven/src/generators/init/files/maven/config/pom.xml__template__ index cf6bf1df6..569bdfd5f 100644 --- a/packages/nx-maven/src/generators/init/files/maven/config/pom.xml__template__ +++ b/packages/nx-maven/src/generators/init/files/maven/config/pom.xml__template__ @@ -16,6 +16,14 @@ <% } -%> + <% if(dependencyManagement === 'spring-boot-4-parent-pom') { -%> + + org.springframework.boot + spring-boot-starter-parent + <%= springBoot4Version %> + + + <% } -%> <% if(dependencyManagement === 'micronaut-parent-pom') { -%> io.micronaut.platform @@ -35,6 +43,9 @@ <% if(dependencyManagement === 'spring-boot-bom') { -%> <%= springBootVersion %> <% } -%> + <% if(dependencyManagement === 'spring-boot-4-bom') { -%> + <%= springBoot4Version %> + <% } -%> <% if(dependencyManagement === 'quarkus-bom') { -%> <%= quarkusVersion %> <% } -%> @@ -48,10 +59,10 @@ <% } -%> - <% if(dependencyManagement === 'spring-boot-bom' || dependencyManagement === 'quarkus-bom' || dependencyManagement === 'micronaut-bom') { -%> + <% if(dependencyManagement === 'spring-boot-bom' || dependencyManagement === 'spring-boot-4-bom' || dependencyManagement === 'quarkus-bom' || dependencyManagement === 'micronaut-bom') { -%> - <% if(dependencyManagement === 'spring-boot-bom') { -%> + <% if(dependencyManagement === 'spring-boot-bom' || dependencyManagement === 'spring-boot-4-bom') { -%> org.springframework.boot spring-boot-dependencies diff --git a/packages/nx-maven/src/generators/init/generator.ts b/packages/nx-maven/src/generators/init/generator.ts index ff7b49f57..fcced623f 100644 --- a/packages/nx-maven/src/generators/init/generator.ts +++ b/packages/nx-maven/src/generators/init/generator.ts @@ -8,6 +8,7 @@ import { micronautVersion, quarkusVersion, springBootVersion, + springBoot4Version, } from '@jnxplus/common'; import { ProjectConfiguration, @@ -29,6 +30,7 @@ import { NxMavenInitGeneratorSchema } from './schema'; interface NormalizedSchema extends NxMavenInitGeneratorSchema { dot: string; springBootVersion: string; + springBoot4Version: string; quarkusVersion: string; micronautVersion: string; } @@ -43,6 +45,7 @@ function normalizeOptions( ...options, dot, springBootVersion, + springBoot4Version, quarkusVersion, micronautVersion, }; diff --git a/packages/nx-maven/src/generators/init/schema.json b/packages/nx-maven/src/generators/init/schema.json index 1931e1d59..8f28d1ed1 100644 --- a/packages/nx-maven/src/generators/init/schema.json +++ b/packages/nx-maven/src/generators/init/schema.json @@ -92,11 +92,19 @@ }, { "value": "spring-boot-parent-pom", - "label": "Spring Boot Parent POM" + "label": "Spring Boot 3 Parent POM" }, { "value": "spring-boot-bom", - "label": "Spring Boot BOM (Bill of Materials)" + "label": "Spring Boot 3 BOM (Bill of Materials)" + }, + { + "value": "spring-boot-4-parent-pom", + "label": "Spring Boot 4 Parent POM" + }, + { + "value": "spring-boot-4-bom", + "label": "Spring Boot 4 BOM (Bill of Materials)" }, { "value": "quarkus-bom", diff --git a/packages/nx-maven/src/generators/library/generator.ts b/packages/nx-maven/src/generators/library/generator.ts index fafca210e..653e8440d 100644 --- a/packages/nx-maven/src/generators/library/generator.ts +++ b/packages/nx-maven/src/generators/library/generator.ts @@ -130,9 +130,11 @@ function addSpringBootFiles( options: NormalizedSchema, templateOptions: TemplateOptionsType, ) { + const springBootFolder = + options.framework === 'spring-boot-4' ? 'spring-boot-4' : 'spring-boot'; generateFiles( tree, - path.join(__dirname, 'files', 'spring-boot', options.language), + path.join(__dirname, 'files', springBootFolder, options.language), options.projectRoot, templateOptions, ); @@ -269,7 +271,10 @@ function addFiles(tree: Tree, options: NormalizedSchema) { template: '', }; - if (options.framework === 'spring-boot') { + if ( + options.framework === 'spring-boot' || + options.framework === 'spring-boot-4' + ) { addSpringBootFiles(tree, options, templateOptions); } diff --git a/packages/nx-maven/src/generators/library/schema.json b/packages/nx-maven/src/generators/library/schema.json index 33e4c5717..0834aeccf 100644 --- a/packages/nx-maven/src/generators/library/schema.json +++ b/packages/nx-maven/src/generators/library/schema.json @@ -70,14 +70,18 @@ "description": "framework", "type": "string", "default": "spring-boot", - "enum": ["spring-boot", "quarkus", "micronaut", "none"], + "enum": ["spring-boot", "spring-boot-4", "quarkus", "micronaut", "none"], "x-prompt": { "message": "Which framework to use? or 'none' to skip.", "type": "list", "items": [ { "value": "spring-boot", - "label": "spring-boot" + "label": "Spring Boot 3" + }, + { + "value": "spring-boot-4", + "label": "Spring Boot 4" }, { "value": "quarkus", diff --git a/packages/nx-maven/src/generators/parent-project/files/pom.xml__template__ b/packages/nx-maven/src/generators/parent-project/files/pom.xml__template__ index aab0599c7..8c5681109 100644 --- a/packages/nx-maven/src/generators/parent-project/files/pom.xml__template__ +++ b/packages/nx-maven/src/generators/parent-project/files/pom.xml__template__ @@ -9,7 +9,7 @@ <%= projectName %> This project was generated with nx-maven -<% if(parentProjectName && dependencyManagement !== 'spring-boot-parent-pom' && dependencyManagement !== 'micronaut-parent-pom') { -%> +<% if(parentProjectName && dependencyManagement !== 'spring-boot-parent-pom' && dependencyManagement !== 'spring-boot-4-parent-pom' && dependencyManagement !== 'micronaut-parent-pom') { -%> <%= parentGroupId %> <%= parentProjectName %> @@ -49,6 +49,24 @@ <% } -%> +<% if(dependencyManagement === 'spring-boot-4-parent-pom') { -%> + + org.springframework.boot + spring-boot-starter-parent + <%= springBoot4Version %> + + + + + <% if(javaVersion !== 'none') { -%> + <%= javaVersion %> + <% } -%> + <% if(language === 'kotlin' || language === 'java & kotlin') { -%> + <%= kotlinVersion %> + <% } -%> + +<% } -%> + <% if(dependencyManagement === 'micronaut-parent-pom') { -%> io.micronaut.platform @@ -69,7 +87,7 @@ <% } -%> -<% if(dependencyManagement === 'spring-boot-bom') { -%> +<% if(dependencyManagement === 'spring-boot-bom' || dependencyManagement === 'spring-boot-4-bom') { -%> <% if(javaVersion !== 'none') { -%> <%= javaVersion %> @@ -77,7 +95,12 @@ <% if(language === 'kotlin' || language === 'java & kotlin') { -%> <%= kotlinVersion %> <% } -%> + <% if(dependencyManagement === 'spring-boot-bom') { -%> <%= springBootVersion %> + <% } -%> + <% if(dependencyManagement === 'spring-boot-4-bom') { -%> + <%= springBoot4Version %> + <% } -%> @ ${java.version} UTF-8 diff --git a/packages/nx-maven/src/generators/parent-project/generator.ts b/packages/nx-maven/src/generators/parent-project/generator.ts index 31db9e89a..dc625504c 100644 --- a/packages/nx-maven/src/generators/parent-project/generator.ts +++ b/packages/nx-maven/src/generators/parent-project/generator.ts @@ -19,6 +19,7 @@ import { parseTags, quarkusVersion, springBootVersion, + springBoot4Version, } from '@jnxplus/common'; import { Tree, @@ -56,6 +57,7 @@ interface NormalizedSchema extends NxMavenParentProjectGeneratorSchema { relativePath: string; kotlinVersion: string; springBootVersion: string; + springBoot4Version: string; quarkusVersion: string; micronautVersion: string; mavenRootDirectory: string; @@ -126,6 +128,7 @@ function normalizeOptions( relativePath, kotlinVersion, springBootVersion, + springBoot4Version, quarkusVersion, micronautVersion, mavenRootDirectory, diff --git a/packages/nx-maven/src/generators/parent-project/schema.json b/packages/nx-maven/src/generators/parent-project/schema.json index 6dffa5a03..5f5544197 100644 --- a/packages/nx-maven/src/generators/parent-project/schema.json +++ b/packages/nx-maven/src/generators/parent-project/schema.json @@ -88,6 +88,8 @@ "enum": [ "spring-boot-parent-pom", "spring-boot-bom", + "spring-boot-4-parent-pom", + "spring-boot-4-bom", "quarkus-bom", "micronaut-parent-pom", "micronaut-bom", @@ -99,11 +101,19 @@ "items": [ { "value": "spring-boot-parent-pom", - "label": "Spring Boot parent POM" + "label": "Spring Boot 3 parent POM" }, { "value": "spring-boot-bom", - "label": "Spring Boot BOM" + "label": "Spring Boot 3 BOM" + }, + { + "value": "spring-boot-4-parent-pom", + "label": "Spring Boot 4 parent POM" + }, + { + "value": "spring-boot-4-bom", + "label": "Spring Boot 4 BOM" }, { "value": "quarkus-bom", diff --git a/testing-projects/jnxplus-e2e/tests/nx-gradle/spring-boot-4-kt.spec.ts b/testing-projects/jnxplus-e2e/tests/nx-gradle/spring-boot-4-kt.spec.ts new file mode 100644 index 000000000..16ce8636c --- /dev/null +++ b/testing-projects/jnxplus-e2e/tests/nx-gradle/spring-boot-4-kt.spec.ts @@ -0,0 +1,441 @@ +import { normalizeName } from '@jnxplus/common'; +import { + checkFilesDoNotExist, + createTestWorkspace, + getData, + killProcessAndPorts, + runNxCommandUntil, +} from '@jnxplus/internal/testing'; +import { names } from '@nx/devkit'; +import { + checkFilesExist, + readFile, + readJson, + runNxCommandAsync, + tmpProjPath, + uniq, + updateFile, +} from '@nx/plugin/testing'; +import { execSync } from 'child_process'; +import { rmSync } from 'fs'; +import * as path from 'path'; + +describe('nx-gradle spring-boot-4 kotlin dsl e2e', () => { + let workspaceDirectory: string; + const isCI = + process.env.CI === 'true' || process.env.GITHUB_ACTIONS === 'true'; + const isWin = process.platform === 'win32'; + const isMacOs = process.platform === 'darwin'; + + const rootProjectName = uniq('root-project-'); + + beforeAll(async () => { + workspaceDirectory = createTestWorkspace(); + + execSync(`npm install -D @jnxplus/nx-gradle@e2e`, { + cwd: workspaceDirectory, + stdio: 'inherit', + env: process.env, + }); + + await runNxCommandAsync( + `generate @jnxplus/nx-gradle:init --gradleRootDirectory . --dsl kotlin --rootProjectName ${rootProjectName} --preset spring-boot-4`, + ); + }, 120000); + + afterAll(async () => { + if (process.env['SKIP_E2E_CLEANUP'] !== 'true') { + rmSync(workspaceDirectory, { + recursive: true, + force: true, + }); + } + }); + + it('should set NX_VERBOSE_LOGGING to true', async () => { + expect(process.env['NX_VERBOSE_LOGGING']).toBe('true'); + }, 120000); + + it('should use dsl option when initiating the workspace with spring-boot-4', async () => { + const packageJson = readJson('package.json'); + expect(packageJson.devDependencies['@jnxplus/nx-gradle']).toBeTruthy(); + + expect(() => + checkFilesExist( + 'gradle/wrapper/gradle-wrapper.jar', + 'gradle/wrapper/gradle-wrapper.properties', + 'gradlew', + 'gradlew.bat', + 'gradle.properties', + 'settings.gradle.kts', + ), + ).not.toThrow(); + + // Check gradle.properties contains Spring Boot 4 version + const gradleProperties = readFile('gradle.properties'); + expect(gradleProperties.includes('springBootVersion=4.0.0')).toBeTruthy(); + }, 120000); + + it('should create a java application with spring-boot-4', async () => { + const appName = uniq('g-sb4-app-'); + + await runNxCommandAsync( + `generate @jnxplus/nx-gradle:application ${appName} --framework spring-boot-4`, + ); + + expect(() => + checkFilesExist( + `apps/${appName}/build.gradle.kts`, + `apps/${appName}/src/main/resources/application.properties`, + `apps/${appName}/src/main/java/com/example/${names( + appName, + ).className.toLocaleLowerCase()}/${ + names(appName).className + }Application.java`, + `apps/${appName}/src/main/java/com/example/${names( + appName, + ).className.toLocaleLowerCase()}/HelloController.java`, + `apps/${appName}/src/test/resources/application.properties`, + `apps/${appName}/src/test/java/com/example/${names( + appName, + ).className.toLocaleLowerCase()}/HelloControllerTests.java`, + ), + ).not.toThrow(); + + const settingsGradle = readFile('settings.gradle.kts'); + expect(settingsGradle.includes(`:${appName}`)).toBeTruthy(); + + const buildGradle = readFile(`apps/${appName}/build.gradle.kts`); + expect(buildGradle.includes('com.example')).toBeTruthy(); + expect(buildGradle.includes('0.0.1-SNAPSHOT')).toBeTruthy(); + + const testResult = await runNxCommandAsync(`test ${appName}`); + expect(testResult.stdout).toContain('Executor ran for Test'); + + const buildResult = await runNxCommandAsync(`build ${appName}`); + expect(buildResult.stdout).toContain('Executor ran for Build'); + + const process = await runNxCommandUntil(`serve ${appName}`, (output) => + output.includes(`Tomcat started on port 8080`), + ); + + const dataResult = await getData(); + expect(dataResult.status).toEqual(200); + expect(dataResult.message).toMatch('Hello World!'); + + await killProcessAndPorts(process.pid, 8080); + }, 240000); + + it('should build-image a java application', async () => { + if (!isWin && !isMacOs && isCI) { + const appName = uniq('g-sb4-app-'); + await runNxCommandAsync( + `generate @jnxplus/nx-gradle:application ${appName} --framework spring-boot-4`, + ); + const buildImageResult = await runNxCommandAsync( + `build-image ${appName}`, + ); + expect(buildImageResult.stdout).toContain('Executor ran for Build Image'); + } + }, 120000); + + it('should create a kotlin application with spring-boot-4', async () => { + const appName = uniq('g-sb4-app-'); + const port = 8181; + + await runNxCommandAsync( + `generate @jnxplus/nx-gradle:application ${appName} --framework spring-boot-4 --language kotlin --port ${port}`, + ); + + expect(() => + checkFilesExist( + `apps/${appName}/build.gradle.kts`, + `apps/${appName}/src/main/resources/application.properties`, + `apps/${appName}/src/main/kotlin/com/example/${names( + appName, + ).className.toLocaleLowerCase()}/${ + names(appName).className + }Application.kt`, + `apps/${appName}/src/main/kotlin/com/example/${names( + appName, + ).className.toLocaleLowerCase()}/HelloController.kt`, + `apps/${appName}/src/test/resources/application.properties`, + `apps/${appName}/src/test/kotlin/com/example/${names( + appName, + ).className.toLocaleLowerCase()}/${ + names(appName).className + }ApplicationTests.kt`, + ), + ).not.toThrow(); + + const buildGradle = readFile(`apps/${appName}/build.gradle.kts`); + expect(buildGradle.includes('com.example')).toBeTruthy(); + expect(buildGradle.includes('0.0.1-SNAPSHOT')).toBeTruthy(); + + const testResult = await runNxCommandAsync(`test ${appName}`); + expect(testResult.stdout).toContain('Executor ran for Test'); + + const buildResult = await runNxCommandAsync(`build ${appName}`); + expect(buildResult.stdout).toContain('Executor ran for Build'); + + const process = await runNxCommandUntil(`serve ${appName}`, (output) => + output.includes(`Tomcat started on port ${port}`), + ); + + const dataResult = await getData(port); + expect(dataResult.status).toEqual(200); + expect(dataResult.message).toMatch('Hello World!'); + + await killProcessAndPorts(process.pid, port); + }, 240000); + + it('should create a java library with spring-boot-4', async () => { + const libName = uniq('g-sb4-lib-'); + + await runNxCommandAsync( + `generate @jnxplus/nx-gradle:library ${libName} --framework spring-boot-4`, + ); + + expect(() => + checkFilesExist( + `libs/${libName}/build.gradle.kts`, + `libs/${libName}/src/main/java/com/example/${names( + libName, + ).className.toLocaleLowerCase()}/HelloService.java`, + `libs/${libName}/src/test/java/com/example/${names( + libName, + ).className.toLocaleLowerCase()}/TestConfiguration.java`, + `libs/${libName}/src/test/java/com/example/${names( + libName, + ).className.toLocaleLowerCase()}/HelloServiceTests.java`, + ), + ).not.toThrow(); + + const buildGradle = readFile(`libs/${libName}/build.gradle.kts`); + expect(buildGradle.includes('com.example')).toBeTruthy(); + expect(buildGradle.includes('0.0.1-SNAPSHOT')).toBeTruthy(); + + const testResult = await runNxCommandAsync(`test ${libName}`); + expect(testResult.stdout).toContain('Executor ran for Test'); + + const buildResult = await runNxCommandAsync(`build ${libName}`); + expect(buildResult.stdout).toContain('Executor ran for Build'); + }, 120000); + + it('should create a kotlin library with spring-boot-4', async () => { + const libName = uniq('g-sb4-lib-'); + + await runNxCommandAsync( + `generate @jnxplus/nx-gradle:library ${libName} --framework spring-boot-4 --language kotlin`, + ); + + expect(() => + checkFilesExist( + `libs/${libName}/build.gradle.kts`, + `libs/${libName}/src/main/kotlin/com/example/${names( + libName, + ).className.toLocaleLowerCase()}/HelloService.kt`, + `libs/${libName}/src/test/kotlin/com/example/${names( + libName, + ).className.toLocaleLowerCase()}/TestConfiguration.kt`, + `libs/${libName}/src/test/kotlin/com/example/${names( + libName, + ).className.toLocaleLowerCase()}/HelloServiceTests.kt`, + ), + ).not.toThrow(); + + const buildGradle = readFile(`libs/${libName}/build.gradle.kts`); + expect(buildGradle.includes('com.example')).toBeTruthy(); + expect(buildGradle.includes('0.0.1-SNAPSHOT')).toBeTruthy(); + + const testResult = await runNxCommandAsync(`test ${libName}`); + expect(testResult.stdout).toContain('Executor ran for Test'); + + const buildResult = await runNxCommandAsync(`build ${libName}`); + expect(buildResult.stdout).toContain('Executor ran for Build'); + }, 120000); + + it('should add a lib to an app dependencies', async () => { + const appName = uniq('g-sb4-app-'); + const libName = uniq('g-sb4-lib-'); + + await runNxCommandAsync( + `generate @jnxplus/nx-gradle:application ${appName} --framework spring-boot-4`, + ); + + await runNxCommandAsync( + `generate @jnxplus/nx-gradle:library ${libName} --framework spring-boot-4 --projects ${appName}`, + ); + + const buildGradle = readFile(`apps/${appName}/build.gradle.kts`); + expect(buildGradle.includes(`:${libName}`)).toBeTruthy(); + + const helloControllerPath = `apps/${appName}/src/main/java/com/example/${names( + appName, + ).className.toLocaleLowerCase()}/HelloController.java`; + const helloControllerContent = readFile(helloControllerPath); + + const regex1 = /package\s*com\.example\..*\s*;/; + const regex2 = /public\s*class\s*HelloController\s*{/; + const regex3 = /"Hello World!"/; + + const newHelloControllerContent = helloControllerContent + .replace( + regex1, + `$&\nimport org.springframework.beans.factory.annotation.Autowired;\nimport com.example.${names( + libName, + ).className.toLocaleLowerCase()}.HelloService;`, + ) + .replace(regex2, '$&\n@Autowired\nprivate HelloService helloService;') + .replace(regex3, 'this.helloService.message()'); + + updateFile(helloControllerPath, newHelloControllerContent); + + const testResult = await runNxCommandAsync(`test ${appName}`); + expect(testResult.stdout).toContain('Executor ran for Test'); + + const buildResult = await runNxCommandAsync(`build ${appName}`); + expect(buildResult.stdout).toContain('Executor ran for Build'); + + await runNxCommandAsync(`dep-graph --file=dep-graph.json`); + const depGraphJson = readJson('dep-graph.json'); + expect(depGraphJson.graph.nodes[rootProjectName]).toBeDefined(); + expect(depGraphJson.graph.nodes[appName]).toBeDefined(); + expect(depGraphJson.graph.nodes[libName]).toBeDefined(); + + expect(depGraphJson.graph.dependencies[appName]).toContainEqual({ + type: 'static', + source: appName, + target: libName, + }); + }, 120000); + + it('should add a kotlin lib to a kotlin app dependencies', async () => { + const appName = uniq('g-sb4-app-'); + const libName = uniq('g-sb4-lib-'); + + await runNxCommandAsync( + `generate @jnxplus/nx-gradle:application ${appName} --framework spring-boot-4 --language kotlin --packaging war`, + ); + + await runNxCommandAsync( + `generate @jnxplus/nx-gradle:library ${libName} --framework spring-boot-4 --language kotlin --projects ${appName}`, + ); + + expect(() => + checkFilesExist( + `apps/${appName}/src/main/kotlin/com/example/${names( + appName, + ).className.toLocaleLowerCase()}/ServletInitializer.kt`, + ), + ).not.toThrow(); + + const buildGradle = readFile(`apps/${appName}/build.gradle.kts`); + expect(buildGradle.includes(`:${libName}`)).toBeTruthy(); + + const helloControllerPath = `apps/${appName}/src/main/kotlin/com/example/${names( + appName, + ).className.toLocaleLowerCase()}/HelloController.kt`; + const helloControllerContent = readFile(helloControllerPath); + + const regex1 = /package\s*com\.example\..*/; + const regex2 = /class\s*HelloController/; + const regex3 = /"Hello World!"/; + + const newHelloControllerContent = helloControllerContent + .replace( + regex1, + `$&\nimport org.springframework.beans.factory.annotation.Autowired\nimport com.example.${names( + libName, + ).className.toLocaleLowerCase()}.HelloService`, + ) + .replace(regex2, '$&(@Autowired val helloService: HelloService)') + .replace(regex3, 'helloService.message()'); + + updateFile(helloControllerPath, newHelloControllerContent); + + const testResult = await runNxCommandAsync(`test ${appName}`); + expect(testResult.stdout).toContain('Executor ran for Test'); + + const buildResult = await runNxCommandAsync(`build ${appName}`); + expect(buildResult.stdout).toContain('Executor ran for Build'); + + await runNxCommandAsync(`dep-graph --file=dep-graph.json`); + const depGraphJson = readJson('dep-graph.json'); + expect(depGraphJson.graph.nodes[rootProjectName]).toBeDefined(); + expect(depGraphJson.graph.nodes[appName]).toBeDefined(); + expect(depGraphJson.graph.nodes[libName]).toBeDefined(); + + expect(depGraphJson.graph.dependencies[appName]).toContainEqual({ + type: 'static', + source: appName, + target: libName, + }); + }, 120000); + + it('should create a minimal java application', async () => { + const appName = uniq('g-sb4-app-'); + const port = 8282; + + await runNxCommandAsync( + `generate @jnxplus/nx-gradle:application ${appName} --framework spring-boot-4 --minimal --port ${port}`, + ); + + expect(() => + checkFilesExist( + `apps/${appName}/build.gradle.kts`, + `apps/${appName}/src/main/java/com/example/${names( + appName, + ).className.toLocaleLowerCase()}/${ + names(appName).className + }Application.java`, + `apps/${appName}/src/main/resources/application.properties`, + `apps/${appName}/src/test/java/com/example/${names( + appName, + ).className.toLocaleLowerCase()}/${ + names(appName).className + }ApplicationTests.java`, + ), + ).not.toThrow(); + + expect(() => + checkFilesDoNotExist( + `apps/${appName}/src/main/java/com/example/${names( + appName, + ).className.toLocaleLowerCase()}/HelloController.java`, + `apps/${appName}/src/test/java/com/example/${names( + appName, + ).className.toLocaleLowerCase()}/HelloControllerTests.java`, + ), + ).not.toThrow(); + + const process = await runNxCommandUntil(`serve ${appName}`, (output) => + output.includes(`Tomcat started on port ${port}`), + ); + + await killProcessAndPorts(process.pid, port); + }, 120000); + + it('should skip starter code when generating a java library with skipStarterCode option', async () => { + const libName = uniq('g-sb4-lib-'); + + await runNxCommandAsync( + `generate @jnxplus/nx-gradle:library ${libName} --framework spring-boot-4 --skipStarterCode`, + ); + + expect(() => + checkFilesExist(`libs/${libName}/build.gradle.kts`), + ).not.toThrow(); + + expect(() => + checkFilesDoNotExist( + `libs/${libName}/src/main/java/com/example/${names( + libName, + ).className.toLocaleLowerCase()}/HelloService.java`, + `libs/${libName}/src/test/java/com/example/${names( + libName, + ).className.toLocaleLowerCase()}/HelloServiceTests.java`, + ), + ).not.toThrow(); + }, 120000); +}); diff --git a/testing-projects/jnxplus-e2e/tests/nx-gradle/spring-boot-4.spec.ts b/testing-projects/jnxplus-e2e/tests/nx-gradle/spring-boot-4.spec.ts new file mode 100644 index 000000000..a2e458edf --- /dev/null +++ b/testing-projects/jnxplus-e2e/tests/nx-gradle/spring-boot-4.spec.ts @@ -0,0 +1,227 @@ +import { normalizeName } from '@jnxplus/common'; +import { + createTestWorkspace, + getData, + killProcessAndPorts, + runNxCommandUntil, +} from '@jnxplus/internal/testing'; +import { names } from '@nx/devkit'; +import { + checkFilesExist, + readFile, + readJson, + runNxCommandAsync, + uniq, +} from '@nx/plugin/testing'; +import { execSync } from 'child_process'; +import { rmSync } from 'fs'; + +describe('nx-gradle spring-boot-4 e2e', () => { + let workspaceDirectory: string; + + const rootProjectName = uniq('root-project-'); + + beforeAll(async () => { + workspaceDirectory = createTestWorkspace(); + + execSync(`npm install -D @jnxplus/nx-gradle@e2e`, { + cwd: workspaceDirectory, + stdio: 'inherit', + env: process.env, + }); + + await runNxCommandAsync( + `generate @jnxplus/nx-gradle:init --rootProjectName ${rootProjectName} --preset spring-boot-4`, + ); + }, 120000); + + afterAll(async () => { + if (process.env['SKIP_E2E_CLEANUP'] !== 'true') { + rmSync(workspaceDirectory, { + recursive: true, + force: true, + }); + } + }); + + it('should init the workspace with spring-boot-4 preset', async () => { + const packageJson = readJson('package.json'); + expect(packageJson.devDependencies['@jnxplus/nx-gradle']).toBeTruthy(); + + expect(() => + checkFilesExist( + 'gradle/wrapper/gradle-wrapper.jar', + 'gradle/wrapper/gradle-wrapper.properties', + 'gradlew', + 'gradlew.bat', + 'gradle.properties', + 'settings.gradle', + ), + ).not.toThrow(); + + // Check gradle.properties contains Spring Boot 4 version + const gradleProperties = readFile('gradle.properties'); + expect(gradleProperties.includes('springBootVersion=4.0.0')).toBeTruthy(); + }, 120000); + + it('should create a java application with spring-boot-4', async () => { + const appName = uniq('g-sb4-app-'); + + await runNxCommandAsync( + `generate @jnxplus/nx-gradle:application ${appName} --framework spring-boot-4`, + ); + + expect(() => + checkFilesExist( + `apps/${appName}/build.gradle`, + `apps/${appName}/src/main/resources/application.properties`, + `apps/${appName}/src/main/java/com/example/${names( + appName, + ).className.toLocaleLowerCase()}/${ + names(appName).className + }Application.java`, + `apps/${appName}/src/main/java/com/example/${names( + appName, + ).className.toLocaleLowerCase()}/HelloController.java`, + ), + ).not.toThrow(); + + const buildResult = await runNxCommandAsync(`build ${appName}`); + expect(buildResult.stdout).toContain('Executor ran for Build'); + + const testResult = await runNxCommandAsync(`test ${appName}`); + expect(testResult.stdout).toContain('Executor ran for Test'); + + const process = await runNxCommandUntil(`serve ${appName}`, (output) => + output.includes(`Tomcat started on port 8080`), + ); + + const dataResult = await getData(); + expect(dataResult.status).toEqual(200); + expect(dataResult.message).toMatch('Hello World!'); + + await killProcessAndPorts(process.pid, 8080); + }, 240000); + + it('should create a kotlin application with spring-boot-4', async () => { + const appName = uniq('g-sb4-app-'); + const port = 8181; + + await runNxCommandAsync( + `generate @jnxplus/nx-gradle:application ${appName} --framework spring-boot-4 --language kotlin --port ${port}`, + ); + + expect(() => + checkFilesExist( + `apps/${appName}/build.gradle`, + `apps/${appName}/src/main/resources/application.properties`, + `apps/${appName}/src/main/kotlin/com/example/${names( + appName, + ).className.toLocaleLowerCase()}/${ + names(appName).className + }Application.kt`, + `apps/${appName}/src/main/kotlin/com/example/${names( + appName, + ).className.toLocaleLowerCase()}/HelloController.kt`, + ), + ).not.toThrow(); + + const buildResult = await runNxCommandAsync(`build ${appName}`); + expect(buildResult.stdout).toContain('Executor ran for Build'); + + const testResult = await runNxCommandAsync(`test ${appName}`); + expect(testResult.stdout).toContain('Executor ran for Test'); + + const process = await runNxCommandUntil(`serve ${appName}`, (output) => + output.includes(`Tomcat started on port ${port}`), + ); + + const dataResult = await getData(port); + expect(dataResult.status).toEqual(200); + expect(dataResult.message).toMatch('Hello World!'); + + await killProcessAndPorts(process.pid, port); + }, 240000); + + it('should create a java library with spring-boot-4', async () => { + const libName = uniq('g-sb4-lib-'); + + await runNxCommandAsync( + `generate @jnxplus/nx-gradle:library ${libName} --framework spring-boot-4`, + ); + + expect(() => + checkFilesExist( + `libs/${libName}/build.gradle`, + `libs/${libName}/src/main/java/com/example/${names( + libName, + ).className.toLocaleLowerCase()}/HelloService.java`, + `libs/${libName}/src/test/java/com/example/${names( + libName, + ).className.toLocaleLowerCase()}/HelloServiceTests.java`, + ), + ).not.toThrow(); + + const buildResult = await runNxCommandAsync(`build ${libName}`); + expect(buildResult.stdout).toContain('Executor ran for Build'); + + const testResult = await runNxCommandAsync(`test ${libName}`); + expect(testResult.stdout).toContain('Executor ran for Test'); + }, 120000); + + it('should create a kotlin library with spring-boot-4', async () => { + const libName = uniq('g-sb4-lib-'); + + await runNxCommandAsync( + `generate @jnxplus/nx-gradle:library ${libName} --framework spring-boot-4 --language kotlin`, + ); + + expect(() => + checkFilesExist( + `libs/${libName}/build.gradle`, + `libs/${libName}/src/main/kotlin/com/example/${names( + libName, + ).className.toLocaleLowerCase()}/HelloService.kt`, + `libs/${libName}/src/test/kotlin/com/example/${names( + libName, + ).className.toLocaleLowerCase()}/HelloServiceTests.kt`, + ), + ).not.toThrow(); + + const buildResult = await runNxCommandAsync(`build ${libName}`); + expect(buildResult.stdout).toContain('Executor ran for Build'); + + const testResult = await runNxCommandAsync(`test ${libName}`); + expect(testResult.stdout).toContain('Executor ran for Test'); + }, 120000); + + it('should add a lib to an app dependencies', async () => { + const appName = uniq('g-sb4-app-'); + const libName = uniq('g-sb4-lib-'); + + await runNxCommandAsync( + `generate @jnxplus/nx-gradle:application ${appName} --framework spring-boot-4`, + ); + + await runNxCommandAsync( + `generate @jnxplus/nx-gradle:library ${libName} --framework spring-boot-4 --projects ${appName}`, + ); + + const buildGradle = readFile(`apps/${appName}/build.gradle`); + expect(buildGradle.includes(`:${libName}`)).toBeTruthy(); + + const buildResult = await runNxCommandAsync(`build ${appName}`); + expect(buildResult.stdout).toContain('Executor ran for Build'); + + await runNxCommandAsync(`dep-graph --file=dep-graph.json`); + const depGraphJson = readJson('dep-graph.json'); + expect(depGraphJson.graph.nodes[appName]).toBeDefined(); + expect(depGraphJson.graph.nodes[libName]).toBeDefined(); + + expect(depGraphJson.graph.dependencies[appName]).toContainEqual({ + type: 'static', + source: appName, + target: libName, + }); + }, 120000); +}); diff --git a/testing-projects/jnxplus-e2e/tests/nx-maven/spring-boot-4-parent-pom.spec.ts b/testing-projects/jnxplus-e2e/tests/nx-maven/spring-boot-4-parent-pom.spec.ts new file mode 100644 index 000000000..4daf20e09 --- /dev/null +++ b/testing-projects/jnxplus-e2e/tests/nx-maven/spring-boot-4-parent-pom.spec.ts @@ -0,0 +1,224 @@ +import { normalizeName } from '@jnxplus/common'; +import { + createTestWorkspace, + getData, + killProcessAndPorts, + runNxCommandUntil, +} from '@jnxplus/internal/testing'; +import { names } from '@nx/devkit'; +import { + checkFilesExist, + readFile, + readJson, + runNxCommandAsync, + uniq, +} from '@nx/plugin/testing'; +import { execSync } from 'child_process'; +import { rmSync } from 'fs'; + +describe('nx-maven spring-boot-4 parent-pom e2e', () => { + let workspaceDirectory: string; + + beforeAll(async () => { + workspaceDirectory = createTestWorkspace(); + + execSync(`npm install -D @jnxplus/nx-maven@e2e`, { + cwd: workspaceDirectory, + stdio: 'inherit', + env: process.env, + }); + + await runNxCommandAsync( + `generate @jnxplus/nx-maven:init --dependencyManagement spring-boot-4-parent-pom`, + ); + }, 120000); + + afterAll(async () => { + if (process.env['SKIP_E2E_CLEANUP'] !== 'true') { + rmSync(workspaceDirectory, { + recursive: true, + force: true, + }); + } + }); + + it('should init the workspace with spring-boot-4-parent-pom', async () => { + const packageJson = readJson('package.json'); + expect(packageJson.devDependencies['@jnxplus/nx-maven']).toBeTruthy(); + + expect(() => + checkFilesExist( + '.mvn/wrapper/maven-wrapper.properties', + 'mvnw', + 'mvnw.cmd', + 'pom.xml', + ), + ).not.toThrow(); + + // Check pom.xml contains Spring Boot 4 parent + const pomXml = readFile('pom.xml'); + expect(pomXml.includes('spring-boot-starter-parent')).toBeTruthy(); + expect(pomXml.includes('4.0.0')).toBeTruthy(); + }, 120000); + + it('should create a java application with spring-boot-4', async () => { + const appName = uniq('m-sb4-app-'); + + await runNxCommandAsync( + `generate @jnxplus/nx-maven:application ${appName} --framework spring-boot-4`, + ); + + expect(() => + checkFilesExist( + `apps/${appName}/pom.xml`, + `apps/${appName}/src/main/resources/application.properties`, + `apps/${appName}/src/main/java/com/example/${names( + appName, + ).className.toLocaleLowerCase()}/${ + names(appName).className + }Application.java`, + `apps/${appName}/src/main/java/com/example/${names( + appName, + ).className.toLocaleLowerCase()}/HelloController.java`, + ), + ).not.toThrow(); + + const buildResult = await runNxCommandAsync(`build ${appName}`); + expect(buildResult.stdout).toContain('Executor ran for Build'); + + const testResult = await runNxCommandAsync(`test ${appName}`); + expect(testResult.stdout).toContain('Executor ran for Test'); + + const process = await runNxCommandUntil(`serve ${appName}`, (output) => + output.includes(`Tomcat started on port 8080`), + ); + + const dataResult = await getData(); + expect(dataResult.status).toEqual(200); + expect(dataResult.message).toMatch('Hello World!'); + + await killProcessAndPorts(process.pid, 8080); + }, 240000); + + it('should create a kotlin application with spring-boot-4', async () => { + const appName = uniq('m-sb4-app-'); + const port = 8282; + + await runNxCommandAsync( + `generate @jnxplus/nx-maven:application ${appName} --framework spring-boot-4 --language kotlin --port ${port}`, + ); + + expect(() => + checkFilesExist( + `apps/${appName}/pom.xml`, + `apps/${appName}/src/main/resources/application.properties`, + `apps/${appName}/src/main/kotlin/com/example/${names( + appName, + ).className.toLocaleLowerCase()}/${ + names(appName).className + }Application.kt`, + `apps/${appName}/src/main/kotlin/com/example/${names( + appName, + ).className.toLocaleLowerCase()}/HelloController.kt`, + ), + ).not.toThrow(); + + const buildResult = await runNxCommandAsync(`build ${appName}`); + expect(buildResult.stdout).toContain('Executor ran for Build'); + + const testResult = await runNxCommandAsync(`test ${appName}`); + expect(testResult.stdout).toContain('Executor ran for Test'); + + const process = await runNxCommandUntil(`serve ${appName}`, (output) => + output.includes(`Tomcat started on port ${port}`), + ); + + const dataResult = await getData(port); + expect(dataResult.status).toEqual(200); + expect(dataResult.message).toMatch('Hello World!'); + + await killProcessAndPorts(process.pid, port); + }, 240000); + + it('should create a java library with spring-boot-4', async () => { + const libName = uniq('m-sb4-lib-'); + + await runNxCommandAsync( + `generate @jnxplus/nx-maven:library ${libName} --framework spring-boot-4`, + ); + + expect(() => + checkFilesExist( + `libs/${libName}/pom.xml`, + `libs/${libName}/src/main/java/com/example/${names( + libName, + ).className.toLocaleLowerCase()}/HelloService.java`, + `libs/${libName}/src/test/java/com/example/${names( + libName, + ).className.toLocaleLowerCase()}/HelloServiceTests.java`, + ), + ).not.toThrow(); + + const buildResult = await runNxCommandAsync(`build ${libName}`); + expect(buildResult.stdout).toContain('Executor ran for Build'); + + const testResult = await runNxCommandAsync(`test ${libName}`); + expect(testResult.stdout).toContain('Executor ran for Test'); + }, 120000); + + it('should create a kotlin library with spring-boot-4', async () => { + const libName = uniq('m-sb4-lib-'); + + await runNxCommandAsync( + `generate @jnxplus/nx-maven:library ${libName} --framework spring-boot-4 --language kotlin`, + ); + + expect(() => + checkFilesExist( + `libs/${libName}/pom.xml`, + `libs/${libName}/src/main/kotlin/com/example/${names( + libName, + ).className.toLocaleLowerCase()}/HelloService.kt`, + `libs/${libName}/src/test/kotlin/com/example/${names( + libName, + ).className.toLocaleLowerCase()}/HelloServiceTests.kt`, + ), + ).not.toThrow(); + + const buildResult = await runNxCommandAsync(`build ${libName}`); + expect(buildResult.stdout).toContain('Executor ran for Build'); + + const testResult = await runNxCommandAsync(`test ${libName}`); + expect(testResult.stdout).toContain('Executor ran for Test'); + }, 120000); + + it('should add a lib to an app dependencies', async () => { + const appName = uniq('m-sb4-app-'); + const libName = uniq('m-sb4-lib-'); + + await runNxCommandAsync( + `generate @jnxplus/nx-maven:application ${appName} --framework spring-boot-4`, + ); + + await runNxCommandAsync( + `generate @jnxplus/nx-maven:library ${libName} --framework spring-boot-4 --projects ${appName}`, + ); + + const pomXml = readFile(`apps/${appName}/pom.xml`); + expect(pomXml.includes(libName)).toBeTruthy(); + + const buildResult = await runNxCommandAsync(`build ${appName}`); + expect(buildResult.stdout).toContain('Executor ran for Build'); + + await runNxCommandAsync(`dep-graph --file=dep-graph.json`); + const depGraphJson = readJson('dep-graph.json'); + expect(depGraphJson.graph.nodes[appName]).toBeDefined(); + expect(depGraphJson.graph.nodes[libName]).toBeDefined(); + + expect(depGraphJson.graph.dependencies[appName]).toContainEqual({ + type: 'static', + source: appName, + target: libName, + }); + }, 120000); +});