diff --git a/cli/cli-jdl.spec.ts b/cli/cli-jdl.spec.ts new file mode 100644 index 000000000000..8eefeabffb3f --- /dev/null +++ b/cli/cli-jdl.spec.ts @@ -0,0 +1,33 @@ +import { join } from 'path'; +import { execa } from 'execa'; +import { basicHelpers as helpers, runResult } from '../lib/testing/index.js'; +import { getPackageRoot } from '../lib/index.js'; + +const jhipsterCli = join(getPackageRoot(), 'bin/jhipster.cjs'); + +describe('allows customizing JDL definitions', () => { + it('accepts a custom JDL definition', async () => { + await helpers + .prepareTemporaryDir() + .withFiles({ + '.blueprint/app/index.mjs': `export const command = { + configs: { + fooConfig: { + jdl: { + type: 'boolean', + tokenType: 'BOOLEAN', + }, + scope: 'storage', + }, + }, +};`, + }) + .commitFiles(); + await execa(jhipsterCli, ['jdl', '--json-only', '--inline', 'application { config { fooConfig false } }'], { stdio: 'pipe' }); + runResult.assertJsonFileContent('.yo-rc.json', { + 'generator-jhipster': { + fooConfig: false, + }, + }); + }); +}); diff --git a/cli/jhipster-command.mjs b/cli/jhipster-command.mjs index 3190dfa43147..fabdf24c302c 100644 --- a/cli/jhipster-command.mjs +++ b/cli/jhipster-command.mjs @@ -23,6 +23,9 @@ import { kebabCase } from 'lodash-es'; import { convertConfigToOption } from '../lib/command/index.js'; export default class JHipsterCommand extends Command { + configs = {}; + blueprintConfigs = {}; + createCommand(name) { return new JHipsterCommand(name); } @@ -177,6 +180,7 @@ export default class JHipsterCommand extends Command { } addJHipsterConfigs(configs = {}, blueprintOptionDescription) { + Object.assign(blueprintOptionDescription ? this.blueprintConfigs : this.configs, configs); Object.entries(configs).forEach(([name, config]) => { const option = convertConfigToOption(name, config); if (option) { diff --git a/cli/program.mjs b/cli/program.mjs index 590ed71bdd2f..1ffbe908225f 100644 --- a/cli/program.mjs +++ b/cli/program.mjs @@ -28,6 +28,7 @@ import { packageNameToNamespace } from '../generators/base/support/index.js'; import command from '../generators/base/command.js'; import { GENERATOR_APP, GENERATOR_BOOTSTRAP, GENERATOR_JDL } from '../generators/generator-list.js'; import { extractArgumentsFromConfigs } from '../lib/command/index.js'; +import { buildJDLApplicationConfig } from '../lib/command/jdl.js'; import logo from './logo.mjs'; import EnvironmentBuilder from './environment-builder.mjs'; import SUB_GENERATORS from './commands.mjs'; @@ -56,6 +57,8 @@ const buildAllDependencies = async (generatorNames, { env, blueprintNamespaces } const meta = await env.getGeneratorMeta(namespace.includes(':') ? namespace : `${JHIPSTER_NS}:${namespace}`); if (meta) { allDependencies[namespace] = { meta, blueprintNamespace }; + } else if (!blueprintNamespace) { + logger.warn(`Generator ${namespace} not found.`); } return meta?.importModule(); }; @@ -276,6 +279,8 @@ export const buildCommands = async ({ const command = everything.pop(); const cmdOptions = everything.pop(); const args = everything; + const commandsConfigs = Object.freeze({ ...command.configs, ...command.blueprintConfigs }); + const jdlDefinition = buildJDLApplicationConfig(commandsConfigs); const options = { ...program.opts(), ...cmdOptions, @@ -284,6 +289,8 @@ export const buildCommands = async ({ entrypointGenerator, blueprints: envBuilder.getBlueprintsOption(), positionalArguments: args, + jdlDefinition, + commandsConfigs, }; if (options.installPath) { // eslint-disable-next-line no-console diff --git a/generators/app/__snapshots__/generator.spec.ts.snap b/generators/app/__snapshots__/generator.spec.ts.snap index 1e26fd49d446..39149896843c 100644 --- a/generators/app/__snapshots__/generator.spec.ts.snap +++ b/generators/app/__snapshots__/generator.spec.ts.snap @@ -36,7 +36,6 @@ Options: --cache-provider Cache provider --enable-swagger-codegen API first development using OpenAPI-generator --enable-hibernate-cache Enable hibernate cache - --message-broker message broker --search-engine Provide search engine for the application when skipping server side generation --skip-check-length-of-identifier Skip check the length of the identifier, only for recent Oracle databases that support 30+ characters metadata --skip-db-changelog Skip the generation of database migrations @@ -50,6 +49,8 @@ Options: --auth Provide authentication type for the application when skipping server side generation (choices: "jwt", "oauth2", "session") --feign-client Generate a feign client --sync-user-with-idp Allow relationships with User for oauth2 applications + --message-broker message broker (choices: "kafka", "pulsar", "no") + --database-migration Database migration (choices: "liquibase") --with-generated-flag Add a GeneratedByJHipster annotation to all generated java classes and interfaces --package-name The package name for the generated application --build Provide build tool for the application when skipping server side generation (choices: "maven", "gradle") @@ -212,6 +213,7 @@ exports[`generator - app with default config should match snapshot 1`] = ` "SERVER_TEST_SRC_DIR": "src/test/java/", "TEST_DIR": "src/test/", "VUE": "vue", + "addOpenapiGeneratorPlugin": undefined, "addPrettierExtensions": [Function], "addSpringMilestoneRepository": false, "angularLocaleId": "en", @@ -222,6 +224,7 @@ exports[`generator - app with default config should match snapshot 1`] = ` "applicationTypeMicroservice": false, "applicationTypeMonolith": true, "authenticationType": "jwt", + "authenticationTypeAny": true, "authenticationTypeJwt": true, "authenticationTypeOauth2": false, "authenticationTypeSession": false, @@ -233,6 +236,7 @@ exports[`generator - app with default config should match snapshot 1`] = ` "baseName": "jhipster", "blueprints": [], "buildTool": "maven", + "buildToolAny": true, "buildToolGradle": false, "buildToolMaven": true, "buildToolUnknown": false, @@ -274,6 +278,7 @@ exports[`generator - app with default config should match snapshot 1`] = ` "cypressTests": false, "dasherizedBaseName": "jhipster", "databaseMigration": undefined, + "databaseMigrationAny": undefined, "databaseMigrationLiquibase": true, "databaseType": "sql", "databaseTypeAny": true, @@ -284,7 +289,13 @@ exports[`generator - app with default config should match snapshot 1`] = ` "databaseTypeNo": false, "databaseTypeSql": true, "defaultEnvironment": "prod", + "defaultEnvironmentAny": true, + "defaultEnvironmentDev": false, + "defaultEnvironmentProd": true, "defaultPackaging": "jar", + "defaultPackagingAny": true, + "defaultPackagingJar": true, + "defaultPackagingWar": false, "devDatabaseExtraOptions": "", "devDatabaseName": "jhipster", "devDatabasePassword": "password", @@ -465,6 +476,8 @@ exports[`generator - app with default config should match snapshot 1`] = ` "generateInMemoryUserCredentials": false, "generateSpringAuditor": true, "generateUserManagement": true, + "githubRepository": undefined, + "githubWorkflows": undefined, "gradleEnterpriseHost": undefined, "hipster": "jhipster_family_member_3", "hipsterBugTrackerLink": "https://github.com/jhipster/generator-jhipster/issues?state=open", @@ -591,6 +604,7 @@ exports[`generator - app with default config should match snapshot 1`] = ` "messageBroker": "no", "messageBrokerAny": false, "messageBrokerKafka": false, + "messageBrokerNo": true, "messageBrokerPulsar": false, "microfrontend": false, "microfrontends": undefined, @@ -723,6 +737,9 @@ exports[`generator - app with default config should match snapshot 1`] = ` ], "packageJsonNodeEngine": undefined, "packageJsonType": "commonjs", + "packageJsonTypeAny": true, + "packageJsonTypeCommonjs": true, + "packageJsonTypeModule": false, "packageName": "com.mycompany.myapp", "pages": [], "prettierExtensions": "md,json,yml,js,cjs,mjs,ts,cts,mts,java,html,css,scss", @@ -743,6 +760,7 @@ exports[`generator - app with default config should match snapshot 1`] = ` "prodDatabaseName": "jhipster", "prodDatabasePassword": "password", "prodDatabaseType": "postgresql", + "prodDatabaseTypeAny": true, "prodDatabaseTypeMariadb": false, "prodDatabaseTypeMssql": false, "prodDatabaseTypeMysql": false, @@ -760,6 +778,7 @@ exports[`generator - app with default config should match snapshot 1`] = ` "reactorBlockOptional": "", "rememberMeKey": undefined, "requiresDeleteAllUsers": false, + "routes": undefined, "searchEngine": "no", "searchEngineAny": false, "searchEngineCouchbase": false, @@ -769,7 +788,12 @@ exports[`generator - app with default config should match snapshot 1`] = ` "serviceDiscoveryAny": false, "serviceDiscoveryConsul": false, "serviceDiscoveryEureka": false, + "serviceDiscoveryNo": true, "serviceDiscoveryType": "no", + "serviceDiscoveryTypeAny": undefined, + "serviceDiscoveryTypeConsul": undefined, + "serviceDiscoveryTypeEureka": undefined, + "serviceDiscoveryTypeNo": undefined, "skipCheckLengthOfIdentifier": false, "skipClient": undefined, "skipCommitHook": undefined, @@ -826,6 +850,7 @@ exports[`generator - app with gateway should match snapshot 1`] = ` "SERVER_TEST_SRC_DIR": "src/test/java/", "TEST_DIR": "src/test/", "VUE": "vue", + "addOpenapiGeneratorPlugin": undefined, "addPrettierExtensions": [Function], "addSpringMilestoneRepository": false, "angularLocaleId": "en", @@ -836,6 +861,7 @@ exports[`generator - app with gateway should match snapshot 1`] = ` "applicationTypeMicroservice": false, "applicationTypeMonolith": false, "authenticationType": "jwt", + "authenticationTypeAny": true, "authenticationTypeJwt": true, "authenticationTypeOauth2": false, "authenticationTypeSession": false, @@ -847,6 +873,7 @@ exports[`generator - app with gateway should match snapshot 1`] = ` "baseName": "jhipster", "blueprints": [], "buildTool": "maven", + "buildToolAny": true, "buildToolGradle": false, "buildToolMaven": true, "buildToolUnknown": false, @@ -888,6 +915,7 @@ exports[`generator - app with gateway should match snapshot 1`] = ` "cypressTests": false, "dasherizedBaseName": "jhipster", "databaseMigration": undefined, + "databaseMigrationAny": undefined, "databaseMigrationLiquibase": true, "databaseType": "sql", "databaseTypeAny": true, @@ -898,7 +926,13 @@ exports[`generator - app with gateway should match snapshot 1`] = ` "databaseTypeNo": false, "databaseTypeSql": true, "defaultEnvironment": "prod", + "defaultEnvironmentAny": true, + "defaultEnvironmentDev": false, + "defaultEnvironmentProd": true, "defaultPackaging": "jar", + "defaultPackagingAny": true, + "defaultPackagingJar": true, + "defaultPackagingWar": false, "devDatabaseExtraOptions": "", "devDatabaseName": "jhipster", "devDatabasePassword": "password", @@ -1081,6 +1115,8 @@ exports[`generator - app with gateway should match snapshot 1`] = ` "generateInMemoryUserCredentials": false, "generateSpringAuditor": true, "generateUserManagement": true, + "githubRepository": undefined, + "githubWorkflows": undefined, "gradleEnterpriseHost": undefined, "hipster": "jhipster_family_member_3", "hipsterBugTrackerLink": "https://github.com/jhipster/generator-jhipster/issues?state=open", @@ -1201,6 +1237,7 @@ exports[`generator - app with gateway should match snapshot 1`] = ` "messageBroker": "no", "messageBrokerAny": false, "messageBrokerKafka": false, + "messageBrokerNo": true, "messageBrokerPulsar": false, "microfrontend": undefined, "microfrontends": undefined, @@ -1333,6 +1370,9 @@ exports[`generator - app with gateway should match snapshot 1`] = ` ], "packageJsonNodeEngine": undefined, "packageJsonType": "commonjs", + "packageJsonTypeAny": true, + "packageJsonTypeCommonjs": true, + "packageJsonTypeModule": false, "packageName": "com.mycompany.myapp", "pages": [], "prettierExtensions": "md,json,yml,js,cjs,mjs,ts,cts,mts,java,html,css,scss", @@ -1353,6 +1393,7 @@ exports[`generator - app with gateway should match snapshot 1`] = ` "prodDatabaseName": "jhipster", "prodDatabasePassword": "password", "prodDatabaseType": "postgresql", + "prodDatabaseTypeAny": true, "prodDatabaseTypeMariadb": false, "prodDatabaseTypeMssql": false, "prodDatabaseTypeMysql": false, @@ -1381,7 +1422,12 @@ exports[`generator - app with gateway should match snapshot 1`] = ` "serviceDiscoveryAny": true, "serviceDiscoveryConsul": true, "serviceDiscoveryEureka": false, + "serviceDiscoveryNo": false, "serviceDiscoveryType": "consul", + "serviceDiscoveryTypeAny": undefined, + "serviceDiscoveryTypeConsul": undefined, + "serviceDiscoveryTypeEureka": undefined, + "serviceDiscoveryTypeNo": undefined, "skipCheckLengthOfIdentifier": false, "skipClient": undefined, "skipCommitHook": undefined, @@ -1438,6 +1484,7 @@ exports[`generator - app with microservice should match snapshot 1`] = ` "SERVER_TEST_SRC_DIR": "src/test/java/", "TEST_DIR": "src/test/", "VUE": "vue", + "addOpenapiGeneratorPlugin": undefined, "addPrettierExtensions": [Function], "addSpringMilestoneRepository": false, "anyEntityHasRelationshipWithUser": false, @@ -1447,6 +1494,7 @@ exports[`generator - app with microservice should match snapshot 1`] = ` "applicationTypeMicroservice": true, "applicationTypeMonolith": false, "authenticationType": "jwt", + "authenticationTypeAny": true, "authenticationTypeJwt": true, "authenticationTypeOauth2": false, "authenticationTypeSession": false, @@ -1457,6 +1505,7 @@ exports[`generator - app with microservice should match snapshot 1`] = ` "baseName": "jhipster", "blueprints": [], "buildTool": "maven", + "buildToolAny": true, "buildToolGradle": false, "buildToolMaven": true, "buildToolUnknown": false, @@ -1497,6 +1546,7 @@ exports[`generator - app with microservice should match snapshot 1`] = ` "cypressTests": false, "dasherizedBaseName": "jhipster", "databaseMigration": undefined, + "databaseMigrationAny": undefined, "databaseMigrationLiquibase": true, "databaseType": "sql", "databaseTypeAny": true, @@ -1507,7 +1557,13 @@ exports[`generator - app with microservice should match snapshot 1`] = ` "databaseTypeNo": false, "databaseTypeSql": true, "defaultEnvironment": "prod", + "defaultEnvironmentAny": true, + "defaultEnvironmentDev": false, + "defaultEnvironmentProd": true, "defaultPackaging": "jar", + "defaultPackagingAny": true, + "defaultPackagingJar": true, + "defaultPackagingWar": false, "devDatabaseExtraOptions": "", "devDatabaseName": "jhipster", "devDatabasePassword": "password", @@ -1690,6 +1746,8 @@ exports[`generator - app with microservice should match snapshot 1`] = ` "generateInMemoryUserCredentials": false, "generateSpringAuditor": true, "generateUserManagement": false, + "githubRepository": undefined, + "githubWorkflows": undefined, "gradleEnterpriseHost": undefined, "hipster": "jhipster_family_member_3", "hipsterBugTrackerLink": "https://github.com/jhipster/generator-jhipster/issues?state=open", @@ -1816,6 +1874,7 @@ exports[`generator - app with microservice should match snapshot 1`] = ` "messageBroker": "no", "messageBrokerAny": false, "messageBrokerKafka": false, + "messageBrokerNo": true, "messageBrokerPulsar": false, "microfrontend": false, "microfrontends": undefined, @@ -1889,6 +1948,9 @@ exports[`generator - app with microservice should match snapshot 1`] = ` ], "packageJsonNodeEngine": undefined, "packageJsonType": "commonjs", + "packageJsonTypeAny": true, + "packageJsonTypeCommonjs": true, + "packageJsonTypeModule": false, "packageName": "com.mycompany.myapp", "pages": [], "prettierExtensions": "md,json,yml,js,cjs,mjs,ts,cts,mts,java", @@ -1909,6 +1971,7 @@ exports[`generator - app with microservice should match snapshot 1`] = ` "prodDatabaseName": "jhipster", "prodDatabasePassword": "password", "prodDatabaseType": "postgresql", + "prodDatabaseTypeAny": true, "prodDatabaseTypeMariadb": false, "prodDatabaseTypeMssql": false, "prodDatabaseTypeMysql": false, @@ -1926,6 +1989,7 @@ exports[`generator - app with microservice should match snapshot 1`] = ` "reactorBlockOptional": "", "rememberMeKey": undefined, "requiresDeleteAllUsers": false, + "routes": undefined, "searchEngine": "no", "searchEngineAny": false, "searchEngineCouchbase": false, @@ -1935,7 +1999,12 @@ exports[`generator - app with microservice should match snapshot 1`] = ` "serviceDiscoveryAny": true, "serviceDiscoveryConsul": true, "serviceDiscoveryEureka": false, + "serviceDiscoveryNo": false, "serviceDiscoveryType": "consul", + "serviceDiscoveryTypeAny": undefined, + "serviceDiscoveryTypeConsul": undefined, + "serviceDiscoveryTypeEureka": undefined, + "serviceDiscoveryTypeNo": undefined, "skipCheckLengthOfIdentifier": false, "skipClient": true, "skipCommitHook": undefined, diff --git a/generators/app/jdl/application-options.ts b/generators/app/jdl/application-options.ts deleted file mode 100644 index 295ff8fab321..000000000000 --- a/generators/app/jdl/application-options.ts +++ /dev/null @@ -1,19 +0,0 @@ -/** - * Copyright 2013-2024 the original author or authors from the JHipster project. - * - * This file is part of the JHipster project, see https://www.jhipster.tech/ - * for more information. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -export { default } from '../../server/jdl/index.js'; diff --git a/generators/app/jdl/index.ts b/generators/app/jdl/index.ts deleted file mode 100644 index 5f8a8ff1c57e..000000000000 --- a/generators/app/jdl/index.ts +++ /dev/null @@ -1,19 +0,0 @@ -/** - * Copyright 2013-2024 the original author or authors from the JHipster project. - * - * This file is part of the JHipster project, see https://www.jhipster.tech/ - * for more information. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -export { default } from './application-options.js'; diff --git a/generators/base-application/support/relationship.ts b/generators/base-application/support/relationship.ts index cbefd4301e37..cb2806d048a6 100644 --- a/generators/base-application/support/relationship.ts +++ b/generators/base-application/support/relationship.ts @@ -19,7 +19,6 @@ import { lowerFirst, upperFirst } from 'lodash-es'; -import type { JSONEntity } from '../../../lib/jdl/core/types/json-config.js'; import type { ValidationResult } from '../../base/api.js'; import type { Entity } from '../../../lib/types/application/entity.js'; import type { Relationship } from '../../../lib/types/application/relationship.js'; @@ -113,7 +112,7 @@ export const loadEntitiesOtherSide = (entities: Entity[], { application }: { app return result; }; -export const addOtherRelationship = (entity: JSONEntity, otherEntity: JSONEntity, relationship: Relationship): Relationship => { +export const addOtherRelationship = (entity: Entity, otherEntity: Entity, relationship: Relationship): Relationship => { relationship.otherEntityRelationshipName = relationship.otherEntityRelationshipName ?? lowerFirst(entity.name); const otherRelationship = { otherEntityName: lowerFirst(entity.name), @@ -123,7 +122,7 @@ export const addOtherRelationship = (entity: JSONEntity, otherEntity: JSONEntity otherEntity: entity, ownerSide: !relationship.ownerSide, otherRelationship: relationship, - } as Relationship; + } as any; otherEntity.relationships = otherEntity.relationships ?? []; otherEntity.relationships.push(otherRelationship); return otherRelationship; diff --git a/generators/base-workspaces/internal/docker-base.ts b/generators/base-workspaces/internal/docker-base.ts index 8eedd02eba8c..50279c9eab1d 100644 --- a/generators/base-workspaces/internal/docker-base.ts +++ b/generators/base-workspaces/internal/docker-base.ts @@ -26,6 +26,8 @@ import { GENERATOR_JHIPSTER } from '../../generator-constants.js'; import { loadDeploymentConfig } from '../../base-workspaces/internal/index.js'; import { loadDerivedAppConfig } from '../../app/support/index.js'; import { loadDerivedPlatformConfig, loadDerivedServerConfig } from '../../server/support/index.js'; +import { loadCommandConfigsIntoApplication } from '../../../lib/command/load.js'; +import { lookupCommandsConfigs } from '../../../lib/command/lookup-commands-configs.js'; const { MAVEN } = buildToolTypes; const { MONOLITH, MICROSERVICE, GATEWAY } = applicationTypes; @@ -81,7 +83,7 @@ export function configureImageNames() { /** * Load config from this.appFolders */ -export function loadConfigs() { +export async function loadConfigs() { this.appConfigs = []; this.gatewayNb = 0; this.monolithicNb = 0; @@ -92,7 +94,7 @@ export function loadConfigs() { // Loading configs this.log.debug(`Apps folders: ${this.appsFolders}`); - this.appsFolders.forEach((appFolder, index) => { + for (const [index, appFolder] of this.appsFolders.entries()) { const path = this.destinationPath(`${this.directoryPath + appFolder}`); this.log.debug(chalk.red.bold(`App folder ${path}`)); if (this.fs.exists(`${path}/.yo-rc.json`)) { @@ -100,6 +102,12 @@ export function loadConfigs() { config.composePort = serverPort + index; this.log.debug(chalk.red.bold(`${config.baseName} has compose port ${config.composePort} and appIndex ${config.applicationIndex}`)); + loadCommandConfigsIntoApplication({ + source: config, + application: config, + commandsConfigs: this.options.commandsConfigs ?? (await lookupCommandsConfigs()), + }); + loadDerivedAppConfig({ application: config }); loadDerivedPlatformConfig({ application: config }); loadDerivedServerConfig({ application: config }); @@ -118,7 +126,7 @@ export function loadConfigs() { } else { throw new Error(`Application '${appFolder}' is not found in the path '${this.directoryPath}'`); } - }); + } } export function setClusteredApps() { @@ -129,7 +137,7 @@ export function setClusteredApps() { } } -export function loadFromYoRc() { +export async function loadFromYoRc() { loadDeploymentConfig.call(this); this.useKafka = false; @@ -137,7 +145,7 @@ export function loadFromYoRc() { this.useMemcached = false; this.useRedis = false; - loadConfigs.call(this); + await loadConfigs.call(this); if (this.microserviceNb > 0 || this.gatewayNb > 0) { this.deploymentApplicationType = MICROSERVICE; } else { diff --git a/generators/base-workspaces/internal/docker-prompts.ts b/generators/base-workspaces/internal/docker-prompts.ts index 3fb235265565..fc8d1aa8321c 100644 --- a/generators/base-workspaces/internal/docker-prompts.ts +++ b/generators/base-workspaces/internal/docker-prompts.ts @@ -182,7 +182,7 @@ async function askForApps() { const props = await this.prompt(prompts); this.appsFolders = this.jhipsterConfig.appsFolders = props.chosenApps; - loadConfigs.call(this); + await loadConfigs.call(this); } /** diff --git a/generators/base/api.d.ts b/generators/base/api.d.ts index 680917d47009..1460bd7ed4d4 100644 --- a/generators/base/api.d.ts +++ b/generators/base/api.d.ts @@ -3,6 +3,8 @@ import type CoreGenerator from '../base-core/index.js'; import type { ApplicationType } from '../../lib/types/application/application.js'; import type { Entity } from '../../lib/types/application/entity.js'; import type { ApplicationOptions } from '../../lib/types/application/options.js'; +import type { JDLApplicationConfig } from '../../lib/jdl/core/types/parsing.js'; +import type { JHipsterConfigs } from '../../lib/command/types.js'; export type ApplicationWithConfig = { config: Record; @@ -16,6 +18,10 @@ export type JHipsterGeneratorOptions = BaseOptions & programName: string; positionalArguments?: unknown[]; createEnvBuilder?: any; + /** @experimental */ + jdlDefinition?: JDLApplicationConfig; + /** @experimental */ + commandsConfigs?: JHipsterConfigs; /* yeoman options */ skipYoResolve?: boolean; diff --git a/generators/bootstrap-application-base/generator.ts b/generators/bootstrap-application-base/generator.ts index c32c95ff2406..49454d2a05e7 100644 --- a/generators/bootstrap-application-base/generator.ts +++ b/generators/bootstrap-application-base/generator.ts @@ -39,7 +39,10 @@ import { GENERATOR_BOOTSTRAP, GENERATOR_COMMON, GENERATOR_PROJECT_NAME } from '. import { packageJson } from '../../lib/index.js'; import { loadLanguagesConfig } from '../languages/support/index.js'; import { loadAppConfig, loadDerivedAppConfig, loadStoredAppOptions } from '../app/support/index.js'; -import jdlDefinition from '../app/jdl/index.js'; +import { lookupCommandsConfigs } from '../../lib/command/lookup-commands-configs.js'; +import { loadCommandConfigsIntoApplication, loadCommandConfigsKeysIntoTemplatesContext } from '../../lib/command/load.js'; +import { getConfigWithDefaults } from '../../lib/jhipster/default-application-options.js'; +import { removeFieldsWithNullishValues } from '../base/support/index.js'; import { createAuthorityEntity, createUserEntity, createUserManagementEntity } from './utils.js'; import { exportJDLTransform, importJDLTransform } from './support/index.js'; @@ -80,6 +83,7 @@ export default class BootstrapApplicationBase extends BaseApplicationGenerator { const destinationPath = this.destinationPath(); const jdlStorePath = this.destinationPath(this.jhipsterConfig.jdlStore); + const { jdlDefinition } = this.options; this.features.commitTransformFactory = () => exportJDLTransform({ destinationPath, jdlStorePath, jdlDefinition }); await this.pipeline({ refresh: true, pendingFiles: false }, importJDLTransform({ destinationPath, jdlStorePath, jdlDefinition })); @@ -142,6 +146,17 @@ export default class BootstrapApplicationBase extends BaseApplicationGenerator { get preparing() { return this.asPreparingTaskGroup({ + /** + * Avoid having undefined keys in the application object when redering ejs templates + */ + async loadApplicationKeys({ application }) { + const { applyDefaults = getConfigWithDefaults, commandsConfigs = await lookupCommandsConfigs() } = this.options; + loadCommandConfigsIntoApplication({ + source: applyDefaults(removeFieldsWithNullishValues(this.config.getAll())), + application, + commandsConfigs, + }); + }, prepareApplication({ application, applicationDefaults }) { loadDerivedAppConfig({ application }); @@ -406,6 +421,15 @@ export default class BootstrapApplicationBase extends BaseApplicationGenerator { get default() { return this.asDefaultTaskGroup({ + /** + * Avoid having undefined keys in the application object when redering ejs templates + */ + async loadApplicationKeys({ application }) { + loadCommandConfigsKeysIntoTemplatesContext({ + templatesContext: application, + commandsConfigs: this.options.commandsConfigs ?? (await lookupCommandsConfigs()), + }); + }, task({ application }) { const packageJsonFiles = [this.destinationPath('package.json')]; if (application.clientRootDir) { diff --git a/generators/docker/generator.spec.ts b/generators/docker/generator.spec.ts index be4d0f171529..6923212996aa 100644 --- a/generators/docker/generator.spec.ts +++ b/generators/docker/generator.spec.ts @@ -32,7 +32,6 @@ import { import { matchElasticSearchDocker } from '../spring-data-elasticsearch/__test-support/elastic-search-matcher.js'; import { cacheTypes, databaseTypes, searchEngineTypes, serviceDiscoveryTypes } from '../../lib/jhipster/index.js'; -import { MESSAGE_BROKER_KAFKA, MESSAGE_BROKER_NO, MESSAGE_BROKER_PULSAR } from '../server/options/message-broker.js'; import { shouldSupportFeatures } from '../../test/support/tests.js'; import { matchConsul, matchEureka } from './__test-support/service-discovery-matcher.js'; import Generator from './index.js'; @@ -67,7 +66,7 @@ matrix = extendMatrix(matrix, { searchEngine: [NO_SEARCH_ENGINE, ELASTICSEARCH], serviceDiscoveryType: [NO_SERVICE_DISCOVERY, EUREKA, CONSUL], enableSwaggerCodegen: [false, true], - messageBroker: [MESSAGE_BROKER_NO, MESSAGE_BROKER_KAFKA, MESSAGE_BROKER_PULSAR], + messageBroker: ['no', 'kafka', 'pulsar'], }); matrix = extendFilteredMatrix(matrix, ({ reactive }) => !reactive, { diff --git a/generators/entity/database-changelog.spec.ts b/generators/entity/database-changelog.spec.ts index 5ded0b45dfdb..e5af64d05166 100644 --- a/generators/entity/database-changelog.spec.ts +++ b/generators/entity/database-changelog.spec.ts @@ -36,9 +36,7 @@ describe('generator - entity database changelogs', () => { await helpers .run(getGenerator('entity')) .withGenerators([[MockedLanguagesGenerator, { namespace: 'jhipster:languages' }]]) - .withJHipsterConfig({ applicationType: 'gateway' }, [ - { ...entityFoo, microservicePath: 'microservice1', microserviceName: 'microservice1' }, - ]) + .withJHipsterConfig({ applicationType: 'gateway' }, [{ ...entityFoo, microserviceName: 'microservice1' }]) .withArguments(['Foo']) .withOptions({ regenerate: true, force: true, ignoreNeedlesError: true }); }); diff --git a/generators/export-jdl/generator.ts b/generators/export-jdl/generator.ts index 398b64e293d9..9591cdf6320a 100644 --- a/generators/export-jdl/generator.ts +++ b/generators/export-jdl/generator.ts @@ -23,7 +23,6 @@ import BaseGenerator from '../base/index.js'; import { applicationOptions } from '../../lib/jhipster/index.js'; import { convertToJDL } from '../../lib/jdl/converters/json-to-jdl-converter.js'; import type { JHipsterGeneratorFeatures, JHipsterGeneratorOptions } from '../base/api.js'; -import jdlDefinition from '../app/jdl/index.js'; const { OptionNames } = applicationOptions; @@ -51,7 +50,7 @@ export default class extends BaseGenerator { return this.asDefaultTaskGroup({ convertToJDL() { try { - const jdlObject = convertToJDL(this.destinationPath(), false, jdlDefinition); + const jdlObject = convertToJDL(this.destinationPath(), false, this.options.jdlDefinition); if (jdlObject) { this.jdlContent = jdlObject.toString(); } diff --git a/generators/jdl/__snapshots__/generator.spec.ts.snap b/generators/jdl/__snapshots__/generator.spec.ts.snap index 1f27ae27a0ad..fde5cf097796 100644 --- a/generators/jdl/__snapshots__/generator.spec.ts.snap +++ b/generators/jdl/__snapshots__/generator.spec.ts.snap @@ -198,7 +198,6 @@ Options: --cache-provider Cache provider --enable-swagger-codegen API first development using OpenAPI-generator --enable-hibernate-cache Enable hibernate cache - --message-broker message broker --search-engine Provide search engine for the application when skipping server side generation --skip-check-length-of-identifier Skip check the length of the identifier, only for recent Oracle databases that support 30+ characters metadata --skip-db-changelog Skip the generation of database migrations @@ -212,6 +211,8 @@ Options: --auth Provide authentication type for the application when skipping server side generation (choices: "jwt", "oauth2", "session") --feign-client Generate a feign client --sync-user-with-idp Allow relationships with User for oauth2 applications + --message-broker message broker (choices: "kafka", "pulsar", "no") + --database-migration Database migration (choices: "liquibase") --with-generated-flag Add a GeneratedByJHipster annotation to all generated java classes and interfaces --package-name The package name for the generated application --build Provide build tool for the application when skipping server side generation (choices: "maven", "gradle") diff --git a/generators/jdl/generator.ts b/generators/jdl/generator.ts index 426576cae2f3..0eb7f1cac601 100644 --- a/generators/jdl/generator.ts +++ b/generators/jdl/generator.ts @@ -33,7 +33,6 @@ import { GENERATOR_JHIPSTER, JHIPSTER_CONFIG_DIR } from '../generator-constants. import { mergeYoRcContent } from '../../lib/utils/yo-rc.js'; import { normalizeBlueprintName } from '../base/internal/blueprint.js'; import { updateApplicationEntitiesTransform } from '../base-application/support/update-application-entities-transform.js'; -import jdlDefinition from '../app/jdl/index.js'; import { addApplicationIndex, allNewApplications, customizeForMicroservices } from './internal/index.js'; /** @@ -135,7 +134,7 @@ export default class JdlGenerator extends BaseGenerator { skipUserManagement: this.options.skipUserManagement, }; - const importer = createImporterFromContent(this.jdlContents.join('\n'), configuration, jdlDefinition); + const importer = createImporterFromContent(this.jdlContents.join('\n'), configuration, this.options.jdlDefinition); const importState = importer.import(); diff --git a/generators/kubernetes-helm/generator.ts b/generators/kubernetes-helm/generator.ts index c6be90e8a1ec..76cdbab45bfa 100644 --- a/generators/kubernetes-helm/generator.ts +++ b/generators/kubernetes-helm/generator.ts @@ -114,10 +114,10 @@ export default class KubernetesHelmGenerator extends BaseWorkspacesGenerator { return { loadFromYoRc, loadSharedConfig() { - this.appConfigs.forEach(element => { - loadDerivedAppConfig({ application: element }); - loadDerivedServerConfig({ application: element }); - }); + for (const app of this.appConfigs) { + loadDerivedAppConfig({ application: app }); + loadDerivedServerConfig({ application: app }); + } loadDeploymentConfig.call(this); derivedKubernetesPlatformProperties(this); }, diff --git a/generators/kubernetes-knative/generator.ts b/generators/kubernetes-knative/generator.ts index 28ec1ac1693c..e6cab36183bc 100644 --- a/generators/kubernetes-knative/generator.ts +++ b/generators/kubernetes-knative/generator.ts @@ -132,10 +132,10 @@ export default class KubernetesKnativeGenerator extends BaseWorkspacesGenerator return { loadFromYoRc, loadSharedConfig() { - this.appConfigs.forEach(element => { - loadDerivedAppConfig({ application: element }); - loadDerivedServerConfig({ application: element }); - }); + for (const app of this.appConfigs) { + loadDerivedAppConfig({ application: app }); + loadDerivedServerConfig({ application: app }); + } loadDeploymentConfig.call(this); derivedKubernetesPlatformProperties(this); }, diff --git a/generators/kubernetes/generator.ts b/generators/kubernetes/generator.ts index 900cdd9092a3..dafa5580f6ae 100644 --- a/generators/kubernetes/generator.ts +++ b/generators/kubernetes/generator.ts @@ -109,10 +109,10 @@ export default class KubernetesGenerator extends BaseWorkspacesGenerator { return { loadFromYoRc, loadSharedConfig() { - this.appConfigs.forEach(element => { - loadDerivedAppConfig({ application: element }); - loadDerivedServerConfig({ application: element }); - }); + for (const app of this.appConfigs) { + loadDerivedAppConfig({ application: app }); + loadDerivedServerConfig({ application: app }); + } loadDeploymentConfig.call(this); derivedKubernetesPlatformProperties(this); }, diff --git a/generators/kubernetes/kubernetes-base.ts b/generators/kubernetes/kubernetes-base.ts index 578cddea1c4f..f227b97028d0 100644 --- a/generators/kubernetes/kubernetes-base.ts +++ b/generators/kubernetes/kubernetes-base.ts @@ -20,7 +20,6 @@ import crypto from 'crypto'; import { defaults } from 'lodash-es'; -import { loadFromYoRc } from '../base-workspaces/internal/docker-base.js'; import { HELM_COUCHBASE_OPERATOR, HELM_ELASTICSEARCH, @@ -76,7 +75,6 @@ export const checkHelm = async function () { }; export function loadConfig() { - loadFromYoRc.call(this); if (!this.jhipsterConfig.dbRandomPassword) { this.jhipsterConfig.dbRandomPassword = this.options.reproducibleTests ? 'SECRET-PASSWORD' : crypto.randomBytes(30).toString('hex'); } diff --git a/generators/liquibase/jdl.spec.ts b/generators/liquibase/jdl.spec.ts deleted file mode 100644 index bad90de45250..000000000000 --- a/generators/liquibase/jdl.spec.ts +++ /dev/null @@ -1,21 +0,0 @@ -import { before, describe, expect, it } from 'esmocha'; -import type { ImportState } from '../../lib/jdl/jdl-importer.js'; -import { createImporterFromContent } from '../../lib/jdl/jdl-importer.js'; -import definition from '../app/jdl/index.js'; - -describe(`generators - server - jdl - incrementalChangelog`, () => { - [true, false].forEach(optionValue => { - describe(`with ${optionValue} value`, () => { - let state: ImportState; - - before(() => { - const importer = createImporterFromContent(`application { config { incrementalChangelog ${optionValue} } }`, undefined, definition); - state = importer.import(); - }); - - it('should set expected value', () => { - expect(state.exportedApplicationsWithEntities.jhipster.config.incrementalChangelog).toBe(optionValue); - }); - }); - }); -}); diff --git a/generators/server/command.ts b/generators/server/command.ts index de1f206a856c..2f966e19b0e6 100644 --- a/generators/server/command.ts +++ b/generators/server/command.ts @@ -53,11 +53,6 @@ const command = { type: Boolean, scope: 'storage', }, - messageBroker: { - description: 'message broker', - type: String, - scope: 'storage', - }, searchEngine: { description: 'Provide search engine for the application when skipping server side generation', type: String, diff --git a/generators/server/jdl/application-definition.ts b/generators/server/jdl/application-definition.ts deleted file mode 100644 index b1420de38f20..000000000000 --- a/generators/server/jdl/application-definition.ts +++ /dev/null @@ -1,69 +0,0 @@ -/** - * Copyright 2013-2024 the original author or authors from the JHipster project. - * - * This file is part of the JHipster project, see https://www.jhipster.tech/ - * for more information. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -import { snakeCase, upperCase } from 'lodash-es'; -import type { JDLApplicationConfig, JHipsterOptionDefinition } from '../../../lib/jdl/core/types/parsing.js'; -import databaseMigrationOption from '../options/database-migration.js'; -import messageBrokerOption from '../options/message-broker.js'; -import { syncUserWithIdpDefinition } from '../options/index.js'; -import { jdlRoutesOptions } from '../../spring-cloud/generators/gateway/jdl/jdl-routes-option.js'; -import { default as liquibaseCommand } from '../../liquibase/command.js'; -import { default as springBootCommand } from '../../spring-boot/command.js'; -import { extractJdlDefinitionFromCommandConfig } from '../../../lib/command/index.js'; - -const jdlOptions: JHipsterOptionDefinition[] = [ - databaseMigrationOption, - messageBrokerOption, - syncUserWithIdpDefinition, - jdlRoutesOptions, - ...extractJdlDefinitionFromCommandConfig(springBootCommand.configs), - ...extractJdlDefinitionFromCommandConfig(liquibaseCommand.configs), -]; - -const applicationConfig: JDLApplicationConfig = { - quotedOptionNames: [], - tokenConfigs: jdlOptions.map(option => ({ - name: upperCase(snakeCase(option.name)), - pattern: option.name, - })), - validatorConfig: Object.fromEntries( - jdlOptions.map(option => [ - upperCase(snakeCase(option.name)), - { - type: option.tokenType, - pattern: option.tokenValuePattern, - msg: `${option.name} property`, - }, - ]), - ), - optionsValues: Object.fromEntries( - jdlOptions - .filter(option => option.knownChoices) - .map(option => [option.name, Object.fromEntries(option.knownChoices!.map(choice => [choice, choice]))]), - ), - optionsTypes: Object.fromEntries( - jdlOptions.map(option => [ - option.name, - { - type: option.type, - }, - ]), - ), -}; - -export default applicationConfig; diff --git a/generators/server/jdl/index.ts b/generators/server/jdl/index.ts deleted file mode 100644 index c72947223780..000000000000 --- a/generators/server/jdl/index.ts +++ /dev/null @@ -1,19 +0,0 @@ -/** - * Copyright 2013-2024 the original author or authors from the JHipster project. - * - * This file is part of the JHipster project, see https://www.jhipster.tech/ - * for more information. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -export { default } from './application-definition.js'; diff --git a/generators/server/options/database-migration.spec.ts b/generators/server/options/database-migration.spec.ts deleted file mode 100644 index 331e45af7278..000000000000 --- a/generators/server/options/database-migration.spec.ts +++ /dev/null @@ -1,23 +0,0 @@ -import { before, describe, expect, it } from 'esmocha'; -import type { ImportState } from '../../../lib/jdl/jdl-importer.js'; -import { createImporterFromContent } from '../../../lib/jdl/jdl-importer.js'; -import definition from '../../app/jdl/index.js'; -import optionDefinition from './database-migration.js'; -import { DATABASE_MIGRATION as optionName } from './index.js'; - -describe(`generators - server - jdl - ${optionName}`, () => { - optionDefinition.knownChoices!.forEach(optionValue => { - describe(`with ${optionValue} value`, () => { - let state: ImportState; - - before(() => { - const importer = createImporterFromContent(`application { config { ${optionName} ${optionValue} } }`, definition); - state = importer.import(); - }); - - it('should set expected value', () => { - expect(state.exportedApplicationsWithEntities.jhipster.config[optionName]).toBe(optionValue); - }); - }); - }); -}); diff --git a/generators/server/options/database-migration.ts b/generators/server/options/database-migration.ts deleted file mode 100644 index 3e54544015ba..000000000000 --- a/generators/server/options/database-migration.ts +++ /dev/null @@ -1,35 +0,0 @@ -/** - * Copyright 2013-2024 the original author or authors from the JHipster project. - * - * This file is part of the JHipster project, see https://www.jhipster.tech/ - * for more information. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import type { JHipsterOptionDefinition } from '../../../lib/jdl/core/types/parsing.js'; - -export const DATABASE_MIGRATION = 'databaseMigration'; -export const DATABASE_MIGRATION_LIQUIBASE = 'liquibase'; - -const ALPHANUMERIC_PATTERN = /^[A-Za-z][A-Za-z0-9]*$/; - -const optionDefinition: JHipsterOptionDefinition = { - name: DATABASE_MIGRATION, - type: 'string', - tokenType: 'NAME', - tokenValuePattern: ALPHANUMERIC_PATTERN, - knownChoices: [DATABASE_MIGRATION_LIQUIBASE], -}; - -export default optionDefinition; diff --git a/generators/server/options/index.ts b/generators/server/options/index.ts deleted file mode 100644 index ca90eaab7319..000000000000 --- a/generators/server/options/index.ts +++ /dev/null @@ -1,22 +0,0 @@ -/** - * Copyright 2013-2024 the original author or authors from the JHipster project. - * - * This file is part of the JHipster project, see https://www.jhipster.tech/ - * for more information. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -export * from './database-migration.js'; -export * from './message-broker.js'; -export * from './sync-user-with-idp.js'; diff --git a/generators/server/options/message-broker.spec.ts b/generators/server/options/message-broker.spec.ts deleted file mode 100644 index 4c6fed21d676..000000000000 --- a/generators/server/options/message-broker.spec.ts +++ /dev/null @@ -1,23 +0,0 @@ -import { before, describe, expect, it } from 'esmocha'; -import type { ImportState } from '../../../lib/jdl/jdl-importer.js'; -import { createImporterFromContent } from '../../../lib/jdl/jdl-importer.js'; -import definition from '../../app/jdl/index.js'; -import optionDefinition from './message-broker.js'; -import { MESSAGE_BROKER } from './index.js'; - -describe('generators - server - jdl - messageBroker', () => { - optionDefinition.knownChoices!.forEach(optionValue => { - describe(`with ${optionValue} value`, () => { - let state: ImportState; - - before(() => { - const importer = createImporterFromContent(`application { config { ${MESSAGE_BROKER} ${optionValue} } }`, undefined, definition); - state = importer.import(); - }); - - it('should set expected value', () => { - expect(state.exportedApplicationsWithEntities.jhipster.config[MESSAGE_BROKER]).toBe(optionValue); - }); - }); - }); -}); diff --git a/generators/server/options/message-broker.ts b/generators/server/options/message-broker.ts deleted file mode 100644 index 8ba3099176ce..000000000000 --- a/generators/server/options/message-broker.ts +++ /dev/null @@ -1,43 +0,0 @@ -/** - * Copyright 2013-2024 the original author or authors from the JHipster project. - * - * This file is part of the JHipster project, see https://www.jhipster.tech/ - * for more information. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import type { JHipsterOptionDefinition } from '../../../lib/jdl/core/types/parsing.js'; -import type { OptionWithDerivedProperties } from '../../base-application/application-options.js'; - -export const MESSAGE_BROKER = 'messageBroker'; - -export const MESSAGE_BROKER_KAFKA = 'kafka'; -export const MESSAGE_BROKER_PULSAR = 'pulsar'; -export const MESSAGE_BROKER_NO = 'no'; - -const ALPHANUMERIC_PATTERN = /^[A-Za-z][A-Za-z0-9]*$/; - -const optionDefinition: JHipsterOptionDefinition = { - name: MESSAGE_BROKER, - type: 'string', - tokenType: 'NAME', - tokenValuePattern: ALPHANUMERIC_PATTERN, - knownChoices: [MESSAGE_BROKER_NO, MESSAGE_BROKER_KAFKA, MESSAGE_BROKER_PULSAR], -}; - -export default optionDefinition; - -type MessageBrokerTypes = [typeof MESSAGE_BROKER_KAFKA, typeof MESSAGE_BROKER_PULSAR, typeof MESSAGE_BROKER_NO]; - -export type MessageBrokerApplicationType = OptionWithDerivedProperties; diff --git a/generators/server/options/sync-user-with-idp.spec.ts b/generators/server/options/sync-user-with-idp.spec.ts deleted file mode 100644 index 4bb808a56168..000000000000 --- a/generators/server/options/sync-user-with-idp.spec.ts +++ /dev/null @@ -1,22 +0,0 @@ -import { before, describe, expect, it } from 'esmocha'; -import type { ImportState } from '../../../lib/jdl/jdl-importer.js'; -import { createImporterFromContent } from '../../../lib/jdl/jdl-importer.js'; -import definition from '../../app/jdl/index.js'; -import { SYNC_USER_WITH_IDP as optionName } from './sync-user-with-idp.js'; - -describe(`generators - server - jdl - ${optionName}`, () => { - [true, false].forEach(optionValue => { - describe(`with ${optionValue} value`, () => { - let state: ImportState; - - before(() => { - const importer = createImporterFromContent(`application { config { ${optionName} ${optionValue} } }`, undefined, definition); - state = importer.import(); - }); - - it('should set expected value', () => { - expect(state.exportedApplicationsWithEntities.jhipster.config[optionName]).toBe(optionValue); - }); - }); - }); -}); diff --git a/generators/server/options/sync-user-with-idp.ts b/generators/server/options/sync-user-with-idp.ts deleted file mode 100644 index 7f8a2954376d..000000000000 --- a/generators/server/options/sync-user-with-idp.ts +++ /dev/null @@ -1,28 +0,0 @@ -/** - * Copyright 2013-2024 the original author or authors from the JHipster project. - * - * This file is part of the JHipster project, see https://www.jhipster.tech/ - * for more information. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import type { JHipsterOptionDefinition } from '../../../lib/jdl/core/types/parsing.js'; - -export const SYNC_USER_WITH_IDP = 'syncUserWithIdp'; - -export const syncUserWithIdpDefinition: JHipsterOptionDefinition = { - name: SYNC_USER_WITH_IDP, - type: 'boolean', - tokenType: 'BOOLEAN', -}; diff --git a/generators/server/support/config.ts b/generators/server/support/config.ts index 31fe8cf18e69..e4be61c92094 100644 --- a/generators/server/support/config.ts +++ b/generators/server/support/config.ts @@ -20,7 +20,6 @@ import { SERVER_TEST_RES_DIR, SERVER_TEST_SRC_DIR, } from '../../generator-constants.js'; -import { MESSAGE_BROKER_KAFKA, MESSAGE_BROKER_NO, MESSAGE_BROKER_PULSAR } from '../../server/options/index.js'; import type { PlatformApplication } from '../../base-application/types.js'; const { NO: NO_DATABASE, SQL, MONGODB, COUCHBASE, NEO4J, CASSANDRA } = databaseTypes; @@ -125,13 +124,6 @@ export const loadDerivedServerConfig = ({ application }: { application: any }) = application.searchEngineCouchbase = application.searchEngine === COUCHBASE; application.searchEngineElasticsearch = application.searchEngine === ELASTICSEARCH; - if (!application.messageBroker) { - application.messageBroker = MESSAGE_BROKER_NO; - } - application.messageBrokerKafka = application.messageBroker === MESSAGE_BROKER_KAFKA; - application.messageBrokerPulsar = application.messageBroker === MESSAGE_BROKER_PULSAR; - application.messageBrokerAny = application.messageBroker && application.messageBroker !== MESSAGE_BROKER_NO; - application.buildToolGradle = application.buildTool === GRADLE; application.buildToolMaven = application.buildTool === MAVEN; application.buildToolUnknown = !application.buildToolGradle && !application.buildToolMaven; diff --git a/generators/server/support/relationship.ts b/generators/server/support/relationship.ts index cf7df3b591d8..77d17d23eafa 100644 --- a/generators/server/support/relationship.ts +++ b/generators/server/support/relationship.ts @@ -43,7 +43,7 @@ export const addEntitiesOtherRelationships = (entities: Entity[]): ValidationRes `Ignoring '${entity.name}' definitions as it is using a built-in Entity '${relationship.otherEntityName}': 'otherEntityRelationshipName' is set with value '${relationship.otherEntityRelationshipName}' at relationship '${relationship.relationshipName}' but no back-reference was found`, ); } else { - relationship.otherRelationship = addOtherRelationship(entity, relationship.otherEntity, relationship) as Relationship; + relationship.otherRelationship = addOtherRelationship(entity, relationship.otherEntity as Entity, relationship) as Relationship; } } } diff --git a/generators/server/types.d.ts b/generators/server/types.d.ts index 13b537875d49..2bb1c91ead28 100644 --- a/generators/server/types.d.ts +++ b/generators/server/types.d.ts @@ -7,7 +7,6 @@ import type { OptionWithDerivedProperties } from '../base-application/applicatio import type { GatewayApplication } from '../spring-cloud/generators/gateway/types.js'; import type { JavaAnnotation } from '../java/support/add-java-annotation.ts'; import type { ApplicationPropertiesNeedles } from './support/needles.ts'; -import type { MessageBrokerApplicationType } from './options/message-broker.js'; export type SpringEntity = { /* Generate entity's Entity */ @@ -117,8 +116,7 @@ export type SpringBootApplication = JavaApplication & BuildToolApplication & SearchEngine & DatabaseTypeApplication & - GatewayApplication & - MessageBrokerApplicationType & { + GatewayApplication & { jhipsterDependenciesVersion: string; springBootDependencies: Record; dockerContainers: Record; diff --git a/generators/spring-boot/command.ts b/generators/spring-boot/command.ts index c57815e64bc7..8ba772e0034e 100644 --- a/generators/spring-boot/command.ts +++ b/generators/spring-boot/command.ts @@ -25,6 +25,8 @@ import { applicationTypes, authenticationTypes } from '../../lib/jhipster/index. const { OAUTH2, SESSION, JWT } = authenticationTypes; const { GATEWAY, MICROSERVICE } = applicationTypes; +const ALPHANUMERIC_PATTERN = /^[A-Za-z][A-Za-z0-9]*$/; + const command = { options: { fakeKeytool: { @@ -146,6 +148,10 @@ const command = { message: 'Do you want to allow relationships with User entity?', when: ({ authenticationType }) => (authenticationType ?? gen.jhipsterConfigWithDefaults.authenticationType) === 'oauth2', }), + jdl: { + type: 'boolean', + tokenType: 'BOOLEAN', + }, configure: gen => { if (gen.jhipsterConfig.syncUserWithIdp === undefined && gen.jhipsterConfigWithDefaults.authenticationType === 'oauth2') { if (gen.isJhipsterVersionLessThan('8.1.1')) { @@ -180,8 +186,34 @@ const command = { choices: ['sql', 'mongodb', 'couchbase', 'cassandra', 'neo4j', 'no'], scope: 'storage', }, + messageBroker: { + description: 'message broker', + cli: { + type: String, + }, + jdl: { + type: 'string', + tokenType: 'NAME', + tokenValuePattern: ALPHANUMERIC_PATTERN, + }, + choices: ['kafka', 'pulsar', 'no'], + scope: 'storage', + }, + databaseMigration: { + description: 'Database migration', + cli: { + type: String, + }, + jdl: { + type: 'string', + tokenType: 'NAME', + tokenValuePattern: ALPHANUMERIC_PATTERN, + }, + choices: ['liquibase'], + scope: 'storage', + }, }, - import: [GENERATOR_JAVA, GENERATOR_LIQUIBASE, GENERATOR_SPRING_DATA_RELATIONAL], + import: [GENERATOR_JAVA, GENERATOR_LIQUIBASE, GENERATOR_SPRING_DATA_RELATIONAL, 'jhipster:spring-cloud:gateway'], } as const satisfies JHipsterCommandDefinition; export default command; diff --git a/generators/spring-boot/options/feign-client.spec.ts b/generators/spring-boot/options/feign-client.spec.ts deleted file mode 100644 index 013be7af896b..000000000000 --- a/generators/spring-boot/options/feign-client.spec.ts +++ /dev/null @@ -1,23 +0,0 @@ -import { before, describe, expect, it } from 'esmocha'; -import type { ImportState } from '../../../lib/jdl/jdl-importer.js'; -import { createImporterFromContent } from '../../../lib/jdl/jdl-importer.js'; -import definition from '../../app/jdl/index.js'; - -const optionName = 'feignClient'; - -describe(`generators - server - jdl - ${optionName}`, () => { - [true, false].forEach(optionValue => { - describe(`with ${optionValue} value`, () => { - let state: ImportState; - - before(() => { - const importer = createImporterFromContent(`application { config { ${optionName} ${optionValue} } }`, undefined, definition); - state = importer.import(); - }); - - it('should set expected value', () => { - expect(state.exportedApplicationsWithEntities.jhipster.config[optionName]).toBe(optionValue); - }); - }); - }); -}); diff --git a/generators/spring-boot/prompts.ts b/generators/spring-boot/prompts.ts index 26fc96f92477..8b023b3d559f 100644 --- a/generators/spring-boot/prompts.ts +++ b/generators/spring-boot/prompts.ts @@ -28,7 +28,6 @@ import { databaseTypes, testFrameworkTypes, } from '../../lib/jhipster/index.js'; -import { MESSAGE_BROKER } from '../server/options/index.js'; import { R2DBC_DB_OPTIONS, SQL_DB_OPTIONS } from '../server/support/database.js'; import type CoreGenerator from '../base-core/generator.js'; @@ -248,7 +247,7 @@ export async function askForOptionalItems(this: CoreGenerator, { control }) { this.jhipsterConfig.serverSideOptions = answers.serverSideOptions; this.jhipsterConfig.websocket = getOptionFromArray(answers.serverSideOptions, WEBSOCKET); this.jhipsterConfig.searchEngine = getOptionFromArray(answers.serverSideOptions, SEARCH_ENGINE); - this.jhipsterConfig.messageBroker = getOptionFromArray(answers.serverSideOptions, MESSAGE_BROKER); + this.jhipsterConfig.messageBroker = getOptionFromArray(answers.serverSideOptions, 'messageBroker'); this.jhipsterConfig.enableSwaggerCodegen = getOptionFromArray(answers.serverSideOptions, ENABLE_SWAGGER_CODEGEN); // Only set this option if it hasn't been set in a previous question, as it's only optional for monoliths if (!this.jhipsterConfig.serviceDiscoveryType) { diff --git a/generators/spring-cloud/generators/gateway/command.ts b/generators/spring-cloud/generators/gateway/command.ts index aaca7c1d83e0..4c94b58c4f82 100644 --- a/generators/spring-cloud/generators/gateway/command.ts +++ b/generators/spring-cloud/generators/gateway/command.ts @@ -18,18 +18,23 @@ */ import type { JHipsterCommandDefinition } from '../../../../lib/command/index.js'; -const command: JHipsterCommandDefinition = { +const command = { configs: { routes: { description: 'Manually configured gateway routes', cli: { - type: String, + type: Array, hide: true, }, + jdl: { + tokenType: 'quotedList', + type: 'quotedList', + tokenValuePattern: /^"[A-Za-z][A-Za-z0-9_]*(?::[A-Za-z][A-Za-z0-9_]+(?::[0-9]+)?)?"$/, + }, scope: 'storage', }, }, import: [], -}; +} as const satisfies JHipsterCommandDefinition; export default command; diff --git a/generators/spring-cloud/generators/gateway/jdl/jdl-routes-option.ts b/generators/spring-cloud/generators/gateway/jdl/jdl-routes-option.ts deleted file mode 100644 index aaa34278f6c2..000000000000 --- a/generators/spring-cloud/generators/gateway/jdl/jdl-routes-option.ts +++ /dev/null @@ -1,8 +0,0 @@ -import type { JHipsterOptionDefinition } from '../../../../../lib/jdl/core/types/parsing.js'; - -export const jdlRoutesOptions: JHipsterOptionDefinition = { - name: 'routes', - tokenType: 'quotedList', - type: 'quotedList', - tokenValuePattern: /^"[A-Za-z][A-Za-z0-9_]*(?::[A-Za-z][A-Za-z0-9_]+(?::[0-9]+)?)?"$/, -}; diff --git a/lib/command/__snapshots__/jdl.spec.ts.snap b/lib/command/__snapshots__/jdl.spec.ts.snap new file mode 100644 index 000000000000..0bed52eaf089 --- /dev/null +++ b/lib/command/__snapshots__/jdl.spec.ts.snap @@ -0,0 +1,86 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`getDefaultJDLApplicationConfig() should return the default JDL application config 1`] = ` +{ + "optionsTypes": { + "databaseMigration": { + "type": "string", + }, + "feignClient": { + "type": "boolean", + }, + "incrementalChangelog": { + "type": "boolean", + }, + "messageBroker": { + "type": "string", + }, + "routes": { + "type": "quotedList", + }, + "syncUserWithIdp": { + "type": "boolean", + }, + }, + "optionsValues": {}, + "quotedOptionNames": [], + "tokenConfigs": [ + { + "name": "DATABASE MIGRATION", + "pattern": "databaseMigration", + }, + { + "name": "FEIGN CLIENT", + "pattern": "feignClient", + }, + { + "name": "INCREMENTAL CHANGELOG", + "pattern": "incrementalChangelog", + }, + { + "name": "MESSAGE BROKER", + "pattern": "messageBroker", + }, + { + "name": "ROUTES", + "pattern": "routes", + }, + { + "name": "SYNC USER WITH IDP", + "pattern": "syncUserWithIdp", + }, + ], + "validatorConfig": { + "DATABASE MIGRATION": { + "msg": "databaseMigration property", + "pattern": /\\^\\[A-Za-z\\]\\[A-Za-z0-9\\]\\*\\$/, + "type": "NAME", + }, + "FEIGN CLIENT": { + "msg": "feignClient property", + "pattern": undefined, + "type": "BOOLEAN", + }, + "INCREMENTAL CHANGELOG": { + "msg": "incrementalChangelog property", + "pattern": undefined, + "type": "BOOLEAN", + }, + "MESSAGE BROKER": { + "msg": "messageBroker property", + "pattern": /\\^\\[A-Za-z\\]\\[A-Za-z0-9\\]\\*\\$/, + "type": "NAME", + }, + "ROUTES": { + "msg": "routes property", + "pattern": /\\^"\\[A-Za-z\\]\\[A-Za-z0-9_\\]\\*\\(\\?::\\[A-Za-z\\]\\[A-Za-z0-9_\\]\\+\\(\\?::\\[0-9\\]\\+\\)\\?\\)\\?"\\$/, + "type": "quotedList", + }, + "SYNC USER WITH IDP": { + "msg": "syncUserWithIdp property", + "pattern": undefined, + "type": "BOOLEAN", + }, + }, +} +`; diff --git a/lib/command/converter.ts b/lib/command/converter.ts index 381dca3bd49f..72ae0666db8c 100644 --- a/lib/command/converter.ts +++ b/lib/command/converter.ts @@ -21,10 +21,11 @@ export const extractJdlDefinitionFromCommandConfig = (configs: JHipsterConfigs = Object.entries(configs) .filter(([_name, def]) => def.jdl) .map(([name, def]) => ({ - ...(def.jdl as Omit), + ...(def.jdl as Omit), name, - knownValues: def.choices?.map(choice => (typeof choice === 'string' ? choice : choice.value)), - })); + knownChoices: def.choices?.map(choice => (typeof choice === 'string' ? choice : choice.value)), + })) + .sort((a, b) => (b.name.startsWith(a.name) ? 1 : a.name.localeCompare(b.name))); export const convertConfigToOption = (name: string, config?: ConfigSpec): JHipsterOption | undefined => { if (!config?.cli?.type) return undefined; diff --git a/lib/command/jdl-options.spec.ts b/lib/command/jdl-options.spec.ts new file mode 100644 index 000000000000..74517cbce486 --- /dev/null +++ b/lib/command/jdl-options.spec.ts @@ -0,0 +1,72 @@ +import { before, describe, expect, it } from 'esmocha'; +import type { ImportState } from '../jdl/jdl-importer.js'; +import { createImporterFromContent } from '../jdl/jdl-importer.js'; +import { lookupCommandsConfigs } from './lookup-commands-configs.js'; + +const jhipsterConfigsWithJDL = await lookupCommandsConfigs({ filter: config => Boolean(config.jdl) }); + +describe('jdl options', () => { + const jdlConfigs = Object.entries(jhipsterConfigsWithJDL!); + + it('jdl configs names should match snapshot', () => { + expect(jdlConfigs.map(([name]) => name)).toMatchInlineSnapshot(` +[ + "feignClient", + "syncUserWithIdp", + "messageBroker", + "databaseMigration", + "incrementalChangelog", + "routes", +] +`); + }); + + for (const [optionName, config] of jdlConfigs) { + let choices: any[] | undefined = config.choices?.map(choice => (typeof choice === 'string' ? choice : choice.value)); + const isBoolean = config.cli?.type === Boolean; + const isArray = config.cli?.type === Array; + if (!choices && isBoolean) { + choices = [true, false]; + } + + if (!choices) { + if (['routes'].includes(optionName)) { + // Option is manually tested. + continue; + } + throw new Error(`No choices found for ${optionName}`); + } + + describe(`generators - server - jdl - ${optionName}`, function () { + choices.forEach(optionValue => { + if (isArray) { + optionValue = `[${optionValue}]`; + } + describe(`with ${optionValue} value`, () => { + let state: ImportState; + + before(() => { + const importer = createImporterFromContent(`application { config { ${optionName} ${optionValue} } }`); + state = importer.import(); + }); + + it('should set expected value', () => { + expect(state.exportedApplicationsWithEntities.jhipster.config[optionName]).toBe(optionValue); + }); + }); + }); + + if (isBoolean) { + it('should not accept unknown value when creating importer', () => { + expect(() => createImporterFromContent(`application { config { ${optionName} unknown } }`)).toThrow(/, but found: "unknown"/); + }); + } else { + it('should not accept unknown value when importing', () => { + expect(() => + createImporterFromContent(`application { config { ${optionName} ${isArray ? `[unknown]` : 'unknown'} } }`).import(), + ).toThrow(/The value 'unknown' is not allowed for the option '(.*)'/); + }); + } + }); + } +}); diff --git a/lib/command/jdl.spec.ts b/lib/command/jdl.spec.ts new file mode 100644 index 000000000000..4284fc456dc9 --- /dev/null +++ b/lib/command/jdl.spec.ts @@ -0,0 +1,12 @@ +import { describe, expect, it } from 'esmocha'; +import { lookupCommandsConfigs } from './lookup-commands-configs.js'; +import { buildJDLApplicationConfig, getDefaultJDLApplicationConfig } from './jdl.js'; + +describe('getDefaultJDLApplicationConfig()', () => { + it('should return the default JDL application config', async () => { + const configs = await lookupCommandsConfigs(); + const discoveredConfigs = buildJDLApplicationConfig(Object.fromEntries(Object.entries(configs).filter(([_key, value]) => value.jdl))); + expect(getDefaultJDLApplicationConfig()).toMatchObject(discoveredConfigs); + expect(getDefaultJDLApplicationConfig()).toMatchSnapshot(); + }); +}); diff --git a/lib/command/jdl.ts b/lib/command/jdl.ts new file mode 100644 index 000000000000..3d799c225d94 --- /dev/null +++ b/lib/command/jdl.ts @@ -0,0 +1,71 @@ +/** + * Copyright 2013-2024 the original author or authors from the JHipster project. + * + * This file is part of the JHipster project, see https://www.jhipster.tech/ + * for more information. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { snakeCase, upperCase } from 'lodash-es'; +import type { JDLApplicationConfig } from '../jdl/core/types/parsing.js'; +import springBootCommand from '../../generators/spring-boot/command.js'; +import liquibaseCommand from '../../generators/liquibase/command.js'; +import gatewayCommand from '../../generators/spring-cloud/generators/gateway/command.js'; +import { extractJdlDefinitionFromCommandConfig } from './converter.js'; +import type { JHipsterConfigs } from './types.js'; + +export const buildJDLApplicationConfig = (configs: JHipsterConfigs): JDLApplicationConfig => { + const jdlOptions = extractJdlDefinitionFromCommandConfig(configs); + return { + quotedOptionNames: [], + tokenConfigs: jdlOptions.map(option => ({ + name: upperCase(snakeCase(option.name)), + pattern: option.name, + })), + validatorConfig: Object.fromEntries( + jdlOptions.map(option => [ + upperCase(snakeCase(option.name)), + { + type: option.tokenType, + pattern: option.tokenValuePattern, + msg: `${option.name} property`, + }, + ]), + ), + optionsValues: Object.fromEntries( + jdlOptions + .filter(option => option.knownChoices) + .map(option => [option.name, Object.fromEntries(option.knownChoices!.map(choice => [choice, choice]))]), + ), + optionsTypes: Object.fromEntries( + jdlOptions.map(option => [ + option.name, + { + type: option.type, + }, + ]), + ), + }; +}; + +let defaultJDLApplicationConfig: JDLApplicationConfig; +export const getDefaultJDLApplicationConfig = () => { + if (defaultJDLApplicationConfig === undefined) { + defaultJDLApplicationConfig = buildJDLApplicationConfig({ + ...springBootCommand.configs, + ...liquibaseCommand.configs, + ...gatewayCommand.configs, + }); + } + return defaultJDLApplicationConfig; +}; diff --git a/lib/command/load.ts b/lib/command/load.ts new file mode 100644 index 000000000000..c9b349c0e05c --- /dev/null +++ b/lib/command/load.ts @@ -0,0 +1,110 @@ +import { upperFirst } from 'lodash-es'; +import type { CommandConfigScope, JHipsterConfigs, JHispterChoices } from './types.js'; + +const prepareChoices = (key: string, choices: JHispterChoices) => + choices + .map(choice => (typeof choice === 'string' ? { value: choice } : choice)) + .map(choice => ({ ...choice, choiceKey: `${key}${upperFirst(choice.value)}` })); + +const filteredScopeEntries = (commandsConfigs: JHipsterConfigs, scopes: CommandConfigScope[]) => + Object.entries(commandsConfigs).filter(([_key, def]) => scopes.includes(def.scope!)); + +function loadConfigIntoContext( + this: Context, + options: { + applyDefaults?: boolean; + source?: Record; + templatesContext: Record; + commandsConfigs: JHipsterConfigs; + scopes: CommandConfigScope[]; + }, +): void { + const { applyDefaults, source, templatesContext, commandsConfigs, scopes = ['storage', 'blueprint'] } = options; + if (commandsConfigs) { + filteredScopeEntries(commandsConfigs, scopes).forEach(([key, def]) => { + let configuredValue = source?.[key] ?? templatesContext[key]; + if (applyDefaults && def.default !== undefined && (configuredValue === undefined || configuredValue === null)) { + if (typeof def.default === 'function') { + configuredValue = def.default.call(this, source); + } else { + configuredValue = def.default; + } + } + templatesContext[key] = configuredValue; + key = key === 'serviceDiscoveryType' ? 'serviceDiscovery' : key; + if (def.choices) { + const choices = prepareChoices(key, def.choices); + const hasConfiguredValue = configuredValue !== undefined && configuredValue !== null; + for (const { choiceKey, value: choiceValue } of choices) { + if (hasConfiguredValue) { + templatesContext[choiceKey] = configuredValue === choiceValue; + } else { + templatesContext[choiceKey] = templatesContext[choiceKey] ?? undefined; + } + } + templatesContext[`${key}Any`] = hasConfiguredValue ? configuredValue !== 'no' : undefined; + } + }); + } +} + +export function loadCommandConfigsIntoApplication( + this: Context, + options: { + source: Record; + application: Record; + commandsConfigs: JHipsterConfigs; + }, +): void { + const { application, commandsConfigs, source } = options; + loadConfigIntoContext.call(this, { + source, + templatesContext: application, + commandsConfigs, + scopes: ['storage', 'blueprint'], + }); +} + +export function loadCommandConfigsIntoGenerator( + this: Context, + options: { + commandsConfigs: JHipsterConfigs; + }, +): void { + const { commandsConfigs } = options; + loadConfigIntoContext.call(this, { + source: (this as any).options, + templatesContext: this as any, + commandsConfigs, + scopes: ['storage', 'blueprint'], + }); +} + +export function loadCommandConfigsKeysIntoTemplatesContext( + this: Context, + options: { + templatesContext: Record; + commandsConfigs: JHipsterConfigs; + scopes?: CommandConfigScope[]; + }, +): void { + const { templatesContext, commandsConfigs, scopes = ['storage', 'blueprint'] } = options; + if (commandsConfigs) { + filteredScopeEntries(commandsConfigs, scopes).forEach(([key, def]) => { + templatesContext[key] = templatesContext[key] ?? undefined; + if (def.choices) { + for (const { choiceKey } of prepareChoices(key, def.choices)) { + templatesContext[choiceKey] = templatesContext[choiceKey] ?? undefined; + } + templatesContext[`${key}Any`] = templatesContext[`${key}Any`] ?? undefined; + if (key === 'serviceDiscoveryType') { + key = 'serviceDiscovery'; + for (const { choiceKey } of prepareChoices(key, def.choices)) { + templatesContext[choiceKey] = templatesContext[choiceKey] ?? undefined; + } + templatesContext[`${key}Any`] = templatesContext[`${key}Any`] ?? undefined; + } + } + }); + } +} diff --git a/lib/command/lookup-commands-configs.ts b/lib/command/lookup-commands-configs.ts new file mode 100644 index 000000000000..43af04bf135e --- /dev/null +++ b/lib/command/lookup-commands-configs.ts @@ -0,0 +1,29 @@ +import { dirname, join } from 'path'; +import { fileURLToPath } from 'url'; +import { glob } from 'glob'; +import type { JHipsterConfig, JHipsterConfigs } from './types.js'; + +const __filename = fileURLToPath(import.meta.url); +const __dirname = dirname(__filename); + +const cwd = join(__dirname, '../..'); +let jdlConfigs: JHipsterConfigs; + +export const lookupCommandsConfigs = async (options?: { filter: (config: JHipsterConfig) => boolean }): Promise => { + if (jdlConfigs) { + return jdlConfigs; + } + const { filter = () => true } = options ?? {}; + jdlConfigs = {}; + const files = [...(await glob('generators/*/index.{j,t}s', { cwd })), ...(await glob('generators/*/generators/*/index.{j,t}s', { cwd }))]; + for (const file of files) { + const index = await import(`${cwd}/${file}`); + const configs: JHipsterConfigs = index.command?.configs ?? {}; + for (const [key, value] of Object.entries(configs)) { + if (filter(value)) { + jdlConfigs[key] = value; + } + } + } + return jdlConfigs; +}; diff --git a/lib/command/types.d.ts b/lib/command/types.d.ts index 3c9cb3804607..5fa012ff2a63 100644 --- a/lib/command/types.d.ts +++ b/lib/command/types.d.ts @@ -31,7 +31,7 @@ export type PromptSpec = { type JHipsterArgumentConfig = SetOptional & { scope?: CommandConfigScope }; -export type ConfigSpec = { +export type ConfigSpec = { readonly description?: string; readonly choices?: JHispterChoices; @@ -39,7 +39,7 @@ export type ConfigSpec = { readonly argument?: JHipsterArgumentConfig; readonly prompt?: | PromptSpec - | ((gen: Generator & { jhipsterConfigWithDefaults: Record }, config: ConfigSpec) => PromptSpec); + | ((gen: ConfigContext & { jhipsterConfigWithDefaults: Record }, config: ConfigSpec) => PromptSpec); readonly jdl?: Omit; readonly storage?: { readonly type?: typeof Boolean | typeof String | typeof Number | typeof Array; @@ -58,26 +58,25 @@ export type ConfigSpec = { | boolean | number | readonly string[] - | ((this: Generator | void, ctx: any) => string | boolean | number | readonly string[]); + | ((this: ConfigContext | void, ctx: any) => string | boolean | number | readonly string[]); /** * Configure the generator according to the selected configuration. */ - readonly configure?: (gen: Generator) => void; + readonly configure?: (gen: ConfigContext) => void; }; export type JHipsterArguments = Record; export type JHipsterOptions = Record; -export type JHipsterConfigs = Record< - string, - RequireAtLeastOne, 'argument' | 'cli' | 'prompt' | 'storage'> ->; +export type JHipsterConfig = RequireAtLeastOne, 'argument' | 'cli' | 'prompt' | 'storage'>; + +export type JHipsterConfigs = Record>; -export type JHipsterCommandDefinition = { +export type JHipsterCommandDefinition = { readonly arguments?: JHipsterArguments; readonly options?: JHipsterOptions; - readonly configs?: JHipsterConfigs; + readonly configs?: JHipsterConfigs; /** * Import options from a generator. * @example ['server', 'jhipster-blueprint:server'] diff --git a/lib/jdl/core/__test-support__/index.ts b/lib/jdl/core/__test-support__/index.ts index 12d402021de0..80bcf3dd6331 100644 --- a/lib/jdl/core/__test-support__/index.ts +++ b/lib/jdl/core/__test-support__/index.ts @@ -8,12 +8,12 @@ import { createImporterFromFiles as originalCreateImporterFromFiles, } from '../../jdl-importer.js'; import type { ParsedJDLApplication, ParsedJDLRoot } from '../types/parsed.js'; -import definition from '../../../../generators/app/jdl/index.js'; import { createJDLLinterFromContent as originalCreateJDLLinterFromContent } from '../linters/jdl-linter.js'; import { convertApplications as originalConvertApplications } from '../../converters/parsed-jdl-to-jdl-object/application-converter.js'; import { createJDLApplication as originalCreateJDLApplication } from '../models/jdl-application-factory.js'; import type { JHipsterYoRcContentAndJDLWrapper } from '../../converters/json-to-jdl-application-converter.js'; import { convertApplicationsToJDL as originalConvertApplicationsToJDL } from '../../converters/json-to-jdl-application-converter.js'; +import { getDefaultJDLApplicationConfig } from '../../../command/jdl.js'; const __filename = fileURLToPath(import.meta.url); const __dirname = dirname(__filename); @@ -21,9 +21,9 @@ const __dirname = dirname(__filename); const runtime = getDefaultRuntime(); export const createImporterFromContent = (content: any, configuration?: any) => - originalCreateImporterFromContent(content, configuration, definition); + originalCreateImporterFromContent(content, configuration, getDefaultJDLApplicationConfig()); export const createImporterFromFiles = (files: any, configuration?: any) => - originalCreateImporterFromFiles(files, configuration, definition); + originalCreateImporterFromFiles(files, configuration, getDefaultJDLApplicationConfig()); export const parseFromConfigurationObject = (configuration: ParsedJDLRoot) => originalParseFromConfigurationObject(configuration, runtime); export const parseFromFiles = (files: string[]) => originalParseFromFiles(files, runtime); diff --git a/lib/jdl/core/built-in-options/jdl-application-definition.ts b/lib/jdl/core/built-in-options/jdl-application-definition.ts index 8e91a3a84383..c50cd14d4781 100644 --- a/lib/jdl/core/built-in-options/jdl-application-definition.ts +++ b/lib/jdl/core/built-in-options/jdl-application-definition.ts @@ -32,7 +32,7 @@ export default class JDLApplicationDefinition { * @param {String|Boolean|Number} value - the option value. * @returns {Boolean} whether the option value exists for the name. */ - doesOptionValueExist(name: string, value: string): boolean { + doesOptionValueExist(name: string, value: string | string[]): boolean { if (!this.doesOptionExist(name)) { return false; } @@ -52,7 +52,7 @@ export default class JDLApplicationDefinition { * @returns {Boolean} the option's existence. */ doesOptionExist(optionName: string): boolean { - return !!optionName && optionName in this.optionTypes; + return Boolean(optionName && optionName in this.optionTypes); } /** diff --git a/lib/jdl/core/models/jdl-application-configuration-factory.ts b/lib/jdl/core/models/jdl-application-configuration-factory.ts index 000f1b5c90cc..24035ebf647c 100644 --- a/lib/jdl/core/models/jdl-application-configuration-factory.ts +++ b/lib/jdl/core/models/jdl-application-configuration-factory.ts @@ -38,6 +38,12 @@ export default function createApplicationConfigurationFromObject( logger.debug(`Unrecognized application option name and value: '${optionName}' and '${optionValue}'.`); return; } + if ( + (Array.isArray(optionValue) || typeof optionValue === 'string') && + !runtime.applicationDefinition.doesOptionValueExist(optionName, optionValue) + ) { + throw new Error(`The value '${optionValue}' is not allowed for the option '${optionName}'.`); + } configuration.setOption(createApplicationJDLConfigurationOption(optionName, optionValue, runtime)); }); return configuration; diff --git a/lib/jdl/core/readers/json-reader.spec.ts b/lib/jdl/core/readers/json-reader.spec.ts deleted file mode 100644 index f94ff8f1c626..000000000000 --- a/lib/jdl/core/readers/json-reader.spec.ts +++ /dev/null @@ -1,94 +0,0 @@ -/** - * Copyright 2013-2024 the original author or authors from the JHipster project. - * - * This file is part of the JHipster project, see https://www.jhipster.tech/ - * for more information. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import { renameSync } from 'fs'; -import { after, before, describe, it } from 'esmocha'; -import { expect } from 'chai'; - -import { unaryOptions } from '../built-in-options/index.js'; -import { getTestFile } from '../__test-support__/index.js'; -import parseFromDir from './json-reader.js'; - -const { SKIP_CLIENT, SKIP_SERVER } = unaryOptions; - -describe('jdl - JSONReader', () => { - describe('parseFromDir', () => { - describe('when passing invalid parameters', () => { - describe('such as nil', () => { - it('should fail', () => { - expect(() => { - // @ts-expect-error - parseFromDir(null); - }).to.throw(/^The app directory must be passed to read JSON files\.$/); - }); - }); - describe('such as a file', () => { - it('should fail', () => { - expect(() => { - parseFromDir('../../__test-files__/invalid_file.txt'); - }).to.throw( - new RegExp( - "The passed directory '../../__test-files__/invalid_file.txt' must exist and must be a directory to read JSON files.", - ), - ); - }); - }); - describe('such as a dir that does not exist', () => { - it('should fail', () => { - expect(() => { - parseFromDir('nodir'); - }).to.throw(new RegExp("The passed directory 'nodir' must exist and must be a directory to read JSON files.")); - }); - }); - }); - describe('when passing valid arguments', () => { - describe('when reading a jhipster app dir', () => { - let content; - - before(() => { - renameSync( - getTestFile('jhipster_app', '.jhipster', 'InvalidBlobType.json'), - getTestFile('jhipster_app', '.jhipster', 'InvalidBlobType.txt'), - ); - content = parseFromDir(getTestFile('jhipster_app')); - }); - after(() => { - renameSync( - getTestFile('jhipster_app', '.jhipster', 'InvalidBlobType.txt'), - getTestFile('jhipster_app', '.jhipster', 'InvalidBlobType.json'), - ); - }); - - it('should read it', () => { - expect(content.entities.Country).not.to.be.undefined; - expect(content.entities.Department).not.to.be.undefined; - expect(content.entities.Employee).not.to.be.undefined; - expect(content.entities.Job).not.to.be.undefined; - expect(content.entities.JobHistory).not.to.be.undefined; - expect(content.entities.Region).not.to.be.undefined; - expect(content.entities.Task).not.to.be.undefined; - expect(content.entities.NoEntity).to.be.undefined; - expect(content.entities.BadEntity).to.be.undefined; - expect(content.getOptions().filter(o => o.name === SKIP_CLIENT).length).eq(1); - expect(content.getOptions().filter(o => o.name === SKIP_SERVER).length).eq(1); - }); - }); - }); - }); -}); diff --git a/lib/jdl/core/readers/json-reader.ts b/lib/jdl/core/readers/json-reader.ts deleted file mode 100644 index 948363de7b74..000000000000 --- a/lib/jdl/core/readers/json-reader.ts +++ /dev/null @@ -1,59 +0,0 @@ -/** - * Copyright 2013-2024 the original author or authors from the JHipster project. - * - * This file is part of the JHipster project, see https://www.jhipster.tech/ - * for more information. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import fs from 'fs'; -import { convertEntitiesToJDL } from '../../converters/json-to-jdl-entity-converter.js'; -import { convertServerOptionsToJDL } from '../../converters/json-to-jdl-option-converter.js'; -import mergeJDLObjects from '../models/jdl-object-merger.js'; -import { doesDirectoryExist } from '../utils/file-utils.js'; -import type JDLObject from '../models/jdl-object.js'; -import type { JSONEntity } from '../types/json-config.js'; -import { readEntityFile, readYoRcFile } from '../../../utils/yo-rc.js'; - -/* Parse the given jhipster app dir and return a JDLObject */ -export default function parseFromDir(dir: string): JDLObject { - if (!dir) { - throw new Error('The app directory must be passed to read JSON files.'); - } - if (!doesDirectoryExist(dir)) { - throw new Error(`The passed directory '${dir}' must exist and must be a directory to read JSON files.`); - } - const entityDir = `${dir}/.jhipster`; - if (!doesDirectoryExist(entityDir)) { - throw new Error(`'${entityDir}' must exist as a directory.`); - } - const entities = new Map(); - const files = fs.readdirSync(entityDir); - files.forEach(file => { - if (file.endsWith('.json')) { - const entityName = file.slice(0, file.length - 5); - try { - entities.set(entityName, readEntityFile(dir, entityName)); - } catch { - // Not an entity file, not adding - } - } - }); - const applicationOptions = readYoRcFile(dir)['generator-jhipster']; - - // @ts-expect-error TODO - const jdlObject = convertServerOptionsToJDL(applicationOptions); - const convertedJDLObject = convertEntitiesToJDL(entities); - return mergeJDLObjects(jdlObject, convertedJDLObject); -} diff --git a/lib/jdl/core/runtime.ts b/lib/jdl/core/runtime.ts index 1b3de2b76c8e..cf09e308f676 100644 --- a/lib/jdl/core/runtime.ts +++ b/lib/jdl/core/runtime.ts @@ -1,6 +1,6 @@ import type { Lexer, TokenType } from 'chevrotain'; -import jhipsterDefinition from '../../../generators/app/jdl/index.js'; import { builtInJDLApplicationConfig } from '../../jhipster/application-options.js'; +import { getDefaultJDLApplicationConfig } from '../../command/jdl.js'; import { buildTokens, createJDLLexer } from './parsing/lexer/lexer.js'; import JDLParser from './parsing/jdl-parser.js'; import { checkConfigKeys, checkTokens } from './parsing/self-checks/parsing-system-checker.js'; @@ -74,9 +74,9 @@ export const createRuntime = (definition: JDLApplicationConfig): JDLRuntime => { }; let defaultRuntime: JDLRuntime; -export const getDefaultRuntime = () => { +export const getDefaultRuntime = (): JDLRuntime => { if (!defaultRuntime) { - defaultRuntime = createRuntime(jhipsterDefinition); + defaultRuntime = createRuntime(getDefaultJDLApplicationConfig()); } return defaultRuntime; diff --git a/lib/jhipster/application-options.ts b/lib/jhipster/application-options.ts index f20d30aaa4ef..ba6f61f87af8 100644 --- a/lib/jhipster/application-options.ts +++ b/lib/jhipster/application-options.ts @@ -297,7 +297,8 @@ export const jhipsterQuotedOptionNames: string[] = [ export const builtInJDLApplicationConfig: JDLApplicationConfig = { optionsTypes: jhipsterOptionTypes, - optionsValues: jhipsterOptionValues, + // Don't validate built-in options. + optionsValues: {}, quotedOptionNames: jhipsterQuotedOptionNames, validatorConfig: builtInConfigPropsValidations, tokenConfigs: [], diff --git a/lib/jhipster/default-application-options.ts b/lib/jhipster/default-application-options.ts index 046949ec2de6..02350746f4db 100644 --- a/lib/jhipster/default-application-options.ts +++ b/lib/jhipster/default-application-options.ts @@ -17,7 +17,7 @@ * limitations under the License. */ -import { MESSAGE_BROKER, MESSAGE_BROKER_NO } from '../../generators/server/options/message-broker.js'; +import type { ApplicationConfiguration } from '../types/application/yo-rc.js'; import applicationTypes from './application-types.js'; import authenticationTypes from './authentication-types.js'; import databaseTypes from './database-types.js'; @@ -76,7 +76,7 @@ const { GRADLE_ENTERPRISE_HOST, } = OptionNames; -const commonDefaultOptions = { +const commonDefaultOptions: Partial = { [AUTHENTICATION_TYPE]: JWT, [BUILD_TOOL]: MAVEN, [DTO_SUFFIX]: OptionValues[DTO_SUFFIX], @@ -84,7 +84,7 @@ const commonDefaultOptions = { [ENABLE_TRANSLATION]: OptionValues[ENABLE_TRANSLATION], [ENTITY_SUFFIX]: OptionValues[ENTITY_SUFFIX], [JHI_PREFIX]: OptionValues[JHI_PREFIX], - [MESSAGE_BROKER]: MESSAGE_BROKER_NO, + messageBroker: 'no', [SEARCH_ENGINE]: (OptionValues[SEARCH_ENGINE] as Record).no, [WEBSOCKET]: (OptionValues[WEBSOCKET] as Record).no, }; diff --git a/lib/testing/get-generator.ts b/lib/testing/get-generator.ts index 3e80610d7c07..c1587fedb43a 100644 --- a/lib/testing/get-generator.ts +++ b/lib/testing/get-generator.ts @@ -1,9 +1,12 @@ -import { resolve } from 'path'; +import { dirname, resolve } from 'path'; import { existsSync } from 'fs'; -import { getPackageRoot } from '../index.js'; +import { fileURLToPath } from 'url'; + +const __filename = fileURLToPath(import.meta.url); +const __dirname = dirname(__filename); export const getGeneratorFolder = (generatorName: string) => { - return resolve(getPackageRoot(), 'generators', generatorName.split(':').join('/generators/')); + return resolve(__dirname, '../../generators', generatorName.split(':').join('/generators/')); }; const getGenerator = (generatorName: string) => { diff --git a/lib/testing/helpers.ts b/lib/testing/helpers.ts index f9cef0d09988..83488401b775 100644 --- a/lib/testing/helpers.ts +++ b/lib/testing/helpers.ts @@ -14,12 +14,12 @@ import { createJHipsterLogger, normalizePathEnd, parseCreationTimestamp } from ' import BaseGenerator from '../../generators/base/index.js'; import type { JHipsterGeneratorOptions } from '../../generators/base/api.js'; import { getPackageRoot, isDistFolder } from '../index.js'; -import type { JSONEntity } from '../jdl/core/types/json-config.js'; import type CoreGenerator from '../../generators/base-core/generator.js'; import type { ApplicationConfiguration } from '../types/application/yo-rc.js'; +import { getDefaultJDLApplicationConfig } from '../command/jdl.js'; +import type { Entity } from '../types/base/entity.js'; import getGenerator from './get-generator.js'; -type BaseEntity = { name: string } & JSONEntity; type GeneratorTestType = YeomanGenerator; type GeneratorTestOptions = JHipsterGeneratorOptions; @@ -85,7 +85,7 @@ export const defineDefaults = async ({ } }; -const createFiles = (workspaceFolder: string, configuration: Record, entities?: BaseEntity[]) => { +const createFiles = (workspaceFolder: string, configuration: Record, entities?: Entity[]): Record => { if (!configuration.baseName) { throw new Error('baseName is required'); } @@ -100,7 +100,7 @@ const createFiles = (workspaceFolder: string, configuration: Record, entities?: BaseEntity[]) => +export const createJHipsterConfigFiles = (configuration: Record, entities?: Entity[]) => createFiles('', configuration, entities); export type FakeBlueprintOptions = { @@ -155,7 +155,7 @@ class JHipsterRunContext extends RunContext { withJHipsterConfig( configuration?: Readonly>, - entities?: BaseEntity[], + entities?: Entity[], ): this { return this.withFiles( createFiles('', { baseName: 'jhipster', creationTimestamp: parseCreationTimestamp('2020-01-01'), ...configuration }, entities), @@ -174,7 +174,7 @@ class JHipsterRunContext extends RunContext { return this; } - withWorkspaceApplicationAtFolder(workspaceFolder: string, configuration: Record, entities?: BaseEntity[]): this { + withWorkspaceApplicationAtFolder(workspaceFolder: string, configuration: Record, entities?: Entity[]): this { if (this.generateApplicationsSet) { throw new Error('Cannot be called after withWorkspaceApplication'); } @@ -182,7 +182,7 @@ class JHipsterRunContext extends RunContext { return this.withFiles(createFiles(workspaceFolder, { ...configuration, ...this.commonWorkspacesConfig }, entities)); } - withWorkspaceApplication(configuration: Record, entities?: BaseEntity[]): this { + withWorkspaceApplication(configuration: Record, entities?: Entity[]): this { return this.withWorkspaceApplicationAtFolder(configuration.baseName as string, configuration, entities); } @@ -377,7 +377,12 @@ class JHipsterTest extends YeomanTest { settings?: RunContextSettings | undefined, envOptions?: BaseEnvironmentOptions | undefined, ): JHipsterRunContext { - return super.run(GeneratorOrNamespace, settings, envOptions).withAdapterOptions({ log: createJHipsterLogger() }) as any; + return super + .run(GeneratorOrNamespace, settings, envOptions) + .withOptions({ + jdlDefinition: getDefaultJDLApplicationConfig(), + } as any) + .withAdapterOptions({ log: createJHipsterLogger() }) as any; } runJHipster( diff --git a/lib/types/application/entity.d.ts b/lib/types/application/entity.d.ts index 7e5310f39095..a9e2a6d0c1d1 100644 --- a/lib/types/application/entity.d.ts +++ b/lib/types/application/entity.d.ts @@ -53,7 +53,6 @@ export interface Entity & ExportGeneratorOptionsFromCommand & ExportGeneratorOptionsFromCommand & + ExportGeneratorOptionsFromCommand & ExportGeneratorOptionsFromCommand >; diff --git a/lib/types/application/yo-rc.d.ts b/lib/types/application/yo-rc.d.ts index 36d02f1cd54b..9f67e2ee4604 100644 --- a/lib/types/application/yo-rc.d.ts +++ b/lib/types/application/yo-rc.d.ts @@ -24,6 +24,7 @@ export type ApplicationConfiguration = Simplify< ExportStoragePropertiesFromCommand & ExportStoragePropertiesFromCommand & ExportStoragePropertiesFromCommand & + ExportStoragePropertiesFromCommand & ExportStoragePropertiesFromCommand >; diff --git a/lib/types/base/entity.d.ts b/lib/types/base/entity.d.ts index 86de9c656d7b..918aea72c4f3 100644 --- a/lib/types/base/entity.d.ts +++ b/lib/types/base/entity.d.ts @@ -18,4 +18,6 @@ export type Entity