Skip to content

Commit 3d76dc7

Browse files
committed
fix: insert add-ons into the existing dependencies block
1 parent fdc57a0 commit 3d76dc7

3 files changed

Lines changed: 46 additions & 9 deletions

File tree

‎docs/features/add-ons.md‎

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,9 @@ and does not fetch the catalog or resolve add-on dependencies.
4141
Additional add-ons require a compatible development JDK before generation.
4242
The installer adds dependencies without duplicating template entries, resolves
4343
artifacts with the generated Gradle wrapper, and reads module metadata from JARs.
44+
Selected dependencies go at the start of the module's existing `dependencies`
45+
block under `// Selected Jmix add-ons`. Test security prerequisites use the same
46+
block; installation does not append additional `dependencies` blocks.
4447
It configures Liquibase includes and, for add-on templates, module dependencies
4548
and test security. Configuration completes before Git staging.
4649

‎src/main/kotlin/io/jmix/cli/generator/AddonInstaller.kt‎

Lines changed: 15 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,6 @@ import java.io.ByteArrayInputStream
1818
import java.io.IOException
1919
import java.nio.file.Files
2020
import java.nio.file.Path
21-
import java.nio.file.StandardOpenOption
2221
import java.util.concurrent.TimeUnit
2322
import java.util.jar.JarFile
2423
import javax.xml.XMLConstants
@@ -66,10 +65,20 @@ object AddonInstaller {
6665
}
6766
}
6867
if (additions.isEmpty()) return
69-
val block = additions.entries.joinToString("\n", "\n// Selected Jmix add-ons\ndependencies {\n", "\n}\n") { (coordinates, value) ->
70-
" ${value.first} '$coordinates:${value.second}'"
68+
val declarations = additions.entries.joinToString("\n", "// Selected Jmix add-ons\n") { (coordinates, value) ->
69+
"${value.first} '$coordinates:${value.second}'"
7170
}
72-
Files.writeString(buildFile, block, StandardOpenOption.APPEND)
71+
prependDependencies(buildFile, declarations)
72+
}
73+
74+
private fun prependDependencies(buildFile: Path, declarations: String) {
75+
val text = Files.readString(buildFile)
76+
val block = Regex("(?m)^dependencies[ \\t]*\\{").find(text)
77+
?: throw IOException("Cannot find the generated project's top-level dependencies block in $buildFile.")
78+
val offset = block.range.last + 1
79+
val newline = if ("\r\n" in text) "\r\n" else "\n"
80+
val insertion = newline + declarations.prependIndent(" ").replace("\n", newline) + newline
81+
Files.writeString(buildFile, text.replaceRange(offset, offset, insertion))
7382
}
7483

7584
private fun resolveArtifacts(projectDir: Path, moduleDir: Path, javaHome: Path): List<Artifact> {
@@ -205,8 +214,8 @@ object AddonInstaller {
205214
?: throw IOException("Cannot identify the generated add-on's test configuration for Jmix Security.")
206215
val starter = "io.jmix.security:jmix-security-starter"
207216
if (starter !in AddonProjectProfile.dependencyCoordinates(Files.readString(buildFile))) {
208-
Files.writeString(buildFile, "\n// Security bootstrap for generated add-on tests\ndependencies {\n" +
209-
" testImplementation '$starter'\n}\n", StandardOpenOption.APPEND)
217+
prependDependencies(buildFile, "// Security bootstrap for generated add-on tests\n" +
218+
"testImplementation '$starter'")
210219
}
211220
if (files.any { Files.readString(it).contains("UserRepository") }) return
212221
val text = Files.readString(config)

‎src/test/kotlin/io/jmix/cli/generator/AddonInstallerTest.kt‎

Lines changed: 28 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@ import java.util.jar.JarOutputStream
1515
import org.junit.jupiter.api.Assertions.*
1616
import org.junit.jupiter.api.Test
1717
import org.junit.jupiter.api.io.TempDir
18+
import org.junit.jupiter.params.ParameterizedTest
19+
import org.junit.jupiter.params.provider.ValueSource
1820

1921
class AddonInstallerTest {
2022
@TempDir lateinit var tempDir: Path
@@ -66,15 +68,37 @@ class AddonInstallerTest {
6668
assertThrows(IOException::class.java) { AddonInstaller.sortModules(listOf(first, second.copy(dependsOn = listOf("A")))) }
6769
}
6870

69-
@Test
70-
fun `dependencies retain template entries and do not duplicate selections`() {
71+
@ParameterizedTest
72+
@ValueSource(strings = ["\n", "\r\n"])
73+
fun `dependencies start the main block and do not duplicate selections`(newline: String) {
7174
val build = tempDir.resolve("build.gradle")
72-
Files.writeString(build, "dependencies { implementation 'demo:existing' }\n")
75+
val buildscript = """
76+
buildscript {
77+
dependencies { classpath 'demo:plugin' }
78+
}
79+
80+
""".trimIndent().replace("\n", newline)
81+
Files.writeString(build, buildscript + "dependencies {${newline} implementation 'demo:existing'${newline}}$newline")
7382
AddonInstaller.appendDependencies(info(), build)
7483
AddonInstaller.appendDependencies(info(), build)
7584
val text = Files.readString(build)
85+
assertTrue(text.startsWith(buildscript))
86+
assertTrue(text.contains("dependencies {$newline // Selected Jmix add-ons$newline implementation 'demo:sample-starter:3.0.1'"))
87+
assertEquals(1, Regex("(?m)^dependencies").findAll(text).count())
7688
assertTrue(text.contains("demo:existing"))
89+
assertTrue(text.indexOf("demo:sample-starter") < text.indexOf("demo:existing"))
7790
assertEquals(1, Regex("demo:sample-starter:3.0.1").findAll(text).count())
91+
if (newline == "\r\n") assertFalse(text.replace(newline, "").contains('\n'))
92+
}
93+
94+
@Test
95+
fun `missing main dependencies block leaves the build file unchanged`() {
96+
val build = tempDir.resolve("build.gradle")
97+
val text = "buildscript {\n dependencies { classpath 'demo:plugin' }\n}\n"
98+
Files.writeString(build, text)
99+
100+
assertThrows(IOException::class.java) { AddonInstaller.appendDependencies(info(), build) }
101+
assertEquals(text, Files.readString(build))
78102
}
79103

80104
@Test
@@ -178,6 +202,7 @@ class AddonInstallerTest {
178202
assertFalse(Files.readString(build).contains("security-starter"))
179203
repeat(2) { AddonInstaller.configureTestSecurity(build, listOf(security)) }
180204
assertEquals(1, Regex("testImplementation 'io.jmix.security:jmix-security-starter'").findAll(Files.readString(build)).count())
205+
assertEquals(1, Regex("(?m)^dependencies").findAll(Files.readString(build)).count())
181206
assertEquals(if (repository.isEmpty()) 1 else 0,
182207
Regex("addonTestUserRepository").findAll(Files.readString(config)).count())
183208
if (extension == "kt") assertTrue(Files.readString(config).contains("open fun addonTestUserRepository"))

0 commit comments

Comments
 (0)