diff --git a/.github/workflows/generator-database-changelog-liquibase.yml b/.github/workflows/generator-database-changelog-liquibase.yml index fa2ed9ae95f5..fe1fd1a79278 100644 --- a/.github/workflows/generator-database-changelog-liquibase.yml +++ b/.github/workflows/generator-database-changelog-liquibase.yml @@ -103,6 +103,8 @@ jobs: #---------------------------------------------------------------------- # Launch tests #---------------------------------------------------------------------- + - name: 'TESTS: check liquibase:diff' + run: ./mvnw liquibase:diff - name: 'TESTS: backend' run: npm run ci:backend:test - name: 'TESTS: frontend' diff --git a/generators/app/__snapshots__/generator.spec.ts.snap b/generators/app/__snapshots__/generator.spec.ts.snap index 763cef5fbab3..3ba08d9793a7 100644 --- a/generators/app/__snapshots__/generator.spec.ts.snap +++ b/generators/app/__snapshots__/generator.spec.ts.snap @@ -518,17 +518,17 @@ exports[`generator - app with default config should match snapshot 1`] = ` "sonar-maven-plugin": "'SONAR-MAVEN-PLUGIN-VERSION'", "spotless-gradle-plugin": "'SPOTLESS-GRADLE-PLUGIN-VERSION'", "spotless-maven-plugin": "'SPOTLESS-MAVEN-PLUGIN-VERSION'", - "spring-boot": "'SPRING-BOOT-VERSION'", "spring-cloud-dependencies": "'SPRING-CLOUD-DEPENDENCIES-VERSION'", - "spring-dependency-management": "'SPRING-DEPENDENCY-MANAGEMENT-VERSION'", "springdoc": "'SPRINGDOC-VERSION'", "testng": "'TESTNG-VERSION'", "typesafe": "'TYPESAFE-VERSION'", "xmemcached": "'XMEMCACHED-VERSION'", "xmemcached-provider": "'XMEMCACHED-PROVIDER-VERSION'", }, + "javaManagedProperties": {}, "javaPackageSrcDir": "src/main/java/com/mycompany/myapp/", "javaPackageTestDir": "src/test/java/com/mycompany/myapp/", + "javaProperties": {}, "javaVersion": "JAVA_VERSION", "jhiPrefix": "jhi", "jhiPrefixCapitalized": "Jhi", @@ -1091,14 +1091,14 @@ exports[`generator - app with gateway should match snapshot 1`] = ` "sonar-maven-plugin": "'SONAR-MAVEN-PLUGIN-VERSION'", "spotless-gradle-plugin": "'SPOTLESS-GRADLE-PLUGIN-VERSION'", "spotless-maven-plugin": "'SPOTLESS-MAVEN-PLUGIN-VERSION'", - "spring-boot": "'SPRING-BOOT-VERSION'", "spring-cloud-dependencies": "'SPRING-CLOUD-DEPENDENCIES-VERSION'", - "spring-dependency-management": "'SPRING-DEPENDENCY-MANAGEMENT-VERSION'", "springdoc": "'SPRINGDOC-VERSION'", "testng": "'TESTNG-VERSION'", }, + "javaManagedProperties": {}, "javaPackageSrcDir": "src/main/java/com/mycompany/myapp/", "javaPackageTestDir": "src/test/java/com/mycompany/myapp/", + "javaProperties": {}, "javaVersion": "JAVA_VERSION", "jhiPrefix": "jhi", "jhiPrefixCapitalized": "Jhi", @@ -1664,17 +1664,17 @@ exports[`generator - app with microservice should match snapshot 1`] = ` "sonar-maven-plugin": "'SONAR-MAVEN-PLUGIN-VERSION'", "spotless-gradle-plugin": "'SPOTLESS-GRADLE-PLUGIN-VERSION'", "spotless-maven-plugin": "'SPOTLESS-MAVEN-PLUGIN-VERSION'", - "spring-boot": "'SPRING-BOOT-VERSION'", "spring-cloud-dependencies": "'SPRING-CLOUD-DEPENDENCIES-VERSION'", - "spring-dependency-management": "'SPRING-DEPENDENCY-MANAGEMENT-VERSION'", "springdoc": "'SPRINGDOC-VERSION'", "testng": "'TESTNG-VERSION'", "typesafe": "'TYPESAFE-VERSION'", "xmemcached": "'XMEMCACHED-VERSION'", "xmemcached-provider": "'XMEMCACHED-PROVIDER-VERSION'", }, + "javaManagedProperties": {}, "javaPackageSrcDir": "src/main/java/com/mycompany/myapp/", "javaPackageTestDir": "src/test/java/com/mycompany/myapp/", + "javaProperties": {}, "javaVersion": "JAVA_VERSION", "jhiPrefix": "jhi", "jhiPrefixCapitalized": "Jhi", diff --git a/generators/base-core/generator.ts b/generators/base-core/generator.ts index d9224ce48575..e8cec3efcabe 100644 --- a/generators/base-core/generator.ts +++ b/generators/base-core/generator.ts @@ -27,7 +27,7 @@ import { GeneratorMeta } from '@yeoman/types'; import chalk from 'chalk'; import { parse as parseYaml, stringify as stringifyYaml } from 'yaml'; import * as _ from 'lodash-es'; -import { kebabCase } from 'lodash-es'; +import { kebabCase, snakeCase } from 'lodash-es'; import { simpleGit } from 'simple-git'; import type { CopyOptions } from 'mem-fs-editor'; import type { Data as TemplateData, Options as TemplateOptions } from 'ejs'; @@ -60,6 +60,7 @@ import command from '../base/command.js'; import { GENERATOR_JHIPSTER, YO_RC_FILE } from '../generator-constants.js'; import { convertConfigToOption } from '../../lib/internal/index.js'; import { getGradleLibsVersionsProperties } from '../gradle/support/dependabot-gradle.js'; +import { dockerPlaceholderGenerator } from '../docker/utils.js'; const { merge, get, set } = _; const { @@ -1000,10 +1001,18 @@ templates: ${JSON.stringify(existingTemplates, null, 2)}`; */ prepareDependencies( map: Record, - valuePlaceholder: (value: string) => string = value => `${_.snakeCase(value).toUpperCase()}_VERSION`, + valuePlaceholder?: 'java' | 'docker' | ((value: string) => string), ): Record { + let placeholder: (value: string) => string; + if (valuePlaceholder === 'java') { + placeholder = value => `'${kebabCase(value).toUpperCase()}-VERSION'`; + } else if (valuePlaceholder === 'docker') { + placeholder = dockerPlaceholderGenerator; + } else { + placeholder = valuePlaceholder ?? (value => `${snakeCase(value).toUpperCase()}_VERSION`); + } if (this.useVersionPlaceholders) { - return Object.fromEntries(Object.keys(map).map(dep => [dep, valuePlaceholder(dep)])); + return Object.fromEntries(Object.keys(map).map(dep => [dep, placeholder(dep)])); } return { ...map, @@ -1027,13 +1036,7 @@ templates: ${JSON.stringify(existingTemplates, null, 2)}`; const gradleLibsVersions = this.readTemplate(gradleCatalog)?.toString(); if (gradleLibsVersions) { - Object.assign( - javaDependencies, - this.prepareDependencies( - getGradleLibsVersionsProperties(gradleLibsVersions!), - value => `'${kebabCase(value).toUpperCase()}-VERSION'`, - ), - ); + Object.assign(javaDependencies, this.prepareDependencies(getGradleLibsVersionsProperties(gradleLibsVersions!), 'java')); } } diff --git a/generators/base-workspaces/internal/docker-dependencies.ts b/generators/base-workspaces/internal/docker-dependencies.ts index 05e567e16b32..fe986c9c9b4b 100644 --- a/generators/base-workspaces/internal/docker-dependencies.ts +++ b/generators/base-workspaces/internal/docker-dependencies.ts @@ -17,7 +17,7 @@ * limitations under the License. */ import { dockerContainers as elasticDockerContainer } from '../../generator-constants.js'; -import { dockerPlaceholderGenerator, getDockerfileContainers } from '../../docker/utils.js'; +import { getDockerfileContainers } from '../../docker/utils.js'; export async function loadDockerDependenciesTask(this: any, { context = this } = {}) { const dockerfile = this.readTemplate(this.jhipsterTemplatePath('../../server/resources/Dockerfile')); @@ -26,6 +26,6 @@ export async function loadDockerDependenciesTask(this: any, { context = this } = ...elasticDockerContainer, ...getDockerfileContainers(dockerfile), }, - dockerPlaceholderGenerator, + 'docker', ); } diff --git a/generators/bootstrap-application-server/generator.ts b/generators/bootstrap-application-server/generator.ts index 967a5a7bbf21..99876f1fc288 100644 --- a/generators/bootstrap-application-server/generator.ts +++ b/generators/bootstrap-application-server/generator.ts @@ -16,7 +16,6 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import * as _ from 'lodash-es'; import BaseApplicationGenerator from '../base-application/index.js'; import { GENERATOR_BOOTSTRAP_APPLICATION_BASE, GENERATOR_BOOTSTRAP_APPLICATION_SERVER } from '../generator-list.js'; @@ -43,10 +42,10 @@ import { import { getGradleLibsVersionsProperties } from '../gradle/support/index.js'; import { getPomVersionProperties } from '../maven/support/index.js'; import { prepareField as prepareFieldForLiquibaseTemplates } from '../liquibase/support/index.js'; -import { dockerPlaceholderGenerator, getDockerfileContainers } from '../docker/utils.js'; +import { getDockerfileContainers } from '../docker/utils.js'; import { GRADLE_VERSION } from '../gradle/constants.js'; import { normalizePathEnd } from '../base/support/path.js'; -import { getFrontendAppName } from '../base/support/index.js'; +import { getFrontendAppName, mutateData } from '../base/support/index.js'; import { getMainClassName } from '../java/support/index.js'; import { loadConfig, loadDerivedConfig } from '../../lib/internal/index.js'; import serverCommand from '../server/command.js'; @@ -82,24 +81,36 @@ export default class BoostrapApplicationServer extends BaseApplicationGenerator const gradleLibsVersions = this.readTemplate( this.jhipsterTemplatePath('../../server/resources/gradle/libs.versions.toml'), )?.toString(); - application.packageInfoJavadocs = []; - application.javaDependencies = this.prepareDependencies( + const applicationJavaDependencies = this.prepareDependencies( { ...getPomVersionProperties(pomFile!), ...getGradleLibsVersionsProperties(gradleLibsVersions!), }, - // Gradle doesn't allows snakeCase - value => `'${_.kebabCase(value).toUpperCase()}-VERSION'`, + 'java', ); const dockerfile = this.readTemplate(this.jhipsterTemplatePath('../../server/resources/Dockerfile')); - application.dockerContainers = this.prepareDependencies( + const applicationDockerContainers = this.prepareDependencies( { ...dockerContainers, ...getDockerfileContainers(dockerfile), }, - dockerPlaceholderGenerator, + 'docker', ); + + mutateData(application, { + packageInfoJavadocs: [], + javaProperties: {}, + javaManagedProperties: {}, + javaDependencies: ({ javaDependencies }) => ({ + ...applicationJavaDependencies, + ...javaDependencies, + }), + dockerContainers: ({ dockerContainers: currentDockerContainers = {} }) => ({ + ...applicationDockerContainers, + ...currentDockerContainers, + }), + }); }, }); } diff --git a/generators/gradle/generator.ts b/generators/gradle/generator.ts index 1ed95d3309dc..097c38f272db 100644 --- a/generators/gradle/generator.ts +++ b/generators/gradle/generator.ts @@ -89,7 +89,10 @@ export default class GradleGenerator extends BaseApplicationGenerator { source.addGradlePlugin = plugin => this.editFile('build.gradle', addGradlePluginCallback(plugin)); source.addGradleMavenRepository = repository => this.editFile('build.gradle', addGradleMavenRepositoryCallback(repository)); source.addGradlePluginManagement = plugin => this.editFile('settings.gradle', addGradlePluginManagementCallback(plugin)); - source.addGradleProperty = property => this.editFile('gradle.properties', addGradlePropertyCallback(property)); + source.addGradleProperty = property => { + application.javaProperties![property.property] = property.value!; + this.editFile('gradle.properties', addGradlePropertyCallback(property)); + }; source.addGradleDependencyCatalogVersions = (versions, { gradleVersionCatalogFile = 'gradle/libs.versions.toml' } = {}) => this.editFile(gradleVersionCatalogFile, addGradleDependenciesCatalogVersionCallback(versions)); source.addGradleDependencyCatalogVersion = (version, options) => source.addGradleDependencyCatalogVersions!([version], options); diff --git a/generators/java/generator.ts b/generators/java/generator.ts index ae367e891613..d71f79c84075 100644 --- a/generators/java/generator.ts +++ b/generators/java/generator.ts @@ -93,6 +93,8 @@ export default class JavaGenerator extends BaseApplicationGenerator { get preparing() { return this.asPreparingTaskGroup({ prepareJavaApplication({ application, source }) { + source.hasJavaProperty = (property: string) => application.javaProperties![property] !== undefined; + source.hasJavaManagedProperty = (property: string) => application.javaManagedProperties![property] !== undefined; source.addJavaDependencies = (dependencies, options) => { if (application.buildToolMaven) { const annotationProcessors = dependencies.filter(dep => dep.scope === 'annotationProcessor'); @@ -145,12 +147,20 @@ export default class JavaGenerator extends BaseApplicationGenerator { source.addJavaDefinition = (definition, options) => { const { dependencies, versions } = definition; if (dependencies) { - source.addJavaDependencies!(dependencies, options); + source.addJavaDependencies!( + dependencies.filter(dep => { + if (dep.versionRef) { + return versions?.find(({ name }) => name === dep.versionRef)?.version; + } + return true; + }), + options, + ); } if (versions) { if (application.buildToolMaven) { source.addMavenDefinition!({ - properties: versions.map(({ name, version }) => ({ property: `${name}.version`, value: version })), + properties: versions.filter(v => v.version).map(({ name, version }) => ({ property: `${name}.version`, value: version })), }); } if (application.buildToolGradle) { diff --git a/generators/java/types.d.ts b/generators/java/types.d.ts index a108b62b026f..cc72c2b0af98 100644 --- a/generators/java/types.d.ts +++ b/generators/java/types.d.ts @@ -18,7 +18,7 @@ export type JavaArtifact = { classifier?: string; } & JavaArtifactType; -export type JavaArtifactVersion = RequireOneOrNone<{ version: string; versionRef: string }, 'version' | 'versionRef'>; +export type JavaArtifactVersion = RequireOneOrNone<{ version?: string; versionRef?: string }, 'version' | 'versionRef'>; export type JavaDependency = JavaArtifact & JavaArtifactVersion; @@ -48,7 +48,13 @@ export type JavaApplication = BaseApplication & temporaryDir: string; + /** Java dependency versions */ javaDependencies: Record; + /** Known properties that can be used */ + javaProperties: Record; + /** Known managed properties that can be used */ + javaManagedProperties: Record; + /** Pre-defined package JavaDocs */ packageInfoJavadocs: { packageName: string; documentation: string }[]; prettierJava: boolean; @@ -57,6 +63,13 @@ export type JavaApplication = BaseApplication & }; export type JavaSourceType = { + /** + * Add a JavaDefinition to the application. + * A version requires a valid version otherwise it will be ignored. + * A dependency with versionRef requires a valid referenced version at `versions` otherwise it will be ignored. + */ addJavaDefinition?(definition: JavaDefinition, options?: JavaNeedleOptions): void; addJavaDependencies?(dependency: JavaDependency[], options?: JavaNeedleOptions): void; + hasJavaProperty?(propertyName: string): boolean; + hasJavaManagedProperty?(propertyName: string): boolean; }; diff --git a/generators/languages/languages.spec.js b/generators/languages/languages.spec.js index 67b9895f672e..ec8cbd974a48 100644 --- a/generators/languages/languages.spec.js +++ b/generators/languages/languages.spec.js @@ -14,6 +14,7 @@ const generatorPath = join(__dirname, 'index.js'); const createClientProject = options => basicHelpers .runJHipster('app') + .withMockedGenerators(['jhipster:liquibase']) .withJHipsterConfig() .withOptions({ ...options, diff --git a/generators/liquibase/generator.ts b/generators/liquibase/generator.ts index 0f60519fa5aa..485f2916060e 100644 --- a/generators/liquibase/generator.ts +++ b/generators/liquibase/generator.ts @@ -46,6 +46,7 @@ import { prepareSqlApplicationProperties } from '../spring-data-relational/suppo import { addEntityFiles, updateEntityFiles, updateConstraintsFiles, updateMigrateFiles, fakeFiles } from './changelog-files.js'; import { fieldTypes } from '../../jdl/jhipster/index.js'; import command from './command.js'; +import type { MavenProperty } from '../maven/types.js'; const { CommonDBTypes: { LONG: TYPE_LONG, INTEGER: TYPE_INTEGER }, @@ -335,37 +336,66 @@ export default class LiquibaseGenerator extends BaseEntityChangesGenerator { throw new Error('Some application fields are be mandatory'); } + const { javaDependencies } = application; + const checkProperty = (property: string) => { + if (!source.hasJavaManagedProperty?.(property) && !source.hasJavaProperty?.(property)) { + const message = `${property} is required by maven-liquibase-plugin, make sure to add it to your pom.xml`; + if (this.skipChecks) { + this.log.warn(message); + } else { + throw new Error(message); + } + } + }; + + const { 'jakarta-validation': validationVersion, h2: h2Version, liquibase: liquibaseVersion } = javaDependencies; + const applicationAny = application as any; const databaseTypeProfile = applicationAny.devDatabaseTypeH2Any ? 'prod' : undefined; let liquibasePluginHibernateDialect; let liquibasePluginJdbcDriver; + const mavenProperties: MavenProperty[] = []; if (applicationAny.devDatabaseTypeH2Any) { // eslint-disable-next-line no-template-curly-in-string liquibasePluginHibernateDialect = '${liquibase-plugin.hibernate-dialect}'; // eslint-disable-next-line no-template-curly-in-string liquibasePluginJdbcDriver = '${liquibase-plugin.driver}'; - source.addMavenDefinition?.({ - properties: [ - { property: 'liquibase-plugin.hibernate-dialect' }, - { property: 'liquibase-plugin.driver' }, - { property: 'h2.version', value: application.springBootDependencies!.h2 }, - { inProfile: 'dev', property: 'liquibase-plugin.hibernate-dialect', value: applicationAny.devHibernateDialect }, - { inProfile: 'prod', property: 'liquibase-plugin.hibernate-dialect', value: applicationAny.prodHibernateDialect }, - { inProfile: 'dev', property: 'liquibase-plugin.driver', value: applicationAny.devJdbcDriver }, - { inProfile: 'prod', property: 'liquibase-plugin.driver', value: applicationAny.prodJdbcDriver }, - ], - }); + if (h2Version) { + mavenProperties.push({ property: 'h2.version', value: h2Version }); + } else { + checkProperty('h2.version'); + } + mavenProperties.push( + { property: 'liquibase-plugin.hibernate-dialect' }, + { property: 'liquibase-plugin.driver' }, + { inProfile: 'dev', property: 'liquibase-plugin.hibernate-dialect', value: applicationAny.devHibernateDialect }, + { inProfile: 'prod', property: 'liquibase-plugin.hibernate-dialect', value: applicationAny.prodHibernateDialect }, + { inProfile: 'dev', property: 'liquibase-plugin.driver', value: applicationAny.devJdbcDriver }, + { inProfile: 'prod', property: 'liquibase-plugin.driver', value: applicationAny.prodJdbcDriver }, + ); } else { liquibasePluginHibernateDialect = applicationAny.prodHibernateDialect; liquibasePluginJdbcDriver = applicationAny.prodJdbcDriver; } + if (validationVersion) { + mavenProperties.push({ property: 'jakarta-validation.version', value: validationVersion }); + } else { + checkProperty('jakarta-validation.version'); + } + + if (liquibaseVersion) { + mavenProperties.push({ property: 'liquibase.version', value: liquibaseVersion }); + } else { + checkProperty('liquibase.version'); + } + source.addMavenDefinition?.({ properties: [ + ...mavenProperties, { inProfile: 'no-liquibase', property: 'profile.no-liquibase', value: ',no-liquibase' }, { property: 'profile.no-liquibase' }, - { property: 'liquibase.version', value: application.springBootDependencies!.liquibase }, { property: 'liquibase-plugin.url' }, { property: 'liquibase-plugin.username' }, { property: 'liquibase-plugin.password' }, @@ -454,14 +484,33 @@ export default class LiquibaseGenerator extends BaseEntityChangesGenerator { throw new Error('Some application fields are be mandatory'); } + const { liquibase: liquibaseVersion, 'gradle-liquibase': gradleLiquibaseVersion } = application.javaDependencies; + if (!liquibaseVersion) { + this.log.warn('liquibaseVersion is required by gradle-liquibase-plugin, make sure to add it to your dependencies'); + } else { + source.addGradleProperty?.({ property: 'liquibaseVersion', value: liquibaseVersion }); + } + source.addGradleProperty?.({ property: 'liquibaseTaskPrefix', value: 'liquibase' }); - source.addGradleProperty?.({ property: 'liquibasePluginVersion', value: application.javaDependencies['gradle-liquibase'] }); - source.addGradleProperty?.({ property: 'liquibaseVersion', value: application.springBootDependencies!.liquibase }); + source.addGradleProperty?.({ property: 'liquibasePluginVersion', value: gradleLiquibaseVersion }); source.applyFromGradle?.({ script: 'gradle/liquibase.gradle' }); source.addGradlePlugin?.({ id: 'org.liquibase.gradle' }); // eslint-disable-next-line no-template-curly-in-string source.addGradlePluginManagement?.({ id: 'org.liquibase.gradle', version: '${liquibasePluginVersion}' }); + + if (application.databaseTypeSql && !application.reactive) { + source.addGradleDependency?.( + { + scope: 'liquibaseRuntime', + groupId: 'org.liquibase.ext', + artifactId: 'liquibase-hibernate6', + // eslint-disable-next-line no-template-curly-in-string + version: liquibaseVersion ? '${liquibaseVersion}' : "${dependencyManagement.importedProperties['liquibase.version']}", + }, + { gradleFile: 'gradle/liquibase.gradle' }, + ); + } }, }); } diff --git a/generators/liquibase/support/maven-plugin.ts b/generators/liquibase/support/maven-plugin.ts index d16c676a830b..0c785d2a8627 100644 --- a/generators/liquibase/support/maven-plugin.ts +++ b/generators/liquibase/support/maven-plugin.ts @@ -38,10 +38,16 @@ export default function mavenPluginContent({ org.liquibase.ext liquibase-hibernate6 \${liquibase.version} + + + jakarta.validation + jakarta.validation-api + \${jakarta-validation.version} ${backendTypeSpringBoot ? ` org.springframework.boot spring-boot-starter-data-jpa + \${spring-boot.version} ` : ''}${devDatabaseTypeH2Any? ` com.h2database diff --git a/generators/liquibase/templates/gradle/liquibase.gradle.ejs b/generators/liquibase/templates/gradle/liquibase.gradle.ejs index c64ac637fba0..8812889138d0 100644 --- a/generators/liquibase/templates/gradle/liquibase.gradle.ejs +++ b/generators/liquibase/templates/gradle/liquibase.gradle.ejs @@ -7,10 +7,6 @@ dependencies { liquibaseRuntime "org.liquibase:liquibase-core" // Dependency required to parse options. Refer to https://github.com/liquibase/liquibase-gradle-plugin/tree/Release_2.2.0#news. liquibaseRuntime "info.picocli:picocli:<%- javaDependencies.picocli %>" -<%_ if (databaseTypeSql && !reactive) { _%> - - liquibaseRuntime "org.liquibase.ext:liquibase-hibernate6:${liquibaseVersion}" -<%_ } _%> <%_ if (databaseTypeSql) { _%> <%_ if (prodDatabaseType !== devDatabaseType) { _%> if (project.hasProperty("prod") || project.hasProperty("gae")) { @@ -26,7 +22,7 @@ dependencies { <%_ } _%> <%_ if (prodDatabaseTypeMssql) { _%> liquibaseRuntime "com.microsoft.sqlserver:mssql-jdbc" - liquibaseRuntime "org.liquibase.ext:liquibase-mssql:${liquibaseVersion}" + liquibaseRuntime "org.liquibase.ext:liquibase-mssql:${dependencyManagement.importedProperties['liquibase.version']}" <%_ } _%> <%_ if (prodDatabaseTypeOracle) { _%> liquibaseRuntime "com.oracle.database.jdbc:ojdbc8" @@ -37,12 +33,13 @@ dependencies { <%_ } else if (databaseTypeNeo4j) { _%> // Exclude current neo4j driver and use the one provided by spring-data // See: https://github.com/jhipster/generator-jhipster/pull/24241 - implementation ("org.liquibase.ext:liquibase-neo4j:${liquibaseVersion}") { + implementation ("org.liquibase.ext:liquibase-neo4j:${dependencyManagement.importedProperties['liquibase.version']}") { exclude group: "org.neo4j.driver", module: "neo4j-java-driver" exclude group: "org.slf4j", module: "*" } implementation "org.springframework:spring-jdbc" <%_ } _%> + // jhipster-needle-gradle-dependency - JHipster will add additional dependencies here } project.ext.diffChangelogFile = "<%= SERVER_MAIN_RES_DIR %>config/liquibase/changelog/" + new Date().format("yyyyMMddHHmmss") + "_changelog.xml" diff --git a/generators/maven/generator.ts b/generators/maven/generator.ts index f0efd2c24df9..828fb7ebe807 100644 --- a/generators/maven/generator.ts +++ b/generators/maven/generator.ts @@ -74,7 +74,8 @@ export default class MavenGenerator extends BaseApplicationGenerator(callback: (arg: T) => any): (arg: T | T[]) => void { return (arg: T | T[]): void => { const argArray = Array.isArray(arg) ? arg : [arg]; @@ -91,7 +92,13 @@ export default class MavenGenerator extends BaseApplicationGenerator this.pomStorage.addPluginManagement(plugin)); source.addMavenPluginRepository = createForEach(repository => this.pomStorage.addPluginRepository(repository)); source.addMavenProfile = createForEach(profile => this.pomStorage.addProfile(profile)); - source.addMavenProperty = createForEach(property => this.pomStorage.addProperty(property)); + source.addMavenProperty = properties => { + properties = Array.isArray(properties) ? properties : [properties]; + for (const property of properties) { + javaProperties![property.property] = property.value!; + this.pomStorage.addProperty(property); + } + }; source.addMavenRepository = createForEach(repository => this.pomStorage.addRepository(repository)); source.addMavenDefinition = definition => { @@ -105,7 +112,9 @@ export default class MavenGenerator extends BaseApplicationGenerator this.pomStorage.addDistributionManagement(distribution)); definition.plugins?.forEach(plugin => this.pomStorage.addPlugin(plugin)); definition.pluginRepositories?.forEach(repository => this.pomStorage.addPluginRepository(repository)); - definition.properties?.forEach(property => this.pomStorage.addProperty(property)); + if (definition.properties) { + source.addMavenProperty!(definition.properties); + } definition.repositories?.forEach(repository => this.pomStorage.addRepository(repository)); definition.annotationProcessors?.forEach(annotation => this.pomStorage.addAnnotationProcessor(annotation)); }; diff --git a/generators/maven/support/dependabot-maven.ts b/generators/maven/support/dependabot-maven.ts index 0c75a6f16d4a..61ec67684b9c 100644 --- a/generators/maven/support/dependabot-maven.ts +++ b/generators/maven/support/dependabot-maven.ts @@ -26,15 +26,38 @@ export function getPomProperties(pomContent: string): Record { return new XMLParser().parse(pomContent).project.properties; } +export type MavenPom = { + project: { + artifactId: string; + version: string; + properties?: Record; + }; +}; + +export type MavenPomAndVersions = { + pomContent: MavenPom; + versions: Record; +}; + +/** + * Extract version properties from pom content + * @param fileContent + */ +export function parseMavenPom(fileContent: string): MavenPom { + return new XMLParser().parse(fileContent); +} + /** * Extract version properties from pom content * @param pomContent */ -export function getPomVersionProperties(pomContent: string): Record { - const { properties, version, artifactId }: { properties: Record; version: string; artifactId: string } = - new XMLParser().parse(pomContent).project; +export function getPomVersionProperties(pomContent: string | MavenPom): Record { + if (typeof pomContent === 'string') { + pomContent = parseMavenPom(pomContent); + } + const { properties, version, artifactId } = pomContent.project; const versions = Object.fromEntries( - Object.entries(properties) + Object.entries(properties ?? []) .filter(([property]) => property.endsWith('.version')) .map(([property, value]) => [property.slice(0, -8), value]), ); diff --git a/generators/server/resources/gradle/libs.versions.toml b/generators/server/resources/gradle/libs.versions.toml index 324eedd6d2c6..e7ba365bc425 100644 --- a/generators/server/resources/gradle/libs.versions.toml +++ b/generators/server/resources/gradle/libs.versions.toml @@ -17,8 +17,6 @@ neo4j-migrations-spring-boot-starter = { module = 'eu.michael-simons.neo4j:neo4j lz4-java = { module = 'org.lz4:lz4-java', version = '1.8.0' } [plugins] -spring-dependency-management = { id = 'io.spring.dependency-management', version = '1.1.4' } - gradle-git-properties = { id = 'com.gorylenko.gradle-git-properties', version = '2.4.1' } node-gradle = { id = 'com.github.node-gradle.node', version = '7.0.1' } diff --git a/generators/server/templates/build.gradle.ejs b/generators/server/templates/build.gradle.ejs index 6b3b6d179324..6583ea7ea046 100644 --- a/generators/server/templates/build.gradle.ejs +++ b/generators/server/templates/build.gradle.ejs @@ -47,6 +47,29 @@ ext { } } +repositories { + // Local maven repository is required for libraries built locally with maven like development jhipster-bom. + <%= !jhipsterDependenciesVersion.includes('-SNAPSHOT') && !jhipsterDependenciesVersion.includes('-CICD') ? '// ' : '' %>mavenLocal() + mavenCentral() + <%_ if (addSpringMilestoneRepository) { _%> + maven { url 'https://repo.spring.io/milestone' } + <%_ } _%> + <%_ if (jhipsterDependenciesVersion.endsWith('-SNAPSHOT')) { _%> + maven { + url "https://oss.sonatype.org/content/repositories/snapshots/" + mavenContent { + snapshotsOnly() + } + } + <%_ } _%> + <%_ if (serviceDiscoveryEureka) { _%> + maven { url 'https://artifactory-oss.prod.netflix.net/artifactory/maven-oss-candidates' } + <%_ } _%> + // jhipster-needle-gradle-repositories - JHipster will add additional repositories +} + +apply plugin: 'io.spring.dependency-management' + <%_ if (enableSwaggerCodegen) { _%> apply from: "gradle/swagger.gradle" <%_ } _%> @@ -137,28 +160,6 @@ configurations { implementation.exclude module: "spring-boot-starter-tomcat" } - -repositories { - // Local maven repository is required for libraries built locally with maven like development jhipster-bom. - <%= !jhipsterDependenciesVersion.includes('-SNAPSHOT') && !jhipsterDependenciesVersion.includes('-CICD') ? '// ' : '' %>mavenLocal() - mavenCentral() - <%_ if (addSpringMilestoneRepository) { _%> - maven { url 'https://repo.spring.io/milestone' } - <%_ } _%> - <%_ if (jhipsterDependenciesVersion.endsWith('-SNAPSHOT')) { _%> - maven { - url "https://oss.sonatype.org/content/repositories/snapshots/" - mavenContent { - snapshotsOnly() - } - } - <%_ } _%> - <%_ if (serviceDiscoveryEureka) { _%> - maven { url 'https://artifactory-oss.prod.netflix.net/artifactory/maven-oss-candidates' } - <%_ } _%> - // jhipster-needle-gradle-repositories - JHipster will add additional repositories -} - dependencies { implementation "com.fasterxml.jackson.datatype:jackson-datatype-hppc" implementation "com.fasterxml.jackson.datatype:jackson-datatype-jsr310" diff --git a/generators/server/templates/pom.xml.ejs b/generators/server/templates/pom.xml.ejs index 5daa8d478538..b4280f9bd979 100644 --- a/generators/server/templates/pom.xml.ejs +++ b/generators/server/templates/pom.xml.ejs @@ -26,7 +26,7 @@ org.springframework.boot spring-boot-starter-parent - <%- javaDependencies['spring-boot'] %> + <%- springBootDependencies['spring-boot-dependencies'] %> diff --git a/generators/spring-boot/generator.ts b/generators/spring-boot/generator.ts index 5f70c07b714b..7536d8bf0b5d 100644 --- a/generators/spring-boot/generator.ts +++ b/generators/spring-boot/generator.ts @@ -18,7 +18,7 @@ */ import os from 'node:os'; import chalk from 'chalk'; -import { kebabCase, sortedUniqBy } from 'lodash-es'; +import { sortedUniqBy } from 'lodash-es'; import BaseApplicationGenerator from '../base-application/index.js'; import { GENERATOR_SERVER, @@ -56,7 +56,7 @@ import { websocketTypes, } from '../../jdl/index.js'; import { writeFiles as writeEntityFiles } from './entity-files.js'; -import { getPomVersionProperties } from '../maven/support/index.js'; +import { getPomVersionProperties, parseMavenPom } from '../maven/support/index.js'; const { CAFFEINE, EHCACHE, HAZELCAST, INFINISPAN, MEMCACHED, REDIS, NO: NO_CACHE } = cacheTypes; const { NO: NO_WEBSOCKET, SPRING_WEBSOCKET } = websocketTypes; @@ -192,20 +192,20 @@ export default class SpringBootGenerator extends BaseApplicationGenerator { get preparing() { return this.asPreparingTaskGroup({ loadSpringBootBom({ application }) { - const pomFile = this.readTemplate(this.jhipsterTemplatePath('../resources/spring-boot-dependencies.pom'))?.toString(); if (this.useVersionPlaceholders) { - application.javaDependencies!['spring-boot'] = "'SPRING-BOOT-VERSION'"; - application.springBootDependencies = {}; + application.springBootDependencies = { + 'spring-boot-dependencies': "'SPRING-BOOT-VERSION'", + }; } else { - application.springBootDependencies = this.prepareDependencies( - getPomVersionProperties(pomFile!), - value => `'${kebabCase(value).toUpperCase()}-VERSION'`, - ); + const pomFile = this.readTemplate(this.jhipsterTemplatePath('../resources/spring-boot-dependencies.pom'))!.toString(); + const pom = parseMavenPom(pomFile); + application.springBootDependencies = this.prepareDependencies(getPomVersionProperties(pom), 'java'); application.javaDependencies!['spring-boot'] = application.springBootDependencies['spring-boot-dependencies']; + Object.assign(application.javaManagedProperties!, pom.project.properties); } }, prepareForTemplates({ application }) { - const SPRING_BOOT_VERSION = application.javaDependencies!['spring-boot']; + const SPRING_BOOT_VERSION = application.springBootDependencies!['spring-boot-dependencies']; application.addSpringMilestoneRepository = (application.backendType ?? 'Java') === 'Java' && (ADD_SPRING_MILESTONE_REPOSITORY || SPRING_BOOT_VERSION.includes('M') || SPRING_BOOT_VERSION.includes('RC')); @@ -414,6 +414,14 @@ public void set${javaBeanCase(propertyName)}(${propertyType} ${propertyName}) { application; const { serviceDiscoveryAny } = application as any; + if (application.buildToolMaven) { + source.addMavenProperty?.({ + property: 'spring-boot.version', + // eslint-disable-next-line no-template-curly-in-string + value: '${project.parent.version}', + }); + } + source.addJavaDependencies?.([ { groupId: 'tech.jhipster', artifactId: 'jhipster-framework', version: jhipsterDependenciesVersion! }, ]); @@ -482,12 +490,6 @@ public void set${javaBeanCase(propertyName)}(${propertyType} ${propertyName}) { version: application.javaDependencies!['spring-boot'], addToBuild: true, }, - { - pluginName: 'spring-dependency-management', - id: 'io.spring.dependency-management', - version: application.javaDependencies!['spring-dependency-management'], - addToBuild: true, - }, ]); } }, diff --git a/test-integration/incremental-changelog-samples/liquibase-jdl-rename-field-post/app.jdl b/test-integration/incremental-changelog-samples/liquibase-jdl-rename-field-post/app.jdl index 8684a1e17c89..68614c948b34 100644 --- a/test-integration/incremental-changelog-samples/liquibase-jdl-rename-field-post/app.jdl +++ b/test-integration/incremental-changelog-samples/liquibase-jdl-rename-field-post/app.jdl @@ -4,6 +4,7 @@ application { baseName jhipsterSampleApplication, packageName tech.jhipster.sample, authenticationType jwt, + devDatabaseType h2Disk, prodDatabaseType postgresql, buildTool maven, searchEngine no, diff --git a/test-integration/incremental-changelog-samples/liquibase-jdl-rename-field/app.jdl b/test-integration/incremental-changelog-samples/liquibase-jdl-rename-field/app.jdl index fe44c2f9feba..5bb3153a274b 100644 --- a/test-integration/incremental-changelog-samples/liquibase-jdl-rename-field/app.jdl +++ b/test-integration/incremental-changelog-samples/liquibase-jdl-rename-field/app.jdl @@ -4,6 +4,7 @@ application { baseName jhipsterSampleApplication, packageName tech.jhipster.sample, authenticationType jwt, + devDatabaseType h2Disk, prodDatabaseType postgresql, buildTool maven, searchEngine no, diff --git a/testing/helpers.ts b/testing/helpers.ts index a363196c19f1..0b25fe6c4522 100644 --- a/testing/helpers.ts +++ b/testing/helpers.ts @@ -1,7 +1,7 @@ /* eslint-disable max-classes-per-file */ import type { BaseEnvironmentOptions, GetGeneratorConstructor, BaseGenerator as YeomanGenerator } from '@yeoman/types'; import { YeomanTest, RunContext, RunContextSettings, RunResult, result } from 'yeoman-test'; -import * as _ from 'lodash-es'; +import { merge, set } from 'lodash-es'; import { basename, join } from 'path'; import EnvironmentBuilder from '../cli/environment-builder.mjs'; @@ -17,8 +17,6 @@ type BaseEntity = any; type GeneratorTestType = YeomanGenerator; type GeneratorTestOptions = JHipsterGeneratorOptions; -const { set } = _; - type JHipsterRunResult = RunResult & { /** * First argument of mocked source calls. @@ -217,7 +215,7 @@ class JHipsterRunContext extends RunContext { withSharedApplication(sharedApplication: Record): this { this.sharedApplication = this.sharedApplication ?? {}; - Object.assign(this.sharedApplication, sharedApplication); + merge(this.sharedApplication, sharedApplication); return this.withSharedData({ sharedApplication: this.sharedApplication }); }